skills 1.5.25 → 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 (2) hide show
  1. package/dist/cli.mjs +173 -48
  2. package/package.json +1 -1
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";
@@ -1464,6 +1464,7 @@ const agents = {
1464
1464
  displayName: "Claude Code",
1465
1465
  skillsDir: ".claude/skills",
1466
1466
  globalSkillsDir: join(claudeHome, "skills"),
1467
+ createProjectSkillsDirByDefault: true,
1467
1468
  detectInstalled: async () => {
1468
1469
  return existsSync(claudeHome);
1469
1470
  }
@@ -2145,6 +2146,11 @@ function isPathSafe$2(basePath, targetPath) {
2145
2146
  function pathsOverlap(pathA, pathB) {
2146
2147
  return isPathSafe$2(pathA, pathB) || isPathSafe$2(pathB, pathA);
2147
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
+ }
2148
2154
  async function isDirEntryOrSymlinkToDir(entry, entryPath) {
2149
2155
  if (entry.isDirectory()) return true;
2150
2156
  if (!entry.isSymbolicLink()) return false;
@@ -2280,15 +2286,14 @@ async function installSkillForAgent(skill, agentType, options = {}) {
2280
2286
  canonicalPath: canonicalDir,
2281
2287
  mode: "symlink"
2282
2288
  };
2283
- if (!isGlobal && !isUniversalAgent(agentType)) {
2284
- if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0])) && agentType !== "claude-code") return {
2285
- success: true,
2286
- path: canonicalDir,
2287
- canonicalPath: canonicalDir,
2288
- mode: "symlink",
2289
- skipped: true
2290
- };
2291
- }
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
+ };
2292
2297
  if (!await createSymlink(canonicalDir, agentDir)) {
2293
2298
  await cleanAndCreateDirectory(agentDir);
2294
2299
  await copyDirectory(skill.path, agentDir, agentType);
@@ -2329,15 +2334,14 @@ const isExcluded$1 = (name, isDirectory = false) => {
2329
2334
  function stripIgnoredEveFrontmatter(raw) {
2330
2335
  const { data, content } = parseFrontmatter(raw);
2331
2336
  const eveData = {};
2337
+ if (typeof data.name === "string") eveData.name = data.name;
2332
2338
  if (typeof data.description === "string") eveData.description = data.description;
2333
2339
  if (typeof data.license === "string") eveData.license = data.license;
2334
- if (data.metadata && typeof data.metadata === "object" && !Array.isArray(data.metadata)) {
2335
- const metadata = Object.fromEntries(Object.entries(data.metadata).filter((entry) => typeof entry[1] === "string"));
2336
- if (Object.keys(metadata).length > 0) eveData.metadata = metadata;
2337
- }
2338
- const keys = Object.keys(eveData);
2339
- if (keys.length === 0) return content.replace(/^\r?\n/u, "");
2340
- 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, "")}`;
2341
2345
  }
2342
2346
  async function copyDirectory(src, dest, agentType) {
2343
2347
  await mkdir(dest, { recursive: true });
@@ -2571,15 +2575,14 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
2571
2575
  canonicalPath: canonicalDir,
2572
2576
  mode: "symlink"
2573
2577
  };
2574
- if (!isGlobal && !isUniversalAgent(agentType)) {
2575
- if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0])) && agentType !== "claude-code") return {
2576
- success: true,
2577
- path: canonicalDir,
2578
- canonicalPath: canonicalDir,
2579
- mode: "symlink",
2580
- skipped: true
2581
- };
2582
- }
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
+ };
2583
2586
  if (!await createSymlink(canonicalDir, agentDir)) {
2584
2587
  await cleanAndCreateDirectory(agentDir);
2585
2588
  await writeSkillFiles(agentDir);
@@ -4121,7 +4124,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
4121
4124
  tree
4122
4125
  };
4123
4126
  }
4124
- var version$1 = "1.5.25";
4127
+ var version$1 = "1.5.26";
4125
4128
  const isCancelled$1 = (value) => typeof value === "symbol";
4126
4129
  const EVE_AGENT_LABEL = "eve agent";
4127
4130
  async function isSourcePrivate(source) {
@@ -4269,9 +4272,11 @@ function buildResultLines(results, targetAgents) {
4269
4272
  const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
4270
4273
  const successfulSymlinks = results.filter((r) => !r.symlinkFailed && !r.skipped && !universal.includes(r.agent)).map((r) => r.agent);
4271
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);
4272
4276
  if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
4273
4277
  if (successfulSymlinks.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList$1(successfulSymlinks)}`);
4274
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)")}`);
4275
4280
  return lines;
4276
4281
  }
4277
4282
  function exitInstallationCancelled() {
@@ -4341,6 +4346,17 @@ async function selectAgentsInteractive(options) {
4341
4346
  return selected;
4342
4347
  }
4343
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
+ }
4344
4360
  function isSkillsShPackUrl(url) {
4345
4361
  try {
4346
4362
  const parsed = new URL(url);
@@ -4544,6 +4560,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4544
4560
  console.log();
4545
4561
  const successful = results.filter((r) => r.success);
4546
4562
  const failed = results.filter((r) => !r.success);
4563
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
4547
4564
  const skillFiles = {};
4548
4565
  for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
4549
4566
  if (await wellKnownPrivacyPromise !== true) track({
@@ -4558,7 +4575,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4558
4575
  sourceType: "well-known"
4559
4576
  });
4560
4577
  if (successful.length > 0 && installGlobally) {
4561
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
4562
4578
  for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
4563
4579
  await addSkillToLock(skill.installName, {
4564
4580
  source: sourceIdentifier,
@@ -4571,7 +4587,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4571
4587
  } catch {}
4572
4588
  }
4573
4589
  if (successful.length > 0 && !installGlobally) {
4574
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
4575
4590
  for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
4576
4591
  const matchingResult = successful.find((r) => r.skill === skill.installName);
4577
4592
  const installDir = matchingResult?.canonicalPath || matchingResult?.path;
@@ -4638,6 +4653,40 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4638
4653
  async function runAdd(args, options = {}) {
4639
4654
  const source = args[0];
4640
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);
4641
4690
  const showInstallTip = () => {
4642
4691
  if (installTipShown) return;
4643
4692
  log.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
@@ -4653,8 +4702,9 @@ async function runAdd(args, options = {}) {
4653
4702
  console.log(import_picocolors.default.dim(" Example:"));
4654
4703
  console.log(` ${import_picocolors.default.cyan("npx skills add")} ${import_picocolors.default.yellow("vercel-labs/agent-skills")}`);
4655
4704
  console.log();
4656
- process.exit(1);
4705
+ emitJsonAndExit(1, "Missing required argument: source");
4657
4706
  }
4707
+ const explicitlySelectedAgents = new Set(options.agent?.includes("*") ? [] : options.agent ?? []);
4658
4708
  if (options.all) {
4659
4709
  options.skill = ["*"];
4660
4710
  options.agent = ["*"];
@@ -4668,13 +4718,19 @@ async function runAdd(args, options = {}) {
4668
4718
  if (mappedAgent) options.agent = ensureUniversalAgents([mappedAgent]);
4669
4719
  }
4670
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.");
4671
4723
  console.log();
4672
4724
  if (!agentResult.isAgent) intro(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills ")));
4673
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");
4674
4726
  else if (!process.stdin.isTTY) showInstallTip();
4675
4727
  let tempDir = null;
4676
4728
  try {
4677
- const spinner$3 = spinner();
4729
+ const spinner$3 = jsonMode ? {
4730
+ start: () => {},
4731
+ stop: () => {},
4732
+ message: () => {}
4733
+ } : spinner();
4678
4734
  spinner$3.start("Parsing source…");
4679
4735
  const parsed = parseSource(source);
4680
4736
  let directDownload = parsed.type === "download";
@@ -4688,6 +4744,7 @@ async function runAdd(args, options = {}) {
4688
4744
  return isRepoPrivate(ownerRepo.owner, ownerRepo.repo).catch(() => null);
4689
4745
  })();
4690
4746
  if (parsed.type === "well-known") {
4747
+ if (jsonMode) emitJsonAndExit(1, "--json is not yet supported for well-known skill sources.");
4691
4748
  if (await handleWellKnownSkills(source, parsed.url, options, spinner$3)) return;
4692
4749
  directDownload = true;
4693
4750
  }
@@ -4703,7 +4760,7 @@ async function runAdd(args, options = {}) {
4703
4760
  if (!existsSync(parsed.localPath)) {
4704
4761
  spinner$3.stop(import_picocolors.default.red("Path not found"));
4705
4762
  outro(import_picocolors.default.red(`Local path does not exist: ${parsed.localPath}`));
4706
- process.exit(1);
4763
+ emitJsonAndExit(1, `Local path does not exist: ${parsed.localPath}`);
4707
4764
  }
4708
4765
  spinner$3.stop("Local path validated");
4709
4766
  spinner$3.start("Discovering skills…");
@@ -4722,15 +4779,18 @@ async function runAdd(args, options = {}) {
4722
4779
  fullDepth: options.fullDepth
4723
4780
  });
4724
4781
  } else if (parsed.type === "github" && !options.fullDepth) {
4782
+ let attemptedBlobInstall = false;
4725
4783
  const BLOB_ALLOWED_OWNERS = [
4726
4784
  "vercel",
4727
4785
  "vercel-labs",
4728
- "heygen-com"
4786
+ "heygen-com",
4787
+ "remotion-dev"
4729
4788
  ];
4730
4789
  const ownerRepo = getOwnerRepo(parsed);
4731
4790
  const owner = ownerRepo?.split("/")[0]?.toLowerCase();
4732
4791
  const isSelfHostedRepo = !!ownerRepo && Object.hasOwn(BLOB_ALLOWED_REPOS, ownerRepo.toLowerCase());
4733
4792
  if (ownerRepo && owner && (isSelfHostedRepo || BLOB_ALLOWED_OWNERS.includes(owner))) {
4793
+ attemptedBlobInstall = true;
4734
4794
  spinner$3.start("Fetching skills…");
4735
4795
  blobResult = await tryBlobInstall(ownerRepo, {
4736
4796
  subpath: parsed.subpath,
@@ -4739,13 +4799,13 @@ async function runAdd(args, options = {}) {
4739
4799
  getToken: getGitHubToken,
4740
4800
  includeInternal
4741
4801
  });
4742
- if (!blobResult) spinner$3.stop(import_picocolors.default.dim("Falling back to clone…"));
4743
4802
  }
4744
4803
  if (blobResult) {
4745
4804
  skills = blobResult.skills;
4746
4805
  spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
4747
4806
  } else {
4748
- spinner$3.start("Cloning repository…");
4807
+ if (attemptedBlobInstall) spinner$3.message("Cloning repository…");
4808
+ else spinner$3.start("Cloning repository…");
4749
4809
  tempDir = await cloneRepo(parsed.url, parsed.ref);
4750
4810
  spinner$3.stop("Repository cloned");
4751
4811
  spinner$3.start("Discovering skills…");
@@ -4768,7 +4828,7 @@ async function runAdd(args, options = {}) {
4768
4828
  spinner$3.stop(import_picocolors.default.red("No skills found"));
4769
4829
  outro(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
4770
4830
  await cleanup(tempDir);
4771
- process.exit(1);
4831
+ emitJsonAndExit(1, "No valid skills found. Skills require a SKILL.md with name and description.");
4772
4832
  }
4773
4833
  if (!blobResult) spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
4774
4834
  if (options.list) {
@@ -4801,7 +4861,7 @@ async function runAdd(args, options = {}) {
4801
4861
  console.log();
4802
4862
  outro("Use --skill <name> to install specific skills");
4803
4863
  await cleanup(tempDir);
4804
- process.exit(0);
4864
+ emitJsonAndExit(0);
4805
4865
  }
4806
4866
  let selectedSkills;
4807
4867
  if (options.skill?.includes("*")) {
@@ -4809,12 +4869,19 @@ async function runAdd(args, options = {}) {
4809
4869
  log.info(`Installing all ${skills.length} skills`);
4810
4870
  } else if (options.skill && options.skill.length > 0) {
4811
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
+ }
4812
4879
  if (selectedSkills.length === 0) {
4813
4880
  log.error(`No matching skills found for: ${options.skill.join(", ")}`);
4814
4881
  log.info("Available skills:");
4815
4882
  for (const s of skills) log.message(` - ${getSkillDisplayName(s)}`);
4816
4883
  await cleanup(tempDir);
4817
- process.exit(1);
4884
+ emitJsonAndExit(1, `No matching skills found for: ${options.skill.join(", ")}`);
4818
4885
  }
4819
4886
  log.info(`Selected ${selectedSkills.length} skill${selectedSkills.length !== 1 ? "s" : ""}: ${selectedSkills.map((s) => import_picocolors.default.cyan(getSkillDisplayName(s))).join(", ")}`);
4820
4887
  } else if (skills.length === 1) {
@@ -4870,7 +4937,7 @@ async function runAdd(args, options = {}) {
4870
4937
  log.error(`Invalid agents: ${invalidAgents.join(", ")}`);
4871
4938
  log.info(`Valid agents: ${validAgents.join(", ")}`);
4872
4939
  await cleanup(tempDir);
4873
- process.exit(1);
4940
+ emitJsonAndExit(1, `Invalid agents: ${invalidAgents.join(", ")}`);
4874
4941
  }
4875
4942
  targetAgents = options.agent;
4876
4943
  } else {
@@ -4889,6 +4956,7 @@ async function runAdd(args, options = {}) {
4889
4956
  }
4890
4957
  if (useEve) {
4891
4958
  targetAgents = ["eve"];
4959
+ if (!options.yes) explicitlySelectedAgents.add("eve");
4892
4960
  log.info(`Installing to: ${import_picocolors.default.cyan(EVE_AGENT_LABEL)}`);
4893
4961
  } else {
4894
4962
  const selected = await selectAgentsInteractive({ global: options.global });
@@ -4897,6 +4965,7 @@ async function runAdd(args, options = {}) {
4897
4965
  exitInstallationCancelled();
4898
4966
  }
4899
4967
  targetAgents = selected;
4968
+ for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
4900
4969
  }
4901
4970
  } else if (installedAgents.length === 0) if (options.yes) {
4902
4971
  targetAgents = validAgents;
@@ -4912,6 +4981,7 @@ async function runAdd(args, options = {}) {
4912
4981
  exitInstallationCancelled();
4913
4982
  }
4914
4983
  targetAgents = selected;
4984
+ for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
4915
4985
  }
4916
4986
  else if (installedAgents.length === 1 || options.yes) {
4917
4987
  targetAgents = ensureUniversalAgents(installedAgents);
@@ -4926,9 +4996,13 @@ async function runAdd(args, options = {}) {
4926
4996
  exitInstallationCancelled();
4927
4997
  }
4928
4998
  targetAgents = selected;
4999
+ for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
4929
5000
  }
4930
5001
  }
4931
- 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
+ }
4932
5006
  let eveSubagentTargets = [void 0];
4933
5007
  if (targetAgents.includes("eve")) {
4934
5008
  const availableSubagents = getEveSubagents(process.cwd());
@@ -5052,8 +5126,10 @@ async function runAdd(args, options = {}) {
5052
5126
  }
5053
5127
  console.log();
5054
5128
  note(summaryLines.join("\n"), "Installation Summary");
5129
+ let auditDataForJson = null;
5055
5130
  try {
5056
5131
  const auditData = await auditPromise;
5132
+ auditDataForJson = auditData;
5057
5133
  if (auditData && ownerRepoForAudit) {
5058
5134
  const securityLines = buildSecurityLines(auditData, selectedSkills.map((s) => ({
5059
5135
  slug: getSkillDisplayName(s),
@@ -5082,12 +5158,14 @@ async function runAdd(args, options = {}) {
5082
5158
  }, agent, {
5083
5159
  global: installGlobally,
5084
5160
  mode: installMode,
5085
- eveSubagent: subagent
5161
+ eveSubagent: subagent,
5162
+ createMissingAgentRoot: explicitlySelectedAgents.has(agent)
5086
5163
  });
5087
5164
  } else result = await installSkillForAgent(skill, agent, {
5088
5165
  global: installGlobally,
5089
5166
  mode: installMode,
5090
- eveSubagent: subagent
5167
+ eveSubagent: subagent,
5168
+ createMissingAgentRoot: explicitlySelectedAgents.has(agent)
5091
5169
  });
5092
5170
  results.push({
5093
5171
  skill: getSkillDisplayName(skill),
@@ -5100,6 +5178,7 @@ async function runAdd(args, options = {}) {
5100
5178
  console.log();
5101
5179
  const successful = results.filter((r) => r.success);
5102
5180
  const failed = results.filter((r) => !r.success);
5181
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
5103
5182
  const skillFiles = {};
5104
5183
  for (const skill of selectedSkills) if (blobResult && "repoPath" in skill) skillFiles[skill.name] = skill.repoPath;
5105
5184
  else if (tempDir && skill.path === tempDir) skillFiles[skill.name] = "SKILL.md";
@@ -5127,8 +5206,16 @@ async function runAdd(args, options = {}) {
5127
5206
  skillFiles: JSON.stringify(skillFiles),
5128
5207
  metadata: options.metadata
5129
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
+ }
5130
5218
  if (successful.length > 0 && installGlobally && normalizedSource) {
5131
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
5132
5219
  let cachedTree;
5133
5220
  if (parsed.type === "github" && !blobResult) cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, getGitHubToken);
5134
5221
  for (const skill of selectedSkills) {
@@ -5159,13 +5246,13 @@ async function runAdd(args, options = {}) {
5159
5246
  }
5160
5247
  }
5161
5248
  if (successful.length > 0 && !installGlobally && !directDownload) {
5162
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
5163
5249
  const eveSubagents = targetAgents.includes("eve") ? eveSubagentTargets.map((s) => s ?? "") : void 0;
5164
5250
  const recordSubagents = eveSubagents && (eveSubagents.length > 1 || eveSubagents.some((s) => s !== ""));
5165
5251
  for (const skill of selectedSkills) {
5166
5252
  const skillDisplayName = getSkillDisplayName(skill);
5167
5253
  if (successfulSkillNames.has(skillDisplayName)) try {
5168
- const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
5254
+ const computedHash = installedSkillHashes.get(skillDisplayName);
5255
+ if (computedHash === void 0) continue;
5169
5256
  const skillPathValue = skillFiles[skill.name];
5170
5257
  await addSkillToLocalLock(skill.name, {
5171
5258
  source: lockSource || parsed.url,
@@ -5179,6 +5266,37 @@ async function runAdd(args, options = {}) {
5179
5266
  } catch {}
5180
5267
  }
5181
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
+ }
5182
5300
  if (successful.length > 0) {
5183
5301
  const bySkill = /* @__PURE__ */ new Map();
5184
5302
  const groupedResults = {};
@@ -5256,8 +5374,10 @@ async function runAdd(args, options = {}) {
5256
5374
  } else log.error(error instanceof Error ? error.message : "Unknown error occurred");
5257
5375
  showInstallTip();
5258
5376
  outro(import_picocolors.default.red("Installation failed"));
5259
- process.exit(1);
5377
+ emitJsonAndExit(1, error instanceof GitCloneError ? `Failed to clone repository\n${error.message}` : error instanceof Error ? error.message : "Unknown error occurred");
5260
5378
  } finally {
5379
+ if (jsonMode) process.removeListener("exit", emitJsonOnExit);
5380
+ restoreStdout();
5261
5381
  await cleanup(tempDir);
5262
5382
  }
5263
5383
  }
@@ -5345,6 +5465,7 @@ function parseAddOptions(args) {
5345
5465
  errors.push("--metadata must be valid JSON");
5346
5466
  }
5347
5467
  } else if (arg === "--full-depth") options.fullDepth = true;
5468
+ else if (arg === "--json") options.json = true;
5348
5469
  else if (arg === "--copy") options.copy = true;
5349
5470
  else if (arg === "--subagent") {
5350
5471
  options.subagent = options.subagent || [];
@@ -7179,7 +7300,8 @@ async function runUpdate(args = []) {
7179
7300
  const BLOB_ALLOWED_OWNERS = [
7180
7301
  "vercel",
7181
7302
  "vercel-labs",
7182
- "heygen-com"
7303
+ "heygen-com",
7304
+ "remotion-dev"
7183
7305
  ];
7184
7306
  const EXCLUDE_FILES = /* @__PURE__ */ new Set(["metadata.json"]);
7185
7307
  const EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -7661,6 +7783,7 @@ ${BOLD}Add Options:${RESET}
7661
7783
  --subagent <names> Install to Eve subagents (use 'root' for the root agent)
7662
7784
  --all Shorthand for --skill '*' --agent '*' -y
7663
7785
  --full-depth Search all subdirectories even when a root SKILL.md exists
7786
+ --json Output results as JSON (machine-readable, no ANSI codes)
7664
7787
 
7665
7788
  ${BOLD}Use Options:${RESET}
7666
7789
  -s, --skill <skill> Specify the skill to use
@@ -7694,6 +7817,7 @@ ${BOLD}Examples:${RESET}
7694
7817
  ${DIM}$${RESET} skills add vercel-labs/agent-skills -g
7695
7818
  ${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
7696
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}
7697
7821
  ${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
7698
7822
  ${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
7699
7823
  ${DIM}$${RESET} skills rm --global frontend-design
@@ -7828,10 +7952,11 @@ async function main() {
7828
7952
  case "install":
7829
7953
  case "a":
7830
7954
  case "add": {
7831
- if (!inAgent) showLogo();
7832
7955
  const { source: addSource, options: addOpts, errors } = parseAddOptions(restArgs);
7956
+ if (!inAgent && !addOpts.json) showLogo();
7833
7957
  if (errors.length > 0) {
7834
7958
  for (const error of errors) console.error(`Error: ${error}`);
7959
+ if (addOpts.json) console.log("[]");
7835
7960
  process.exitCode = 1;
7836
7961
  break;
7837
7962
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.5.25",
3
+ "version": "1.5.26",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {