skills 1.3.4 → 1.3.5

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 +5 -9
  2. package/dist/cli.mjs +175 -104
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -215,14 +215,14 @@ Skills can be installed to any of these agents:
215
215
  | OpenClaw | `openclaw` | `skills/` | `~/.moltbot/skills/` |
216
216
  | Cline | `cline` | `.cline/skills/` | `~/.cline/skills/` |
217
217
  | CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` |
218
- | Codex | `codex` | `.codex/skills/` | `~/.codex/skills/` |
218
+ | Codex | `codex` | `.agents/skills/` | `~/.codex/skills/` |
219
219
  | Command Code | `command-code` | `.commandcode/skills/` | `~/.commandcode/skills/` |
220
220
  | Continue | `continue` | `.continue/skills/` | `~/.continue/skills/` |
221
221
  | Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` |
222
222
  | Cursor | `cursor` | `.cursor/skills/` | `~/.cursor/skills/` |
223
223
  | Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
224
- | Gemini CLI | `gemini-cli` | `.gemini/skills/` | `~/.gemini/skills/` |
225
- | GitHub Copilot | `github-copilot` | `.github/skills/` | `~/.copilot/skills/` |
224
+ | Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
225
+ | GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
226
226
  | Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
227
227
  | Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
228
228
  | iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
@@ -232,12 +232,12 @@ Skills can be installed to any of these agents:
232
232
  | MCPJam | `mcpjam` | `.mcpjam/skills/` | `~/.mcpjam/skills/` |
233
233
  | Mistral Vibe | `mistral-vibe` | `.vibe/skills/` | `~/.vibe/skills/` |
234
234
  | Mux | `mux` | `.mux/skills/` | `~/.mux/skills/` |
235
- | OpenCode | `opencode` | `.opencode/skills/` | `~/.config/opencode/skills/` |
235
+ | OpenCode | `opencode` | `.agents/skills/` | `~/.config/opencode/skills/` |
236
236
  | OpenHands | `openhands` | `.openhands/skills/` | `~/.openhands/skills/` |
237
237
  | Pi | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` |
238
238
  | Qoder | `qoder` | `.qoder/skills/` | `~/.qoder/skills/` |
239
239
  | Qwen Code | `qwen-code` | `.qwen/skills/` | `~/.qwen/skills/` |
240
- | Replit | `replit` | `.agent/skills/` | N/A (project-only) |
240
+ | Replit | `replit` | `.agents/skills/` | N/A (project-only) |
241
241
  | Roo Code | `roo` | `.roo/skills/` | `~/.roo/skills/` |
242
242
  | Trae | `trae` | `.trae/skills/` | `~/.trae/skills/` |
243
243
  | Trae CN | `trae-cn` | `.trae/skills/` | `~/.trae-cn/skills/` |
@@ -322,14 +322,11 @@ The CLI searches for skills in these locations within a repository:
322
322
  - `./skills/`
323
323
  - `.cline/skills/`
324
324
  - `.codebuddy/skills/`
325
- - `.codex/skills/`
326
325
  - `.commandcode/skills/`
327
326
  - `.continue/skills/`
328
327
  - `.crush/skills/`
329
328
  - `.cursor/skills/`
330
329
  - `.factory/skills/`
331
- - `.gemini/skills/`
332
- - `.github/skills/`
333
330
  - `.goose/skills/`
334
331
  - `.junie/skills/`
335
332
  - `.iflow/skills/`
@@ -339,7 +336,6 @@ The CLI searches for skills in these locations within a repository:
339
336
  - `.mcpjam/skills/`
340
337
  - `.vibe/skills/`
341
338
  - `.mux/skills/`
342
- - `.opencode/skills/`
343
339
  - `.openhands/skills/`
344
340
  - `.pi/skills/`
345
341
  - `.qoder/skills/`
package/dist/cli.mjs CHANGED
@@ -173,10 +173,12 @@ const S_STEP_CANCEL = import_picocolors.default.red("■");
173
173
  const S_STEP_SUBMIT = import_picocolors.default.green("◇");
174
174
  const S_RADIO_ACTIVE = import_picocolors.default.green("●");
175
175
  const S_RADIO_INACTIVE = import_picocolors.default.dim("○");
176
+ const S_CHECKBOX_LOCKED = import_picocolors.default.green("✓");
176
177
  const S_BAR = import_picocolors.default.dim("│");
178
+ const S_BAR_H = import_picocolors.default.dim("─");
177
179
  const cancelSymbol = Symbol("cancel");
178
180
  async function searchMultiselect(options) {
179
- const { message, items, maxVisible = 8, initialSelected = [], required = false } = options;
181
+ const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection } = options;
180
182
  return new Promise((resolve) => {
181
183
  const rl = readline.createInterface({
182
184
  input: process.stdin,
@@ -189,6 +191,7 @@ async function searchMultiselect(options) {
189
191
  let cursor = 0;
190
192
  const selected = new Set(initialSelected);
191
193
  let lastRenderHeight = 0;
194
+ const lockedValues = lockedSection ? lockedSection.items.map((i) => i.value) : [];
192
195
  const filter = (item, q) => {
193
196
  if (!q) return true;
194
197
  const lowerQ = q.toLowerCase();
@@ -211,6 +214,13 @@ async function searchMultiselect(options) {
211
214
  const icon = state === "active" ? S_STEP_ACTIVE : state === "cancel" ? S_STEP_CANCEL : S_STEP_SUBMIT;
212
215
  lines.push(`${icon} ${import_picocolors.default.bold(message)}`);
213
216
  if (state === "active") {
217
+ if (lockedSection && lockedSection.items.length > 0) {
218
+ lines.push(`${S_BAR}`);
219
+ lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold(lockedSection.title)} ${S_BAR_H.repeat(30)}`);
220
+ for (const item of lockedSection.items) lines.push(`${S_BAR} ${S_CHECKBOX_LOCKED} ${item.label}`);
221
+ lines.push(`${S_BAR}`);
222
+ lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Other agents")} ${S_BAR_H.repeat(34)}`);
223
+ }
214
224
  const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
215
225
  lines.push(searchLine);
216
226
  lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, space select, enter confirm")}`);
@@ -241,16 +251,16 @@ async function searchMultiselect(options) {
241
251
  }
242
252
  }
243
253
  lines.push(`${S_BAR}`);
244
- if (selected.size === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
254
+ const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
255
+ if (allSelectedLabels.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
245
256
  else {
246
- const selectedLabels = items.filter((item) => selected.has(item.value)).map((item) => item.label);
247
- const summary = selectedLabels.length <= 3 ? selectedLabels.join(", ") : `${selectedLabels.slice(0, 3).join(", ")} +${selectedLabels.length - 3} more`;
257
+ const summary = allSelectedLabels.length <= 3 ? allSelectedLabels.join(", ") : `${allSelectedLabels.slice(0, 3).join(", ")} +${allSelectedLabels.length - 3} more`;
248
258
  lines.push(`${S_BAR} ${import_picocolors.default.green("Selected:")} ${summary}`);
249
259
  }
250
260
  lines.push(`${import_picocolors.default.dim("└")}`);
251
261
  } else if (state === "submit") {
252
- const selectedLabels = items.filter((item) => selected.has(item.value)).map((item) => item.label);
253
- lines.push(`${S_BAR} ${import_picocolors.default.dim(selectedLabels.join(", "))}`);
262
+ const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
263
+ lines.push(`${S_BAR} ${import_picocolors.default.dim(allSelectedLabels.join(", "))}`);
254
264
  } else if (state === "cancel") lines.push(`${S_BAR} ${import_picocolors.default.strikethrough(import_picocolors.default.dim("Cancelled"))}`);
255
265
  process.stdout.write(lines.join("\n") + "\n");
256
266
  lastRenderHeight = lines.length;
@@ -261,10 +271,10 @@ async function searchMultiselect(options) {
261
271
  rl.close();
262
272
  };
263
273
  const submit = () => {
264
- if (required && selected.size === 0) return;
274
+ if (required && selected.size === 0 && lockedValues.length === 0) return;
265
275
  render("submit");
266
276
  cleanup();
267
- resolve(Array.from(selected));
277
+ resolve([...lockedValues, ...Array.from(selected)]);
268
278
  };
269
279
  const cancel = () => {
270
280
  render("cancel");
@@ -597,7 +607,7 @@ const agents = {
597
607
  codex: {
598
608
  name: "codex",
599
609
  displayName: "Codex",
600
- skillsDir: ".codex/skills",
610
+ skillsDir: ".agents/skills",
601
611
  globalSkillsDir: join(codexHome, "skills"),
602
612
  detectInstalled: async () => {
603
613
  return existsSync(codexHome) || existsSync("/etc/codex");
@@ -651,7 +661,7 @@ const agents = {
651
661
  "gemini-cli": {
652
662
  name: "gemini-cli",
653
663
  displayName: "Gemini CLI",
654
- skillsDir: ".gemini/skills",
664
+ skillsDir: ".agents/skills",
655
665
  globalSkillsDir: join(home, ".gemini/skills"),
656
666
  detectInstalled: async () => {
657
667
  return existsSync(join(home, ".gemini"));
@@ -660,7 +670,7 @@ const agents = {
660
670
  "github-copilot": {
661
671
  name: "github-copilot",
662
672
  displayName: "GitHub Copilot",
663
- skillsDir: ".github/skills",
673
+ skillsDir: ".agents/skills",
664
674
  globalSkillsDir: join(home, ".copilot/skills"),
665
675
  detectInstalled: async () => {
666
676
  return existsSync(join(process.cwd(), ".github")) || existsSync(join(home, ".copilot"));
@@ -759,7 +769,7 @@ const agents = {
759
769
  opencode: {
760
770
  name: "opencode",
761
771
  displayName: "OpenCode",
762
- skillsDir: ".opencode/skills",
772
+ skillsDir: ".agents/skills",
763
773
  globalSkillsDir: join(configHome, "opencode/skills"),
764
774
  detectInstalled: async () => {
765
775
  return existsSync(join(configHome, "opencode")) || existsSync(join(claudeHome, "skills"));
@@ -804,10 +814,10 @@ const agents = {
804
814
  replit: {
805
815
  name: "replit",
806
816
  displayName: "Replit",
807
- skillsDir: ".agent/skills",
817
+ skillsDir: ".agents/skills",
808
818
  globalSkillsDir: void 0,
809
819
  detectInstalled: async () => {
810
- return existsSync(join(process.cwd(), ".agent"));
820
+ return existsSync(join(process.cwd(), ".agents"));
811
821
  }
812
822
  },
813
823
  roo: {
@@ -889,6 +899,15 @@ async function detectInstalledAgents() {
889
899
  installed: await config.detectInstalled()
890
900
  })))).filter((r) => r.installed).map((r) => r.type);
891
901
  }
902
+ function getUniversalAgents() {
903
+ return Object.entries(agents).filter(([_, config]) => config.skillsDir === ".agents/skills").map(([type]) => type);
904
+ }
905
+ function getNonUniversalAgents() {
906
+ return Object.entries(agents).filter(([_, config]) => config.skillsDir !== ".agents/skills").map(([type]) => type);
907
+ }
908
+ function isUniversalAgent(type) {
909
+ return agents[type].skillsDir === ".agents/skills";
910
+ }
892
911
  const AGENTS_DIR$2 = ".agents";
893
912
  const SKILLS_SUBDIR = "skills";
894
913
  function sanitizeName(name) {
@@ -1196,22 +1215,30 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1196
1215
  }
1197
1216
  async function listInstalledSkills(options = {}) {
1198
1217
  const cwd = options.cwd || process.cwd();
1199
- const installedSkills = [];
1218
+ const skillsMap = /* @__PURE__ */ new Map();
1200
1219
  const scopes = [];
1201
1220
  const detectedAgents = await detectInstalledAgents();
1202
- if (options.global === void 0) {
1203
- scopes.push({
1204
- global: false,
1205
- path: getCanonicalSkillsDir(false, cwd)
1206
- });
1221
+ const agentFilter = options.agentFilter;
1222
+ const agentsToCheck = agentFilter ? detectedAgents.filter((a) => agentFilter.includes(a)) : detectedAgents;
1223
+ const scopeTypes = [];
1224
+ if (options.global === void 0) scopeTypes.push({ global: false }, { global: true });
1225
+ else scopeTypes.push({ global: options.global });
1226
+ for (const { global: isGlobal } of scopeTypes) {
1207
1227
  scopes.push({
1208
- global: true,
1209
- path: getCanonicalSkillsDir(true, cwd)
1228
+ global: isGlobal,
1229
+ path: getCanonicalSkillsDir(isGlobal, cwd)
1210
1230
  });
1211
- } else scopes.push({
1212
- global: options.global,
1213
- path: getCanonicalSkillsDir(options.global, cwd)
1214
- });
1231
+ for (const agentType of agentsToCheck) {
1232
+ const agent = agents[agentType];
1233
+ if (isGlobal && agent.globalSkillsDir === void 0) continue;
1234
+ const agentDir = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1235
+ if (!scopes.some((s) => s.path === agentDir && s.global === isGlobal)) scopes.push({
1236
+ global: isGlobal,
1237
+ path: agentDir,
1238
+ agentType
1239
+ });
1240
+ }
1241
+ }
1215
1242
  for (const scope of scopes) try {
1216
1243
  const entries = await readdir(scope.path, { withFileTypes: true });
1217
1244
  for (const entry of entries) {
@@ -1225,22 +1252,35 @@ async function listInstalledSkills(options = {}) {
1225
1252
  }
1226
1253
  const skill = await parseSkillMd(skillMdPath);
1227
1254
  if (!skill) continue;
1255
+ const scopeKey = scope.global ? "global" : "project";
1256
+ const skillKey = `${scopeKey}:${skill.name}`;
1257
+ if (scope.agentType) {
1258
+ if (skillsMap.has(skillKey)) {
1259
+ const existing = skillsMap.get(skillKey);
1260
+ if (!existing.agents.includes(scope.agentType)) existing.agents.push(scope.agentType);
1261
+ } else skillsMap.set(skillKey, {
1262
+ name: skill.name,
1263
+ description: skill.description,
1264
+ path: skillDir,
1265
+ canonicalPath: skillDir,
1266
+ scope: scopeKey,
1267
+ agents: [scope.agentType]
1268
+ });
1269
+ continue;
1270
+ }
1228
1271
  const sanitizedSkillName = sanitizeName(skill.name);
1229
1272
  const installedAgents = [];
1230
- const agentFilter = options.agentFilter;
1231
- const agentsToCheck = agentFilter ? detectedAgents.filter((a) => agentFilter.includes(a)) : detectedAgents;
1232
1273
  for (const agentType of agentsToCheck) {
1233
1274
  const agent = agents[agentType];
1234
1275
  if (scope.global && agent.globalSkillsDir === void 0) continue;
1235
1276
  const agentBase = scope.global ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1236
1277
  let found = false;
1237
- const possibleNames = [
1278
+ const possibleNames = Array.from(new Set([
1238
1279
  entry.name,
1239
1280
  sanitizedSkillName,
1240
1281
  skill.name.toLowerCase().replace(/\s+/g, "-").replace(/[\/\\:\0]/g, "")
1241
- ];
1242
- const uniqueNames = Array.from(new Set(possibleNames));
1243
- for (const possibleName of uniqueNames) {
1282
+ ]));
1283
+ for (const possibleName of possibleNames) {
1244
1284
  const agentSkillDir = join(agentBase, possibleName);
1245
1285
  if (!isPathSafe(agentBase, agentSkillDir)) continue;
1246
1286
  try {
@@ -1268,17 +1308,20 @@ async function listInstalledSkills(options = {}) {
1268
1308
  } catch {}
1269
1309
  if (found) installedAgents.push(agentType);
1270
1310
  }
1271
- installedSkills.push({
1311
+ if (skillsMap.has(skillKey)) {
1312
+ const existing = skillsMap.get(skillKey);
1313
+ for (const agent of installedAgents) if (!existing.agents.includes(agent)) existing.agents.push(agent);
1314
+ } else skillsMap.set(skillKey, {
1272
1315
  name: skill.name,
1273
1316
  description: skill.description,
1274
1317
  path: skillDir,
1275
1318
  canonicalPath: skillDir,
1276
- scope: scope.global ? "global" : "project",
1319
+ scope: scopeKey,
1277
1320
  agents: installedAgents
1278
1321
  });
1279
1322
  }
1280
1323
  } catch {}
1281
- return installedSkills;
1324
+ return Array.from(skillsMap.values());
1282
1325
  }
1283
1326
  const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
1284
1327
  let cliVersion = null;
@@ -1700,7 +1743,7 @@ async function saveSelectedAgents(agents) {
1700
1743
  lock.lastSelectedAgents = agents;
1701
1744
  await writeSkillLock(lock);
1702
1745
  }
1703
- var version$1 = "1.3.4";
1746
+ var version$1 = "1.3.5";
1704
1747
  const isCancelled = (value) => typeof value === "symbol";
1705
1748
  async function isSourcePrivate(source) {
1706
1749
  const ownerRepo = parseOwnerRepo(source);
@@ -1722,6 +1765,44 @@ function formatList$1(items, maxShow = 5) {
1722
1765
  const remaining = items.length - maxShow;
1723
1766
  return `${shown.join(", ")} +${remaining} more`;
1724
1767
  }
1768
+ function splitAgentsByType(agentTypes) {
1769
+ const universal = [];
1770
+ const symlinked = [];
1771
+ for (const a of agentTypes) if (isUniversalAgent(a)) universal.push(agents[a].displayName);
1772
+ else symlinked.push(agents[a].displayName);
1773
+ return {
1774
+ universal,
1775
+ symlinked
1776
+ };
1777
+ }
1778
+ function buildAgentSummaryLines(targetAgents, installMode) {
1779
+ const lines = [];
1780
+ const { universal, symlinked } = splitAgentsByType(targetAgents);
1781
+ if (installMode === "symlink") {
1782
+ if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
1783
+ if (symlinked.length > 0) lines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(symlinked)}`);
1784
+ } else {
1785
+ const allNames = targetAgents.map((a) => agents[a].displayName);
1786
+ lines.push(` ${import_picocolors.default.dim("copy →")} ${formatList$1(allNames)}`);
1787
+ }
1788
+ return lines;
1789
+ }
1790
+ function ensureUniversalAgents(targetAgents) {
1791
+ const universalAgents = getUniversalAgents();
1792
+ const result = [...targetAgents];
1793
+ for (const ua of universalAgents) if (!result.includes(ua)) result.push(ua);
1794
+ return result;
1795
+ }
1796
+ function buildResultLines(results, targetAgents) {
1797
+ const lines = [];
1798
+ const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
1799
+ const successfulSymlinks = results.filter((r) => !r.symlinkFailed && !universal.includes(r.agent)).map((r) => r.agent);
1800
+ const failedSymlinks = results.filter((r) => r.symlinkFailed).map((r) => r.agent);
1801
+ if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
1802
+ if (successfulSymlinks.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList$1(successfulSymlinks)}`);
1803
+ if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(failedSymlinks)}`);
1804
+ return lines;
1805
+ }
1725
1806
  function multiselect(opts) {
1726
1807
  return fe({
1727
1808
  ...opts,
@@ -1755,11 +1836,35 @@ async function promptForAgents(message, choices) {
1755
1836
  return selected;
1756
1837
  }
1757
1838
  async function selectAgentsInteractive(options) {
1758
- return promptForAgents("Which agents do you want to install to?", Object.keys(agents).map((a) => ({
1839
+ const supportsGlobalFilter = (a) => !options.global || agents[a].globalSkillsDir;
1840
+ const universalAgents = getUniversalAgents().filter(supportsGlobalFilter);
1841
+ const otherAgents = getNonUniversalAgents().filter(supportsGlobalFilter);
1842
+ const universalSection = {
1843
+ title: "Universal (.agents/skills)",
1844
+ items: universalAgents.map((a) => ({
1845
+ value: a,
1846
+ label: agents[a].displayName
1847
+ }))
1848
+ };
1849
+ const otherChoices = otherAgents.map((a) => ({
1759
1850
  value: a,
1760
1851
  label: agents[a].displayName,
1761
- hint: `${options.global ? agents[a].globalSkillsDir : agents[a].skillsDir}`
1762
- })));
1852
+ hint: options.global ? agents[a].globalSkillsDir : agents[a].skillsDir
1853
+ }));
1854
+ let lastSelected;
1855
+ try {
1856
+ lastSelected = await getLastSelectedAgents();
1857
+ } catch {}
1858
+ const selected = await searchMultiselect({
1859
+ message: "Which agents do you want to install to?",
1860
+ items: otherChoices,
1861
+ initialSelected: lastSelected ? lastSelected.filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)) : [],
1862
+ lockedSection: universalSection
1863
+ });
1864
+ if (!isCancelled(selected)) try {
1865
+ await saveSelectedAgents(selected);
1866
+ } catch {}
1867
+ return selected;
1763
1868
  }
1764
1869
  setVersion(version$1);
1765
1870
  async function handleRemoteSkill(source, url, options, spinner) {
@@ -1802,6 +1907,7 @@ async function handleRemoteSkill(source, url, options, spinner) {
1802
1907
  }
1803
1908
  let targetAgents;
1804
1909
  const validAgents = Object.keys(agents);
1910
+ const universalAgents = getUniversalAgents();
1805
1911
  if (options.agent?.includes("*")) {
1806
1912
  targetAgents = validAgents;
1807
1913
  M.info(`Installing to all ${targetAgents.length} agents`);
@@ -1812,21 +1918,17 @@ async function handleRemoteSkill(source, url, options, spinner) {
1812
1918
  M.info(`Valid agents: ${validAgents.join(", ")}`);
1813
1919
  process.exit(1);
1814
1920
  }
1815
- targetAgents = options.agent;
1921
+ targetAgents = ensureUniversalAgents(options.agent);
1816
1922
  } else {
1817
1923
  spinner.start("Loading agents...");
1818
1924
  const installedAgents = await detectInstalledAgents();
1819
1925
  const totalAgents = Object.keys(agents).length;
1820
1926
  spinner.stop(`${totalAgents} agents`);
1821
1927
  if (installedAgents.length === 0) if (options.yes) {
1822
- targetAgents = validAgents;
1823
- M.info("Installing to all agents");
1928
+ targetAgents = universalAgents;
1929
+ M.info(`Installing to universal agents`);
1824
1930
  } else {
1825
- M.info("Select agents to install skills to");
1826
- const selected = await promptForAgents("Which agents do you want to install to?", Object.entries(agents).map(([key, config]) => ({
1827
- value: key,
1828
- label: config.displayName
1829
- })));
1931
+ const selected = await selectAgentsInteractive({ global: options.global });
1830
1932
  if (pD(selected)) {
1831
1933
  xe("Installation cancelled");
1832
1934
  process.exit(0);
@@ -1834,11 +1936,10 @@ async function handleRemoteSkill(source, url, options, spinner) {
1834
1936
  targetAgents = selected;
1835
1937
  }
1836
1938
  else if (installedAgents.length === 1 || options.yes) {
1837
- targetAgents = installedAgents;
1838
- if (installedAgents.length === 1) {
1839
- const firstAgent = installedAgents[0];
1840
- M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
1841
- } else M.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
1939
+ targetAgents = ensureUniversalAgents(installedAgents);
1940
+ const { universal, symlinked } = splitAgentsByType(targetAgents);
1941
+ if (symlinked.length > 0) M.info(`Installing to: ${import_picocolors.default.green("universal")} + ${symlinked.map((a) => import_picocolors.default.cyan(a)).join(", ")}`);
1942
+ else M.info(`Installing to: ${import_picocolors.default.green("universal agents")}`);
1842
1943
  } else {
1843
1944
  const selected = await selectAgentsInteractive({ global: options.global });
1844
1945
  if (pD(selected)) {
@@ -1896,15 +1997,9 @@ async function handleRemoteSkill(source, url, options, spinner) {
1896
1997
  })));
1897
1998
  const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
1898
1999
  const summaryLines = [];
1899
- const agentNames = targetAgents.map((a) => agents[a].displayName);
1900
- if (installMode === "symlink") {
1901
- const shortCanonical = shortenPath$1(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
1902
- summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
1903
- summaryLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(agentNames)}`);
1904
- } else {
1905
- summaryLines.push(`${import_picocolors.default.cyan(remoteSkill.installName)}`);
1906
- summaryLines.push(` ${import_picocolors.default.dim("copy →")} ${formatList$1(agentNames)}`);
1907
- }
2000
+ const shortCanonical = shortenPath$1(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
2001
+ summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2002
+ summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
1908
2003
  const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
1909
2004
  if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
1910
2005
  console.log();
@@ -1969,12 +2064,9 @@ async function handleRemoteSkill(source, url, options, spinner) {
1969
2064
  const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
1970
2065
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
1971
2066
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
1972
- const symlinked = successful.filter((r) => !r.symlinkFailed).map((r) => r.agent);
1973
- const copied = successful.filter((r) => r.symlinkFailed).map((r) => r.agent);
1974
- if (symlinked.length > 0) resultLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(symlinked)}`);
1975
- if (copied.length > 0) resultLines.push(` ${import_picocolors.default.yellow("copied →")} ${formatList$1(copied)}`);
2067
+ resultLines.push(...buildResultLines(successful, targetAgents));
1976
2068
  }
1977
- const title = import_picocolors.default.green(`Installed 1 skill to ${successful.length} agent${successful.length !== 1 ? "s" : ""}`);
2069
+ const title = import_picocolors.default.green("Installed 1 skill");
1978
2070
  Me(resultLines.join("\n"), title);
1979
2071
  const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
1980
2072
  if (symlinkFailures.length > 0) {
@@ -2145,7 +2237,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2145
2237
  }
2146
2238
  const cwd = process.cwd();
2147
2239
  const summaryLines = [];
2148
- const agentNames = targetAgents.map((a) => agents[a].displayName);
2240
+ targetAgents.map((a) => agents[a].displayName);
2149
2241
  const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
2150
2242
  skillName: skill.installName,
2151
2243
  agent,
@@ -2158,15 +2250,10 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2158
2250
  }
2159
2251
  for (const skill of selectedSkills) {
2160
2252
  if (summaryLines.length > 0) summaryLines.push("");
2161
- if (installMode === "symlink") {
2162
- const shortCanonical = shortenPath$1(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2163
- summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2164
- summaryLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(agentNames)}`);
2165
- if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
2166
- } else {
2167
- summaryLines.push(`${import_picocolors.default.cyan(skill.installName)}`);
2168
- summaryLines.push(` ${import_picocolors.default.dim("copy →")} ${formatList$1(agentNames)}`);
2169
- }
2253
+ const shortCanonical = shortenPath$1(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2254
+ summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2255
+ summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2256
+ if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
2170
2257
  const skillOverwrites = overwriteStatus.get(skill.installName);
2171
2258
  const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
2172
2259
  if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
@@ -2228,7 +2315,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2228
2315
  bySkill.set(r.skill, skillResults);
2229
2316
  }
2230
2317
  const skillCount = bySkill.size;
2231
- const agentCount = new Set(successful.map((r) => r.agent)).size;
2232
2318
  const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2233
2319
  const copiedAgents = symlinkFailures.map((r) => r.agent);
2234
2320
  const resultLines = [];
@@ -2245,13 +2331,10 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2245
2331
  const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
2246
2332
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2247
2333
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
2248
- const symlinked = skillResults.filter((r) => !r.symlinkFailed).map((r) => r.agent);
2249
- const copied = skillResults.filter((r) => r.symlinkFailed).map((r) => r.agent);
2250
- if (symlinked.length > 0) resultLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(symlinked)}`);
2251
- if (copied.length > 0) resultLines.push(` ${import_picocolors.default.yellow("copied →")} ${formatList$1(copied)}`);
2334
+ resultLines.push(...buildResultLines(skillResults, targetAgents));
2252
2335
  }
2253
2336
  }
2254
- const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""} to ${agentCount} agent${agentCount !== 1 ? "s" : ""}`);
2337
+ const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
2255
2338
  Me(resultLines.join("\n"), title);
2256
2339
  if (symlinkFailures.length > 0) {
2257
2340
  M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
@@ -2374,10 +2457,10 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
2374
2457
  })));
2375
2458
  const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
2376
2459
  const summaryLines = [];
2377
- const agentNames = targetAgents.map((a) => agents[a].displayName);
2460
+ targetAgents.map((a) => agents[a].displayName);
2378
2461
  const shortCanonical = shortenPath$1(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
2379
2462
  summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2380
- summaryLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(agentNames)}`);
2463
+ summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2381
2464
  const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
2382
2465
  if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
2383
2466
  console.log();
@@ -2430,11 +2513,8 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
2430
2513
  const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
2431
2514
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2432
2515
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
2433
- const symlinked = successful.filter((r) => !r.symlinkFailed).map((r) => r.agent);
2434
- const copied = successful.filter((r) => r.symlinkFailed).map((r) => r.agent);
2435
- if (symlinked.length > 0) resultLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(symlinked)}`);
2436
- if (copied.length > 0) resultLines.push(` ${import_picocolors.default.yellow("copied →")} ${formatList$1(copied)}`);
2437
- const title = import_picocolors.default.green(`Installed 1 skill to ${successful.length} agent${successful.length !== 1 ? "s" : ""}`);
2516
+ resultLines.push(...buildResultLines(successful, targetAgents));
2517
+ const title = import_picocolors.default.green("Installed 1 skill");
2438
2518
  Me(resultLines.join("\n"), title);
2439
2519
  const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2440
2520
  if (symlinkFailures.length > 0) {
@@ -2674,7 +2754,7 @@ async function runAdd(args, options = {}) {
2674
2754
  }
2675
2755
  const cwd = process.cwd();
2676
2756
  const summaryLines = [];
2677
- const agentNames = targetAgents.map((a) => agents[a].displayName);
2757
+ targetAgents.map((a) => agents[a].displayName);
2678
2758
  const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
2679
2759
  skillName: skill.name,
2680
2760
  agent,
@@ -2687,14 +2767,9 @@ async function runAdd(args, options = {}) {
2687
2767
  }
2688
2768
  for (const skill of selectedSkills) {
2689
2769
  if (summaryLines.length > 0) summaryLines.push("");
2690
- if (installMode === "symlink") {
2691
- const shortCanonical = shortenPath$1(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
2692
- summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2693
- summaryLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(agentNames)}`);
2694
- } else {
2695
- summaryLines.push(`${import_picocolors.default.cyan(getSkillDisplayName(skill))}`);
2696
- summaryLines.push(` ${import_picocolors.default.dim("copy →")} ${formatList$1(agentNames)}`);
2697
- }
2770
+ const shortCanonical = shortenPath$1(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
2771
+ summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2772
+ summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2698
2773
  const skillOverwrites = overwriteStatus.get(skill.name);
2699
2774
  const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
2700
2775
  if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
@@ -2784,7 +2859,6 @@ async function runAdd(args, options = {}) {
2784
2859
  bySkill.set(r.skill, skillResults);
2785
2860
  }
2786
2861
  const skillCount = bySkill.size;
2787
- const agentCount = new Set(successful.map((r) => r.agent)).size;
2788
2862
  const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2789
2863
  const copiedAgents = symlinkFailures.map((r) => r.agent);
2790
2864
  const resultLines = [];
@@ -2801,13 +2875,10 @@ async function runAdd(args, options = {}) {
2801
2875
  const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
2802
2876
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2803
2877
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
2804
- const symlinked = skillResults.filter((r) => !r.symlinkFailed).map((r) => r.agent);
2805
- const copied = skillResults.filter((r) => r.symlinkFailed).map((r) => r.agent);
2806
- if (symlinked.length > 0) resultLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(symlinked)}`);
2807
- if (copied.length > 0) resultLines.push(` ${import_picocolors.default.yellow("copied →")} ${formatList$1(copied)}`);
2878
+ resultLines.push(...buildResultLines(skillResults, targetAgents));
2808
2879
  }
2809
2880
  }
2810
- const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""} to ${agentCount} agent${agentCount !== 1 ? "s" : ""}`);
2881
+ const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
2811
2882
  Me(resultLines.join("\n"), title);
2812
2883
  if (symlinkFailures.length > 0) {
2813
2884
  M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {