skills 1.5.24 → 1.5.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +6 -6
  2. package/dist/cli.mjs +284 -100
  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
@@ -18,7 +18,7 @@ import { Writable } from "stream";
18
18
  import { promisify } from "util";
19
19
  import { execFile, spawn, spawnSync } from "child_process";
20
20
  import { access, chmod, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
21
- import { parse } from "yaml";
21
+ import { parse, stringify } from "yaml";
22
22
  import { createHash } from "crypto";
23
23
  import { mkdir as mkdir$1, mkdtemp as mkdtemp$1, readFile as readFile$1, rm as rm$1, stat as stat$1, writeFile as writeFile$1 } from "node:fs/promises";
24
24
  import { createHash as createHash$1 } from "node:crypto";
@@ -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
  }
@@ -1139,11 +1153,13 @@ const AGENT_PROJECT_SKILL_DIRS = [
1139
1153
  ".codex/skills",
1140
1154
  ".commandcode/skills",
1141
1155
  ".continue/skills",
1156
+ ".factory/skills",
1142
1157
  ".github/skills",
1143
1158
  ".goose/skills",
1144
1159
  ".grok/skills",
1145
1160
  ".iflow/skills",
1146
1161
  ".junie/skills",
1162
+ ".kilo/skills",
1147
1163
  ".kilocode/skills",
1148
1164
  ".kimchi/skills",
1149
1165
  ".kiro/skills",
@@ -1339,6 +1355,7 @@ const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
1339
1355
  const hermesHome = process.env.HERMES_HOME?.trim() || join(home, ".hermes");
1340
1356
  const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home, ".autohand");
1341
1357
  const grokHome = process.env.GROK_HOME?.trim() || join(home, ".grok");
1358
+ const sarvamHome = process.env.SARVAM_HOME?.trim() || join(home, ".sarvam");
1342
1359
  const zedAppDataHome = process.env.APPDATA?.trim();
1343
1360
  const zedFlatpakConfigHome = process.env.FLATPAK_XDG_CONFIG_HOME?.trim();
1344
1361
  function packageJsonHasDependency(packageJsonPath, dependencyName) {
@@ -1391,6 +1408,7 @@ const agents = {
1391
1408
  displayName: "Antigravity",
1392
1409
  skillsDir: ".agents/skills",
1393
1410
  globalSkillsDir: join(home, ".gemini/antigravity/skills"),
1411
+ showInUniversalPrompt: false,
1394
1412
  detectInstalled: async () => {
1395
1413
  return existsSync(join(home, ".gemini/antigravity"));
1396
1414
  }
@@ -1400,6 +1418,7 @@ const agents = {
1400
1418
  displayName: "Antigravity CLI",
1401
1419
  skillsDir: ".agents/skills",
1402
1420
  globalSkillsDir: join(home, ".gemini/antigravity-cli/skills"),
1421
+ showInUniversalPrompt: false,
1403
1422
  detectInstalled: async () => {
1404
1423
  return existsSync(join(home, ".gemini/antigravity-cli"));
1405
1424
  }
@@ -1445,6 +1464,7 @@ const agents = {
1445
1464
  displayName: "Claude Code",
1446
1465
  skillsDir: ".claude/skills",
1447
1466
  globalSkillsDir: join(claudeHome, "skills"),
1467
+ createProjectSkillsDirByDefault: true,
1448
1468
  detectInstalled: async () => {
1449
1469
  return existsSync(claudeHome);
1450
1470
  }
@@ -1562,6 +1582,7 @@ const agents = {
1562
1582
  displayName: "Deep Agents",
1563
1583
  skillsDir: ".agents/skills",
1564
1584
  globalSkillsDir: join(home, ".deepagents/agent/skills"),
1585
+ showInUniversalPrompt: false,
1565
1586
  detectInstalled: async () => {
1566
1587
  return existsSync(join(home, ".deepagents"));
1567
1588
  }
@@ -1588,7 +1609,7 @@ const agents = {
1588
1609
  droid: {
1589
1610
  name: "droid",
1590
1611
  displayName: "Droid",
1591
- skillsDir: ".factory/skills",
1612
+ skillsDir: ".agents/skills",
1592
1613
  globalSkillsDir: join(home, ".factory/skills"),
1593
1614
  detectInstalled: async () => {
1594
1615
  return existsSync(join(home, ".factory"));
@@ -1623,6 +1644,15 @@ const agents = {
1623
1644
  return existsSync(join(home, ".forge"));
1624
1645
  }
1625
1646
  },
1647
+ fx: {
1648
+ name: "fx",
1649
+ displayName: "fx",
1650
+ skillsDir: ".fx/skills",
1651
+ globalSkillsDir: join(home, ".fx/skills"),
1652
+ detectInstalled: async () => {
1653
+ return existsSync(join(home, ".fx"));
1654
+ }
1655
+ },
1626
1656
  "gemini-cli": {
1627
1657
  name: "gemini-cli",
1628
1658
  displayName: "Gemini CLI",
@@ -1707,10 +1737,10 @@ const agents = {
1707
1737
  kilo: {
1708
1738
  name: "kilo",
1709
1739
  displayName: "Kilo Code",
1710
- skillsDir: ".kilocode/skills",
1711
- globalSkillsDir: join(home, ".kilocode/skills"),
1740
+ skillsDir: ".agents/skills",
1741
+ globalSkillsDir: join(home, ".kilo/skills"),
1712
1742
  detectInstalled: async () => {
1713
- return existsSync(join(home, ".kilocode"));
1743
+ return existsSync(join(home, ".kilo")) || existsSync(join(home, ".kilocode"));
1714
1744
  }
1715
1745
  },
1716
1746
  kimchi: {
@@ -1922,6 +1952,16 @@ const agents = {
1922
1952
  return existsSync(join(home, ".roo"));
1923
1953
  }
1924
1954
  },
1955
+ "sarvam-code": {
1956
+ name: "sarvam-code",
1957
+ displayName: "Sarvam Code",
1958
+ skillsDir: ".agents/skills",
1959
+ globalSkillsDir: join(home, ".agents/skills"),
1960
+ showInUniversalPrompt: false,
1961
+ detectInstalled: async () => {
1962
+ return existsSync(sarvamHome);
1963
+ }
1964
+ },
1925
1965
  "tabnine-cli": {
1926
1966
  name: "tabnine-cli",
1927
1967
  displayName: "Tabnine CLI",
@@ -2106,6 +2146,11 @@ function isPathSafe$2(basePath, targetPath) {
2106
2146
  function pathsOverlap(pathA, pathB) {
2107
2147
  return isPathSafe$2(pathA, pathB) || isPathSafe$2(pathB, pathA);
2108
2148
  }
2149
+ function shouldSkipProjectAgentSymlink(agentType, isGlobal, cwd, createMissingAgentRoot) {
2150
+ if (isGlobal || isUniversalAgent(agentType) || createMissingAgentRoot || agents[agentType].createProjectSkillsDirByDefault) return false;
2151
+ const agentRoot = agents[agentType].skillsDir.split("/")[0];
2152
+ return !existsSync(join(cwd, agentRoot));
2153
+ }
2109
2154
  async function isDirEntryOrSymlinkToDir(entry, entryPath) {
2110
2155
  if (entry.isDirectory()) return true;
2111
2156
  if (!entry.isSymbolicLink()) return false;
@@ -2241,15 +2286,14 @@ async function installSkillForAgent(skill, agentType, options = {}) {
2241
2286
  canonicalPath: canonicalDir,
2242
2287
  mode: "symlink"
2243
2288
  };
2244
- if (!isGlobal && !isUniversalAgent(agentType)) {
2245
- if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0])) && agentType !== "claude-code") return {
2246
- success: true,
2247
- path: canonicalDir,
2248
- canonicalPath: canonicalDir,
2249
- mode: "symlink",
2250
- skipped: true
2251
- };
2252
- }
2289
+ if (shouldSkipProjectAgentSymlink(agentType, isGlobal, cwd, options.createMissingAgentRoot ?? false)) return {
2290
+ success: true,
2291
+ path: canonicalDir,
2292
+ canonicalPath: canonicalDir,
2293
+ mode: "symlink",
2294
+ skipped: true,
2295
+ skipReason: "missing-agent-project-directory"
2296
+ };
2253
2297
  if (!await createSymlink(canonicalDir, agentDir)) {
2254
2298
  await cleanAndCreateDirectory(agentDir);
2255
2299
  await copyDirectory(skill.path, agentDir, agentType);
@@ -2290,15 +2334,14 @@ const isExcluded$1 = (name, isDirectory = false) => {
2290
2334
  function stripIgnoredEveFrontmatter(raw) {
2291
2335
  const { data, content } = parseFrontmatter(raw);
2292
2336
  const eveData = {};
2337
+ if (typeof data.name === "string") eveData.name = data.name;
2293
2338
  if (typeof data.description === "string") eveData.description = data.description;
2294
2339
  if (typeof data.license === "string") eveData.license = data.license;
2295
- if (data.metadata && typeof data.metadata === "object" && !Array.isArray(data.metadata)) {
2296
- const metadata = Object.fromEntries(Object.entries(data.metadata).filter((entry) => typeof entry[1] === "string"));
2297
- if (Object.keys(metadata).length > 0) eveData.metadata = metadata;
2298
- }
2299
- const keys = Object.keys(eveData);
2300
- if (keys.length === 0) return content.replace(/^\r?\n/u, "");
2301
- return `---\n${keys.map((key) => `${key}: ${JSON.stringify(eveData[key])}`).join("\n")}\n---\n${content.replace(/^\r?\n/u, "")}`;
2340
+ if (data.compatibility !== void 0) eveData.compatibility = data.compatibility;
2341
+ if (typeof data.version === "string" || typeof data.version === "number") eveData.version = data.version;
2342
+ if (data.metadata && typeof data.metadata === "object" && !Array.isArray(data.metadata)) eveData.metadata = data.metadata;
2343
+ if (Object.keys(eveData).length === 0) return content.replace(/^\r?\n/u, "");
2344
+ return `---\n${stringify(eveData).trimEnd()}\n---\n${content.replace(/^\r?\n/u, "")}`;
2302
2345
  }
2303
2346
  async function copyDirectory(src, dest, agentType) {
2304
2347
  await mkdir(dest, { recursive: true });
@@ -2532,15 +2575,14 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
2532
2575
  canonicalPath: canonicalDir,
2533
2576
  mode: "symlink"
2534
2577
  };
2535
- if (!isGlobal && !isUniversalAgent(agentType)) {
2536
- if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0])) && agentType !== "claude-code") return {
2537
- success: true,
2538
- path: canonicalDir,
2539
- canonicalPath: canonicalDir,
2540
- mode: "symlink",
2541
- skipped: true
2542
- };
2543
- }
2578
+ if (shouldSkipProjectAgentSymlink(agentType, isGlobal, cwd, options.createMissingAgentRoot ?? false)) return {
2579
+ success: true,
2580
+ path: canonicalDir,
2581
+ canonicalPath: canonicalDir,
2582
+ mode: "symlink",
2583
+ skipped: true,
2584
+ skipReason: "missing-agent-project-directory"
2585
+ };
2544
2586
  if (!await createSymlink(canonicalDir, agentDir)) {
2545
2587
  await cleanAndCreateDirectory(agentDir);
2546
2588
  await writeSkillFiles(agentDir);
@@ -3871,11 +3913,13 @@ const PRIORITY_PREFIXES = [
3871
3913
  ".codex/skills/",
3872
3914
  ".commandcode/skills/",
3873
3915
  ".continue/skills/",
3916
+ ".factory/skills/",
3874
3917
  ".github/skills/",
3875
3918
  ".goose/skills/",
3876
3919
  ".grok/skills/",
3877
3920
  ".iflow/skills/",
3878
3921
  ".junie/skills/",
3922
+ ".kilo/skills/",
3879
3923
  ".kilocode/skills/",
3880
3924
  ".kimchi/skills/",
3881
3925
  ".kiro/skills/",
@@ -3977,6 +4021,35 @@ function computeSnapshotHash(files) {
3977
4021
  }
3978
4022
  return hash.digest("hex");
3979
4023
  }
4024
+ function getSkillFolderPath(skillMdPath) {
4025
+ const pathLower = skillMdPath.toLowerCase();
4026
+ if (pathLower.endsWith("/skill.md")) return skillMdPath.slice(0, -9);
4027
+ if (pathLower === "skill.md") return "";
4028
+ return skillMdPath.slice(0, -9);
4029
+ }
4030
+ const SNAPSHOT_EXCLUDED_FILES = /* @__PURE__ */ new Set(["metadata.json"]);
4031
+ const SNAPSHOT_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
4032
+ ".git",
4033
+ "__pycache__",
4034
+ "__pypackages__"
4035
+ ]);
4036
+ function isInstallableSnapshotPath(path) {
4037
+ const parts = path.split("/");
4038
+ const fileName = parts.at(-1);
4039
+ if (!fileName || SNAPSHOT_EXCLUDED_FILES.has(fileName)) return false;
4040
+ return parts.slice(0, -1).every((part) => !SNAPSHOT_EXCLUDED_DIRS.has(part));
4041
+ }
4042
+ function hasCompleteNestedSnapshot(tree, skillMdPath, files) {
4043
+ const folderPath = getSkillFolderPath(skillMdPath);
4044
+ if (!folderPath) return true;
4045
+ const folderPrefix = `${folderPath}/`;
4046
+ const snapshotPaths = new Set(files.map((file) => file.path));
4047
+ return tree.tree.every((entry) => {
4048
+ if (entry.type !== "blob" || !entry.path.startsWith(folderPrefix)) return true;
4049
+ const relativePath = entry.path.slice(folderPrefix.length);
4050
+ return !isInstallableSnapshotPath(relativePath) || snapshotPaths.has(relativePath);
4051
+ });
4052
+ }
3980
4053
  async function tryBlobInstall(ownerRepo, options = {}) {
3981
4054
  if (options.ref !== void 0) return null;
3982
4055
  const tree = await fetchRepoTree(ownerRepo, options.ref, options.getToken);
@@ -4033,10 +4106,10 @@ async function tryBlobInstall(ownerRepo, options = {}) {
4033
4106
  };
4034
4107
  }));
4035
4108
  if (!downloads.every((d) => d.download !== null)) return null;
4109
+ if (!downloads.every(({ skill, download }) => hasCompleteNestedSnapshot(tree, skill.mdPath, download.files))) return null;
4036
4110
  return {
4037
4111
  skills: downloads.map(({ skill, download }) => {
4038
- const mdPathLower = skill.mdPath.toLowerCase();
4039
- 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");
4112
+ const files = getSkillFolderPath(skill.mdPath) ? download.files : download.files.filter((file) => file.path.toLowerCase() === "skill.md");
4040
4113
  return {
4041
4114
  name: skill.name,
4042
4115
  description: skill.description,
@@ -4051,7 +4124,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
4051
4124
  tree
4052
4125
  };
4053
4126
  }
4054
- var version$1 = "1.5.24";
4127
+ var version$1 = "1.5.26";
4055
4128
  const isCancelled$1 = (value) => typeof value === "symbol";
4056
4129
  const EVE_AGENT_LABEL = "eve agent";
4057
4130
  async function isSourcePrivate(source) {
@@ -4199,11 +4272,21 @@ function buildResultLines(results, targetAgents) {
4199
4272
  const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
4200
4273
  const successfulSymlinks = results.filter((r) => !r.symlinkFailed && !r.skipped && !universal.includes(r.agent)).map((r) => r.agent);
4201
4274
  const failedSymlinks = results.filter((r) => r.symlinkFailed && !r.skipped).map((r) => r.agent);
4275
+ const skippedSymlinks = results.filter((r) => r.skipped && r.skipReason === "missing-agent-project-directory" && symlinkAgents.includes(r.agent)).map((r) => r.agent);
4202
4276
  if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
4203
4277
  if (successfulSymlinks.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList$1(successfulSymlinks)}`);
4204
4278
  if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(failedSymlinks)}`);
4279
+ if (skippedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("skipped:")} ${formatList$1(skippedSymlinks)} ${import_picocolors.default.dim("(project directory not found)")}`);
4205
4280
  return lines;
4206
4281
  }
4282
+ function exitInstallationCancelled() {
4283
+ cancel("Installation cancelled");
4284
+ if (!process.stdin.isTTY) {
4285
+ 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.");
4286
+ process.exit(1);
4287
+ }
4288
+ process.exit(0);
4289
+ }
4207
4290
  async function promptForAgents(message, choices) {
4208
4291
  let lastSelected;
4209
4292
  try {
@@ -4263,6 +4346,17 @@ async function selectAgentsInteractive(options) {
4263
4346
  return selected;
4264
4347
  }
4265
4348
  setVersion(version$1);
4349
+ function buildJsonSecurity(auditData, skillName, source) {
4350
+ const data = auditData?.[skillName];
4351
+ if (!data || Object.keys(data).length === 0) return null;
4352
+ const socketAlerts = data.socket?.alerts ?? 0;
4353
+ return {
4354
+ ...data.ath && { gen: data.ath.risk },
4355
+ ...data.socket && { socket: `${socketAlerts} alert${socketAlerts !== 1 ? "s" : ""}` },
4356
+ ...data.snyk && { snyk: data.snyk.risk },
4357
+ ...source && { details: `https://skills.sh/${source}` }
4358
+ };
4359
+ }
4266
4360
  function isSkillsShPackUrl(url) {
4267
4361
  try {
4268
4362
  const parsed = new URL(url);
@@ -4337,10 +4431,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4337
4431
  maxVisible: 20,
4338
4432
  selectAll: true
4339
4433
  });
4340
- if (isCancelled$1(selected)) {
4341
- cancel("Installation cancelled");
4342
- process.exit(0);
4343
- }
4434
+ if (isCancelled$1(selected)) exitInstallationCancelled();
4344
4435
  selectedSkills = selected;
4345
4436
  }
4346
4437
  let targetAgents;
@@ -4370,10 +4461,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4370
4461
  value: key,
4371
4462
  label: config.displayName
4372
4463
  })));
4373
- if (isCancel(selected)) {
4374
- cancel("Installation cancelled");
4375
- process.exit(0);
4376
- }
4464
+ if (isCancelled$1(selected)) exitInstallationCancelled();
4377
4465
  targetAgents = selected;
4378
4466
  }
4379
4467
  else if (installedAgents.length === 1 || options.yes) {
@@ -4384,10 +4472,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4384
4472
  } else log.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
4385
4473
  } else {
4386
4474
  const selected = await selectAgentsInteractive({ global: options.global });
4387
- if (isCancel(selected)) {
4388
- cancel("Installation cancelled");
4389
- process.exit(0);
4390
- }
4475
+ if (isCancelled$1(selected)) exitInstallationCancelled();
4391
4476
  targetAgents = selected;
4392
4477
  }
4393
4478
  }
@@ -4406,10 +4491,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4406
4491
  hint: "Install in home directory (available across all projects)"
4407
4492
  }]
4408
4493
  });
4409
- if (isCancel(scope)) {
4410
- cancel("Installation cancelled");
4411
- process.exit(0);
4412
- }
4494
+ if (isCancel(scope)) exitInstallationCancelled();
4413
4495
  installGlobally = scope;
4414
4496
  }
4415
4497
  let installMode = options.copy ? "copy" : "symlink";
@@ -4427,10 +4509,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4427
4509
  hint: "Independent copies for each agent"
4428
4510
  }]
4429
4511
  });
4430
- if (isCancel(modeChoice)) {
4431
- cancel("Installation cancelled");
4432
- process.exit(0);
4433
- }
4512
+ if (isCancel(modeChoice)) exitInstallationCancelled();
4434
4513
  installMode = modeChoice;
4435
4514
  } else if (uniqueDirs.size <= 1) installMode = "copy";
4436
4515
  const cwd = process.cwd();
@@ -4460,10 +4539,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4460
4539
  note(summaryLines.join("\n"), "Installation Summary");
4461
4540
  if (!options.yes) {
4462
4541
  const confirmed = await confirm({ message: "Proceed with installation?" });
4463
- if (isCancel(confirmed) || !confirmed) {
4464
- cancel("Installation cancelled");
4465
- process.exit(0);
4466
- }
4542
+ if (isCancel(confirmed) || !confirmed) exitInstallationCancelled();
4467
4543
  }
4468
4544
  const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
4469
4545
  const wellKnownPrivacyPromise = isSourcePrivate(sourceIdentifier).catch(() => null);
@@ -4484,6 +4560,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4484
4560
  console.log();
4485
4561
  const successful = results.filter((r) => r.success);
4486
4562
  const failed = results.filter((r) => !r.success);
4563
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
4487
4564
  const skillFiles = {};
4488
4565
  for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
4489
4566
  if (await wellKnownPrivacyPromise !== true) track({
@@ -4498,7 +4575,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4498
4575
  sourceType: "well-known"
4499
4576
  });
4500
4577
  if (successful.length > 0 && installGlobally) {
4501
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
4502
4578
  for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
4503
4579
  await addSkillToLock(skill.installName, {
4504
4580
  source: sourceIdentifier,
@@ -4511,7 +4587,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4511
4587
  } catch {}
4512
4588
  }
4513
4589
  if (successful.length > 0 && !installGlobally) {
4514
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
4515
4590
  for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
4516
4591
  const matchingResult = successful.find((r) => r.skill === skill.installName);
4517
4592
  const installDir = matchingResult?.canonicalPath || matchingResult?.path;
@@ -4578,6 +4653,40 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4578
4653
  async function runAdd(args, options = {}) {
4579
4654
  const source = args[0];
4580
4655
  let installTipShown = false;
4656
+ const jsonMode = options.json === true;
4657
+ const jsonResults = [];
4658
+ const originalStdoutWrite = process.stdout.write;
4659
+ let stdoutSuppressed = false;
4660
+ if (jsonMode) {
4661
+ process.stdout.write = process.stderr.write.bind(process.stderr);
4662
+ stdoutSuppressed = true;
4663
+ }
4664
+ const restoreStdout = () => {
4665
+ if (stdoutSuppressed) {
4666
+ process.stdout.write = originalStdoutWrite;
4667
+ stdoutSuppressed = false;
4668
+ }
4669
+ };
4670
+ let jsonEmitted = false;
4671
+ const emitJson = () => {
4672
+ if (!jsonMode || jsonEmitted) return;
4673
+ jsonEmitted = true;
4674
+ restoreStdout();
4675
+ console.log(JSON.stringify(jsonResults, null, 2));
4676
+ };
4677
+ const emitJsonAndExit = (code, errorMessage) => {
4678
+ if (jsonMode) {
4679
+ if (errorMessage !== void 0) console.error(errorMessage);
4680
+ if (code !== 0 && jsonResults.length === 0) jsonResults.push({
4681
+ status: "failed",
4682
+ error: errorMessage ?? "Installation failed"
4683
+ });
4684
+ emitJson();
4685
+ }
4686
+ process.exit(code);
4687
+ };
4688
+ const emitJsonOnExit = () => emitJson();
4689
+ if (jsonMode) process.once("exit", emitJsonOnExit);
4581
4690
  const showInstallTip = () => {
4582
4691
  if (installTipShown) return;
4583
4692
  log.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
@@ -4593,8 +4702,9 @@ async function runAdd(args, options = {}) {
4593
4702
  console.log(import_picocolors.default.dim(" Example:"));
4594
4703
  console.log(` ${import_picocolors.default.cyan("npx skills add")} ${import_picocolors.default.yellow("vercel-labs/agent-skills")}`);
4595
4704
  console.log();
4596
- process.exit(1);
4705
+ emitJsonAndExit(1, "Missing required argument: source");
4597
4706
  }
4707
+ const explicitlySelectedAgents = new Set(options.agent?.includes("*") ? [] : options.agent ?? []);
4598
4708
  if (options.all) {
4599
4709
  options.skill = ["*"];
4600
4710
  options.agent = ["*"];
@@ -4608,13 +4718,19 @@ async function runAdd(args, options = {}) {
4608
4718
  if (mappedAgent) options.agent = ensureUniversalAgents([mappedAgent]);
4609
4719
  }
4610
4720
  }
4721
+ if (jsonMode && !options.yes) emitJsonAndExit(1, "The --json flag requires --yes (or --all) to run non-interactively.");
4722
+ if (jsonMode && options.list) emitJsonAndExit(1, "The --json flag cannot be combined with --list.");
4611
4723
  console.log();
4612
4724
  if (!agentResult.isAgent) intro(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills ")));
4613
4725
  if (agentResult.isAgent) log.info(import_picocolors.default.bgCyan(import_picocolors.default.black(import_picocolors.default.bold(` ${agentResult.agent.name} `))) + " Agent detected — installing non-interactively");
4614
4726
  else if (!process.stdin.isTTY) showInstallTip();
4615
4727
  let tempDir = null;
4616
4728
  try {
4617
- const spinner$3 = spinner();
4729
+ const spinner$3 = jsonMode ? {
4730
+ start: () => {},
4731
+ stop: () => {},
4732
+ message: () => {}
4733
+ } : spinner();
4618
4734
  spinner$3.start("Parsing source…");
4619
4735
  const parsed = parseSource(source);
4620
4736
  let directDownload = parsed.type === "download";
@@ -4628,6 +4744,7 @@ async function runAdd(args, options = {}) {
4628
4744
  return isRepoPrivate(ownerRepo.owner, ownerRepo.repo).catch(() => null);
4629
4745
  })();
4630
4746
  if (parsed.type === "well-known") {
4747
+ if (jsonMode) emitJsonAndExit(1, "--json is not yet supported for well-known skill sources.");
4631
4748
  if (await handleWellKnownSkills(source, parsed.url, options, spinner$3)) return;
4632
4749
  directDownload = true;
4633
4750
  }
@@ -4643,7 +4760,7 @@ async function runAdd(args, options = {}) {
4643
4760
  if (!existsSync(parsed.localPath)) {
4644
4761
  spinner$3.stop(import_picocolors.default.red("Path not found"));
4645
4762
  outro(import_picocolors.default.red(`Local path does not exist: ${parsed.localPath}`));
4646
- process.exit(1);
4763
+ emitJsonAndExit(1, `Local path does not exist: ${parsed.localPath}`);
4647
4764
  }
4648
4765
  spinner$3.stop("Local path validated");
4649
4766
  spinner$3.start("Discovering skills…");
@@ -4662,15 +4779,18 @@ async function runAdd(args, options = {}) {
4662
4779
  fullDepth: options.fullDepth
4663
4780
  });
4664
4781
  } else if (parsed.type === "github" && !options.fullDepth) {
4782
+ let attemptedBlobInstall = false;
4665
4783
  const BLOB_ALLOWED_OWNERS = [
4666
4784
  "vercel",
4667
4785
  "vercel-labs",
4668
- "heygen-com"
4786
+ "heygen-com",
4787
+ "remotion-dev"
4669
4788
  ];
4670
4789
  const ownerRepo = getOwnerRepo(parsed);
4671
4790
  const owner = ownerRepo?.split("/")[0]?.toLowerCase();
4672
4791
  const isSelfHostedRepo = !!ownerRepo && Object.hasOwn(BLOB_ALLOWED_REPOS, ownerRepo.toLowerCase());
4673
4792
  if (ownerRepo && owner && (isSelfHostedRepo || BLOB_ALLOWED_OWNERS.includes(owner))) {
4793
+ attemptedBlobInstall = true;
4674
4794
  spinner$3.start("Fetching skills…");
4675
4795
  blobResult = await tryBlobInstall(ownerRepo, {
4676
4796
  subpath: parsed.subpath,
@@ -4679,13 +4799,13 @@ async function runAdd(args, options = {}) {
4679
4799
  getToken: getGitHubToken,
4680
4800
  includeInternal
4681
4801
  });
4682
- if (!blobResult) spinner$3.stop(import_picocolors.default.dim("Falling back to clone…"));
4683
4802
  }
4684
4803
  if (blobResult) {
4685
4804
  skills = blobResult.skills;
4686
4805
  spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
4687
4806
  } else {
4688
- spinner$3.start("Cloning repository…");
4807
+ if (attemptedBlobInstall) spinner$3.message("Cloning repository…");
4808
+ else spinner$3.start("Cloning repository…");
4689
4809
  tempDir = await cloneRepo(parsed.url, parsed.ref);
4690
4810
  spinner$3.stop("Repository cloned");
4691
4811
  spinner$3.start("Discovering skills…");
@@ -4708,7 +4828,7 @@ async function runAdd(args, options = {}) {
4708
4828
  spinner$3.stop(import_picocolors.default.red("No skills found"));
4709
4829
  outro(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
4710
4830
  await cleanup(tempDir);
4711
- process.exit(1);
4831
+ emitJsonAndExit(1, "No valid skills found. Skills require a SKILL.md with name and description.");
4712
4832
  }
4713
4833
  if (!blobResult) spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
4714
4834
  if (options.list) {
@@ -4741,7 +4861,7 @@ async function runAdd(args, options = {}) {
4741
4861
  console.log();
4742
4862
  outro("Use --skill <name> to install specific skills");
4743
4863
  await cleanup(tempDir);
4744
- process.exit(0);
4864
+ emitJsonAndExit(0);
4745
4865
  }
4746
4866
  let selectedSkills;
4747
4867
  if (options.skill?.includes("*")) {
@@ -4749,12 +4869,19 @@ async function runAdd(args, options = {}) {
4749
4869
  log.info(`Installing all ${skills.length} skills`);
4750
4870
  } else if (options.skill && options.skill.length > 0) {
4751
4871
  selectedSkills = filterSkills(skills, options.skill);
4872
+ if (jsonMode) {
4873
+ for (const requested of options.skill) if (filterSkills(skills, [requested]).length === 0) jsonResults.push({
4874
+ name: requested,
4875
+ status: "skipped",
4876
+ reason: "No matching skill found in source"
4877
+ });
4878
+ }
4752
4879
  if (selectedSkills.length === 0) {
4753
4880
  log.error(`No matching skills found for: ${options.skill.join(", ")}`);
4754
4881
  log.info("Available skills:");
4755
4882
  for (const s of skills) log.message(` - ${getSkillDisplayName(s)}`);
4756
4883
  await cleanup(tempDir);
4757
- process.exit(1);
4884
+ emitJsonAndExit(1, `No matching skills found for: ${options.skill.join(", ")}`);
4758
4885
  }
4759
4886
  log.info(`Selected ${selectedSkills.length} skill${selectedSkills.length !== 1 ? "s" : ""}: ${selectedSkills.map((s) => import_picocolors.default.cyan(getSkillDisplayName(s))).join(", ")}`);
4760
4887
  } else if (skills.length === 1) {
@@ -4792,9 +4919,8 @@ async function runAdd(args, options = {}) {
4792
4919
  selectAll: true
4793
4920
  });
4794
4921
  if (isCancelled$1(selected)) {
4795
- cancel("Installation cancelled");
4796
4922
  await cleanup(tempDir);
4797
- process.exit(0);
4923
+ exitInstallationCancelled();
4798
4924
  }
4799
4925
  selectedSkills = selected;
4800
4926
  }
@@ -4811,7 +4937,7 @@ async function runAdd(args, options = {}) {
4811
4937
  log.error(`Invalid agents: ${invalidAgents.join(", ")}`);
4812
4938
  log.info(`Valid agents: ${validAgents.join(", ")}`);
4813
4939
  await cleanup(tempDir);
4814
- process.exit(1);
4940
+ emitJsonAndExit(1, `Invalid agents: ${invalidAgents.join(", ")}`);
4815
4941
  }
4816
4942
  targetAgents = options.agent;
4817
4943
  } else {
@@ -4825,21 +4951,21 @@ async function runAdd(args, options = {}) {
4825
4951
  initialValue: true
4826
4952
  });
4827
4953
  if (isCancel(useEve)) {
4828
- cancel("Installation cancelled");
4829
4954
  await cleanup(tempDir);
4830
- process.exit(0);
4955
+ exitInstallationCancelled();
4831
4956
  }
4832
4957
  if (useEve) {
4833
4958
  targetAgents = ["eve"];
4959
+ if (!options.yes) explicitlySelectedAgents.add("eve");
4834
4960
  log.info(`Installing to: ${import_picocolors.default.cyan(EVE_AGENT_LABEL)}`);
4835
4961
  } else {
4836
4962
  const selected = await selectAgentsInteractive({ global: options.global });
4837
- if (isCancel(selected)) {
4838
- cancel("Installation cancelled");
4963
+ if (isCancelled$1(selected)) {
4839
4964
  await cleanup(tempDir);
4840
- process.exit(0);
4965
+ exitInstallationCancelled();
4841
4966
  }
4842
4967
  targetAgents = selected;
4968
+ for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
4843
4969
  }
4844
4970
  } else if (installedAgents.length === 0) if (options.yes) {
4845
4971
  targetAgents = validAgents;
@@ -4850,12 +4976,12 @@ async function runAdd(args, options = {}) {
4850
4976
  value: key,
4851
4977
  label: config.displayName
4852
4978
  })));
4853
- if (isCancel(selected)) {
4854
- cancel("Installation cancelled");
4979
+ if (isCancelled$1(selected)) {
4855
4980
  await cleanup(tempDir);
4856
- process.exit(0);
4981
+ exitInstallationCancelled();
4857
4982
  }
4858
4983
  targetAgents = selected;
4984
+ for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
4859
4985
  }
4860
4986
  else if (installedAgents.length === 1 || options.yes) {
4861
4987
  targetAgents = ensureUniversalAgents(installedAgents);
@@ -4865,15 +4991,18 @@ async function runAdd(args, options = {}) {
4865
4991
  } else log.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
4866
4992
  } else {
4867
4993
  const selected = await selectAgentsInteractive({ global: options.global });
4868
- if (isCancel(selected)) {
4869
- cancel("Installation cancelled");
4994
+ if (isCancelled$1(selected)) {
4870
4995
  await cleanup(tempDir);
4871
- process.exit(0);
4996
+ exitInstallationCancelled();
4872
4997
  }
4873
4998
  targetAgents = selected;
4999
+ for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
4874
5000
  }
4875
5001
  }
4876
- if (options.subagent && options.subagent.length > 0 && !targetAgents.includes("eve")) targetAgents = [...targetAgents, "eve"];
5002
+ if (options.subagent && options.subagent.length > 0) {
5003
+ explicitlySelectedAgents.add("eve");
5004
+ if (!targetAgents.includes("eve")) targetAgents = [...targetAgents, "eve"];
5005
+ }
4877
5006
  let eveSubagentTargets = [void 0];
4878
5007
  if (targetAgents.includes("eve")) {
4879
5008
  const availableSubagents = getEveSubagents(process.cwd());
@@ -4894,9 +5023,8 @@ async function runAdd(args, options = {}) {
4894
5023
  required: true
4895
5024
  });
4896
5025
  if (isCancel(selectedSubagents)) {
4897
- cancel("Installation cancelled");
4898
5026
  await cleanup(tempDir);
4899
- process.exit(0);
5027
+ exitInstallationCancelled();
4900
5028
  }
4901
5029
  eveSubagentTargets = selectedSubagents.map((s) => s === "" ? void 0 : s);
4902
5030
  }
@@ -4918,9 +5046,8 @@ async function runAdd(args, options = {}) {
4918
5046
  }]
4919
5047
  });
4920
5048
  if (isCancel(scope)) {
4921
- cancel("Installation cancelled");
4922
5049
  await cleanup(tempDir);
4923
- process.exit(0);
5050
+ exitInstallationCancelled();
4924
5051
  }
4925
5052
  installGlobally = scope;
4926
5053
  }
@@ -4941,9 +5068,8 @@ async function runAdd(args, options = {}) {
4941
5068
  }]
4942
5069
  });
4943
5070
  if (isCancel(modeChoice)) {
4944
- cancel("Installation cancelled");
4945
5071
  await cleanup(tempDir);
4946
- process.exit(0);
5072
+ exitInstallationCancelled();
4947
5073
  }
4948
5074
  installMode = modeChoice;
4949
5075
  } else if (uniqueDirs.size <= 1 || allEve) installMode = "copy";
@@ -5000,8 +5126,10 @@ async function runAdd(args, options = {}) {
5000
5126
  }
5001
5127
  console.log();
5002
5128
  note(summaryLines.join("\n"), "Installation Summary");
5129
+ let auditDataForJson = null;
5003
5130
  try {
5004
5131
  const auditData = await auditPromise;
5132
+ auditDataForJson = auditData;
5005
5133
  if (auditData && ownerRepoForAudit) {
5006
5134
  const securityLines = buildSecurityLines(auditData, selectedSkills.map((s) => ({
5007
5135
  slug: getSkillDisplayName(s),
@@ -5013,9 +5141,8 @@ async function runAdd(args, options = {}) {
5013
5141
  if (!options.yes) {
5014
5142
  const confirmed = await confirm({ message: "Proceed with installation?" });
5015
5143
  if (isCancel(confirmed) || !confirmed) {
5016
- cancel("Installation cancelled");
5017
5144
  await cleanup(tempDir);
5018
- process.exit(0);
5145
+ exitInstallationCancelled();
5019
5146
  }
5020
5147
  }
5021
5148
  spinner$3.start("Installing skills…");
@@ -5031,12 +5158,14 @@ async function runAdd(args, options = {}) {
5031
5158
  }, agent, {
5032
5159
  global: installGlobally,
5033
5160
  mode: installMode,
5034
- eveSubagent: subagent
5161
+ eveSubagent: subagent,
5162
+ createMissingAgentRoot: explicitlySelectedAgents.has(agent)
5035
5163
  });
5036
5164
  } else result = await installSkillForAgent(skill, agent, {
5037
5165
  global: installGlobally,
5038
5166
  mode: installMode,
5039
- eveSubagent: subagent
5167
+ eveSubagent: subagent,
5168
+ createMissingAgentRoot: explicitlySelectedAgents.has(agent)
5040
5169
  });
5041
5170
  results.push({
5042
5171
  skill: getSkillDisplayName(skill),
@@ -5049,6 +5178,7 @@ async function runAdd(args, options = {}) {
5049
5178
  console.log();
5050
5179
  const successful = results.filter((r) => r.success);
5051
5180
  const failed = results.filter((r) => !r.success);
5181
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
5052
5182
  const skillFiles = {};
5053
5183
  for (const skill of selectedSkills) if (blobResult && "repoPath" in skill) skillFiles[skill.name] = skill.repoPath;
5054
5184
  else if (tempDir && skill.path === tempDir) skillFiles[skill.name] = "SKILL.md";
@@ -5076,8 +5206,16 @@ async function runAdd(args, options = {}) {
5076
5206
  skillFiles: JSON.stringify(skillFiles),
5077
5207
  metadata: options.metadata
5078
5208
  });
5209
+ const installedSkillHashes = /* @__PURE__ */ new Map();
5210
+ if (successful.length > 0 && (jsonMode || !installGlobally)) for (const skill of selectedSkills) {
5211
+ const skillDisplayName = getSkillDisplayName(skill);
5212
+ if (!successfulSkillNames.has(skillDisplayName)) continue;
5213
+ try {
5214
+ const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
5215
+ installedSkillHashes.set(skillDisplayName, computedHash);
5216
+ } catch {}
5217
+ }
5079
5218
  if (successful.length > 0 && installGlobally && normalizedSource) {
5080
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
5081
5219
  let cachedTree;
5082
5220
  if (parsed.type === "github" && !blobResult) cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, getGitHubToken);
5083
5221
  for (const skill of selectedSkills) {
@@ -5108,13 +5246,13 @@ async function runAdd(args, options = {}) {
5108
5246
  }
5109
5247
  }
5110
5248
  if (successful.length > 0 && !installGlobally && !directDownload) {
5111
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
5112
5249
  const eveSubagents = targetAgents.includes("eve") ? eveSubagentTargets.map((s) => s ?? "") : void 0;
5113
5250
  const recordSubagents = eveSubagents && (eveSubagents.length > 1 || eveSubagents.some((s) => s !== ""));
5114
5251
  for (const skill of selectedSkills) {
5115
5252
  const skillDisplayName = getSkillDisplayName(skill);
5116
5253
  if (successfulSkillNames.has(skillDisplayName)) try {
5117
- const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
5254
+ const computedHash = installedSkillHashes.get(skillDisplayName);
5255
+ if (computedHash === void 0) continue;
5118
5256
  const skillPathValue = skillFiles[skill.name];
5119
5257
  await addSkillToLocalLock(skill.name, {
5120
5258
  source: lockSource || parsed.url,
@@ -5128,6 +5266,37 @@ async function runAdd(args, options = {}) {
5128
5266
  } catch {}
5129
5267
  }
5130
5268
  }
5269
+ if (jsonMode) {
5270
+ const jsonSource = normalizedSource ?? (parsed.type === "local" ? parsed.localPath : parsed.url);
5271
+ for (const skill of selectedSkills) {
5272
+ const name = getSkillDisplayName(skill);
5273
+ const skillResults = results.filter((r) => r.skill === name);
5274
+ const failures = skillResults.filter((r) => !r.success);
5275
+ if (failures.length > 0) {
5276
+ jsonResults.push({
5277
+ name,
5278
+ status: "failed",
5279
+ error: failures[0].error ?? "Installation failed"
5280
+ });
5281
+ continue;
5282
+ }
5283
+ jsonResults.push({
5284
+ name,
5285
+ status: "installed",
5286
+ source: jsonSource,
5287
+ ref: parsed.ref ?? null,
5288
+ hash: installedSkillHashes.get(name) ?? null,
5289
+ path: skillResults[0]?.canonicalPath ?? skillResults[0]?.path,
5290
+ scope: installGlobally ? "global" : "project",
5291
+ agents: skillResults.filter((r) => !r.skipped).map((r) => r.agent),
5292
+ mode: skillResults[0]?.mode ?? installMode,
5293
+ security: buildJsonSecurity(auditDataForJson, name, ownerRepoForAudit)
5294
+ });
5295
+ }
5296
+ emitJson();
5297
+ if (failed.length > 0 || jsonResults.some((result) => result.status === "skipped")) process.exitCode = 1;
5298
+ return;
5299
+ }
5131
5300
  if (successful.length > 0) {
5132
5301
  const bySkill = /* @__PURE__ */ new Map();
5133
5302
  const groupedResults = {};
@@ -5205,8 +5374,10 @@ async function runAdd(args, options = {}) {
5205
5374
  } else log.error(error instanceof Error ? error.message : "Unknown error occurred");
5206
5375
  showInstallTip();
5207
5376
  outro(import_picocolors.default.red("Installation failed"));
5208
- process.exit(1);
5377
+ emitJsonAndExit(1, error instanceof GitCloneError ? `Failed to clone repository\n${error.message}` : error instanceof Error ? error.message : "Unknown error occurred");
5209
5378
  } finally {
5379
+ if (jsonMode) process.removeListener("exit", emitJsonOnExit);
5380
+ restoreStdout();
5210
5381
  await cleanup(tempDir);
5211
5382
  }
5212
5383
  }
@@ -5294,6 +5465,7 @@ function parseAddOptions(args) {
5294
5465
  errors.push("--metadata must be valid JSON");
5295
5466
  }
5296
5467
  } else if (arg === "--full-depth") options.fullDepth = true;
5468
+ else if (arg === "--json") options.json = true;
5297
5469
  else if (arg === "--copy") options.copy = true;
5298
5470
  else if (arg === "--subagent") {
5299
5471
  options.subagent = options.subagent || [];
@@ -6192,7 +6364,11 @@ async function removeCommand(skillNames, options) {
6192
6364
  const scanDir = async (dir) => {
6193
6365
  try {
6194
6366
  const entries = await readdir(dir, { withFileTypes: true });
6195
- for (const entry of entries) if (entry.isDirectory()) skillNamesSet.add(entry.name);
6367
+ for (const entry of entries) {
6368
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
6369
+ if (!await hasSkillMd(join(dir, entry.name))) continue;
6370
+ skillNamesSet.add(entry.name);
6371
+ }
6196
6372
  } catch (err) {
6197
6373
  if (err instanceof Error && err.code !== "ENOENT") log.warn(`Could not scan directory ${dir}: ${err.message}`);
6198
6374
  }
@@ -7124,7 +7300,8 @@ async function runUpdate(args = []) {
7124
7300
  const BLOB_ALLOWED_OWNERS = [
7125
7301
  "vercel",
7126
7302
  "vercel-labs",
7127
- "heygen-com"
7303
+ "heygen-com",
7304
+ "remotion-dev"
7128
7305
  ];
7129
7306
  const EXCLUDE_FILES = /* @__PURE__ */ new Set(["metadata.json"]);
7130
7307
  const EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -7140,6 +7317,10 @@ const USE_AGENT_CONFIGS = {
7140
7317
  codex: {
7141
7318
  command: "codex",
7142
7319
  args: []
7320
+ },
7321
+ "sarvam-code": {
7322
+ command: "sarvam-code",
7323
+ args: []
7143
7324
  }
7144
7325
  };
7145
7326
  const SUPPORTED_USE_AGENTS = Object.keys(USE_AGENT_CONFIGS);
@@ -7602,6 +7783,7 @@ ${BOLD}Add Options:${RESET}
7602
7783
  --subagent <names> Install to Eve subagents (use 'root' for the root agent)
7603
7784
  --all Shorthand for --skill '*' --agent '*' -y
7604
7785
  --full-depth Search all subdirectories even when a root SKILL.md exists
7786
+ --json Output results as JSON (machine-readable, no ANSI codes)
7605
7787
 
7606
7788
  ${BOLD}Use Options:${RESET}
7607
7789
  -s, --skill <skill> Specify the skill to use
@@ -7635,6 +7817,7 @@ ${BOLD}Examples:${RESET}
7635
7817
  ${DIM}$${RESET} skills add vercel-labs/agent-skills -g
7636
7818
  ${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
7637
7819
  ${DIM}$${RESET} skills add vercel-labs/agent-skills --skill pr-review commit
7820
+ ${DIM}$${RESET} skills add vercel-labs/agent-skills --json -y ${DIM}# JSON output${RESET}
7638
7821
  ${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
7639
7822
  ${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
7640
7823
  ${DIM}$${RESET} skills rm --global frontend-design
@@ -7769,10 +7952,11 @@ async function main() {
7769
7952
  case "install":
7770
7953
  case "a":
7771
7954
  case "add": {
7772
- if (!inAgent) showLogo();
7773
7955
  const { source: addSource, options: addOpts, errors } = parseAddOptions(restArgs);
7956
+ if (!inAgent && !addOpts.json) showLogo();
7774
7957
  if (errors.length > 0) {
7775
7958
  for (const error of errors) console.error(`Error: ${error}`);
7959
+ if (addOpts.json) console.log("[]");
7776
7960
  process.exitCode = 1;
7777
7961
  break;
7778
7962
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.5.24",
3
+ "version": "1.5.26",
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",