skills 1.4.0 → 1.4.2

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.
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { r as __toESM } from "./_chunks/rolldown-runtime.mjs";
3
3
  import { l as pD, u as require_picocolors } from "./_chunks/libs/@clack/core.mjs";
4
- import { a as Y, c as xe, i as Se, l as ye, n as M, o as fe, r as Me, s as ve, t as Ie } from "./_chunks/libs/@clack/prompts.mjs";
4
+ import { a as Y, c as ve, i as Se, l as xe, n as M, o as be, r as Me, s as fe, t as Ie, u as ye } from "./_chunks/libs/@clack/prompts.mjs";
5
5
  import "./_chunks/libs/@kwsites/file-exists.mjs";
6
6
  import "./_chunks/libs/@kwsites/promise-deferred.mjs";
7
7
  import { t as esm_default } from "./_chunks/libs/simple-git.mjs";
@@ -13,7 +13,7 @@ import { execSync, spawn, spawnSync } from "child_process";
13
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
14
14
  import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
15
15
  import { homedir, platform, tmpdir } from "os";
16
- import "crypto";
16
+ import { createHash } from "crypto";
17
17
  import { fileURLToPath } from "url";
18
18
  import * as readline from "readline";
19
19
  import { Writable } from "stream";
@@ -180,7 +180,8 @@ const S_STEP_CANCEL = import_picocolors.default.red("■");
180
180
  const S_STEP_SUBMIT = import_picocolors.default.green("◇");
181
181
  const S_RADIO_ACTIVE = import_picocolors.default.green("●");
182
182
  const S_RADIO_INACTIVE = import_picocolors.default.dim("○");
183
- const S_CHECKBOX_LOCKED = import_picocolors.default.green("✓");
183
+ import_picocolors.default.green("✓");
184
+ const S_BULLET = import_picocolors.default.green("•");
184
185
  const S_BAR = import_picocolors.default.dim("│");
185
186
  const S_BAR_H = import_picocolors.default.dim("─");
186
187
  const cancelSymbol = Symbol("cancel");
@@ -223,10 +224,11 @@ async function searchMultiselect(options) {
223
224
  if (state === "active") {
224
225
  if (lockedSection && lockedSection.items.length > 0) {
225
226
  lines.push(`${S_BAR}`);
226
- lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold(lockedSection.title)} ${S_BAR_H.repeat(30)}`);
227
- for (const item of lockedSection.items) lines.push(`${S_BAR} ${S_CHECKBOX_LOCKED} ${item.label}`);
227
+ const lockedTitle = `${import_picocolors.default.bold(lockedSection.title)} ${import_picocolors.default.dim("── always included")}`;
228
+ lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${lockedTitle} ${S_BAR_H.repeat(12)}`);
229
+ for (const item of lockedSection.items) lines.push(`${S_BAR} ${S_BULLET} ${import_picocolors.default.bold(item.label)}`);
228
230
  lines.push(`${S_BAR}`);
229
- lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Other agents")} ${S_BAR_H.repeat(34)}`);
231
+ lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Additional agents")} ${S_BAR_H.repeat(29)}`);
230
232
  }
231
233
  const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
232
234
  lines.push(searchLine);
@@ -348,7 +350,13 @@ var GitCloneError = class extends Error {
348
350
  };
349
351
  async function cloneRepo(url, ref) {
350
352
  const tempDir = await mkdtemp(join(tmpdir(), "skills-"));
351
- const git = esm_default({ timeout: { block: CLONE_TIMEOUT_MS } });
353
+ const git = esm_default({
354
+ timeout: { block: CLONE_TIMEOUT_MS },
355
+ env: {
356
+ ...process.env,
357
+ GIT_TERMINAL_PROMPT: "0"
358
+ }
359
+ });
352
360
  const cloneOptions = ref ? [
353
361
  "--depth",
354
362
  "1",
@@ -416,6 +424,36 @@ async function getPluginSkillPaths(basePath) {
416
424
  } catch {}
417
425
  return searchDirs;
418
426
  }
427
+ async function getPluginGroupings(basePath) {
428
+ const groupings = /* @__PURE__ */ new Map();
429
+ try {
430
+ const content = await readFile(join(basePath, ".claude-plugin/marketplace.json"), "utf-8");
431
+ const manifest = JSON.parse(content);
432
+ const pluginRoot = manifest.metadata?.pluginRoot;
433
+ if (pluginRoot === void 0 || isValidRelativePath(pluginRoot)) for (const plugin of manifest.plugins ?? []) {
434
+ if (!plugin.name) continue;
435
+ if (typeof plugin.source !== "string" && plugin.source !== void 0) continue;
436
+ if (plugin.source !== void 0 && !isValidRelativePath(plugin.source)) continue;
437
+ const pluginBase = join(basePath, pluginRoot ?? "", plugin.source ?? "");
438
+ if (!isContainedIn(pluginBase, basePath)) continue;
439
+ if (plugin.skills && plugin.skills.length > 0) for (const skillPath of plugin.skills) {
440
+ if (!isValidRelativePath(skillPath)) continue;
441
+ const skillDir = join(pluginBase, skillPath);
442
+ if (isContainedIn(skillDir, basePath)) groupings.set(resolve(skillDir), plugin.name);
443
+ }
444
+ }
445
+ } catch {}
446
+ try {
447
+ const content = await readFile(join(basePath, ".claude-plugin/plugin.json"), "utf-8");
448
+ const manifest = JSON.parse(content);
449
+ if (manifest.name && manifest.skills && manifest.skills.length > 0) for (const skillPath of manifest.skills) {
450
+ if (!isValidRelativePath(skillPath)) continue;
451
+ const skillDir = join(basePath, skillPath);
452
+ if (isContainedIn(skillDir, basePath)) groupings.set(resolve(skillDir), manifest.name);
453
+ }
454
+ } catch {}
455
+ return groupings;
456
+ }
419
457
  const SKIP_DIRS = [
420
458
  "node_modules",
421
459
  ".git",
@@ -467,9 +505,16 @@ async function discoverSkills(basePath, subpath, options) {
467
505
  const skills = [];
468
506
  const seenNames = /* @__PURE__ */ new Set();
469
507
  const searchPath = subpath ? join(basePath, subpath) : basePath;
508
+ const pluginGroupings = await getPluginGroupings(searchPath);
509
+ const enhanceSkill = (skill) => {
510
+ const resolvedPath = resolve(skill.path);
511
+ if (pluginGroupings.has(resolvedPath)) skill.pluginName = pluginGroupings.get(resolvedPath);
512
+ return skill;
513
+ };
470
514
  if (await hasSkillMd(searchPath)) {
471
- const skill = await parseSkillMd(join(searchPath, "SKILL.md"), options);
515
+ let skill = await parseSkillMd(join(searchPath, "SKILL.md"), options);
472
516
  if (skill) {
517
+ skill = enhanceSkill(skill);
473
518
  skills.push(skill);
474
519
  seenNames.add(skill.name);
475
520
  if (!options?.fullDepth) return skills;
@@ -489,7 +534,6 @@ async function discoverSkills(basePath, subpath, options) {
489
534
  join(searchPath, ".codex/skills"),
490
535
  join(searchPath, ".commandcode/skills"),
491
536
  join(searchPath, ".continue/skills"),
492
- join(searchPath, ".cursor/skills"),
493
537
  join(searchPath, ".github/skills"),
494
538
  join(searchPath, ".goose/skills"),
495
539
  join(searchPath, ".iflow/skills"),
@@ -513,8 +557,9 @@ async function discoverSkills(basePath, subpath, options) {
513
557
  for (const entry of entries) if (entry.isDirectory()) {
514
558
  const skillDir = join(dir, entry.name);
515
559
  if (await hasSkillMd(skillDir)) {
516
- const skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
560
+ let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
517
561
  if (skill && !seenNames.has(skill.name)) {
562
+ skill = enhanceSkill(skill);
518
563
  skills.push(skill);
519
564
  seenNames.add(skill.name);
520
565
  }
@@ -524,8 +569,9 @@ async function discoverSkills(basePath, subpath, options) {
524
569
  if (skills.length === 0 || options?.fullDepth) {
525
570
  const allSkillDirs = await findSkillDirs(searchPath);
526
571
  for (const skillDir of allSkillDirs) {
527
- const skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
572
+ let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
528
573
  if (skill && !seenNames.has(skill.name)) {
574
+ skill = enhanceSkill(skill);
529
575
  skills.push(skill);
530
576
  seenNames.add(skill.name);
531
577
  }
@@ -570,7 +616,7 @@ const agents = {
570
616
  skillsDir: ".agent/skills",
571
617
  globalSkillsDir: join(home, ".gemini/antigravity/skills"),
572
618
  detectInstalled: async () => {
573
- return existsSync(join(process.cwd(), ".agent")) || existsSync(join(home, ".gemini/antigravity"));
619
+ return existsSync(join(home, ".gemini/antigravity"));
574
620
  }
575
621
  },
576
622
  augment: {
@@ -603,8 +649,8 @@ const agents = {
603
649
  cline: {
604
650
  name: "cline",
605
651
  displayName: "Cline",
606
- skillsDir: ".cline/skills",
607
- globalSkillsDir: join(home, ".cline/skills"),
652
+ skillsDir: ".agents/skills",
653
+ globalSkillsDir: join(home, ".agents", "skills"),
608
654
  detectInstalled: async () => {
609
655
  return existsSync(join(home, ".cline"));
610
656
  }
@@ -645,6 +691,15 @@ const agents = {
645
691
  return existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"));
646
692
  }
647
693
  },
694
+ cortex: {
695
+ name: "cortex",
696
+ displayName: "Cortex Code",
697
+ skillsDir: ".cortex/skills",
698
+ globalSkillsDir: join(home, ".snowflake/cortex/skills"),
699
+ detectInstalled: async () => {
700
+ return existsSync(join(home, ".snowflake/cortex"));
701
+ }
702
+ },
648
703
  crush: {
649
704
  name: "crush",
650
705
  displayName: "Crush",
@@ -657,7 +712,7 @@ const agents = {
657
712
  cursor: {
658
713
  name: "cursor",
659
714
  displayName: "Cursor",
660
- skillsDir: ".cursor/skills",
715
+ skillsDir: ".agents/skills",
661
716
  globalSkillsDir: join(home, ".cursor/skills"),
662
717
  detectInstalled: async () => {
663
718
  return existsSync(join(home, ".cursor"));
@@ -687,7 +742,7 @@ const agents = {
687
742
  skillsDir: ".agents/skills",
688
743
  globalSkillsDir: join(home, ".copilot/skills"),
689
744
  detectInstalled: async () => {
690
- return existsSync(join(process.cwd(), ".github")) || existsSync(join(home, ".copilot"));
745
+ return existsSync(join(home, ".copilot"));
691
746
  }
692
747
  },
693
748
  goose: {
@@ -786,7 +841,7 @@ const agents = {
786
841
  skillsDir: ".agents/skills",
787
842
  globalSkillsDir: join(configHome, "opencode/skills"),
788
843
  detectInstalled: async () => {
789
- return existsSync(join(configHome, "opencode")) || existsSync(join(claudeHome, "skills"));
844
+ return existsSync(join(configHome, "opencode"));
790
845
  }
791
846
  },
792
847
  openhands: {
@@ -832,7 +887,7 @@ const agents = {
832
887
  globalSkillsDir: join(configHome, "agents/skills"),
833
888
  showInUniversalList: false,
834
889
  detectInstalled: async () => {
835
- return existsSync(join(process.cwd(), ".agents"));
890
+ return existsSync(join(process.cwd(), ".replit"));
836
891
  }
837
892
  },
838
893
  roo: {
@@ -906,6 +961,14 @@ const agents = {
906
961
  detectInstalled: async () => {
907
962
  return existsSync(join(home, ".adal"));
908
963
  }
964
+ },
965
+ universal: {
966
+ name: "universal",
967
+ displayName: "Universal",
968
+ skillsDir: ".agents/skills",
969
+ globalSkillsDir: join(configHome, "agents/skills"),
970
+ showInUniversalList: false,
971
+ detectInstalled: async () => false
909
972
  }
910
973
  };
911
974
  async function detectInstalledAgents() {
@@ -936,6 +999,16 @@ function isPathSafe(basePath, targetPath) {
936
999
  function getCanonicalSkillsDir(global, cwd) {
937
1000
  return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
938
1001
  }
1002
+ function getAgentBaseDir(agentType, global, cwd) {
1003
+ if (isUniversalAgent(agentType)) return getCanonicalSkillsDir(global, cwd);
1004
+ const agent = agents[agentType];
1005
+ const baseDir = global ? homedir() : cwd || process.cwd();
1006
+ if (global) {
1007
+ if (agent.globalSkillsDir === void 0) return join(baseDir, agent.skillsDir);
1008
+ return agent.globalSkillsDir;
1009
+ }
1010
+ return join(baseDir, agent.skillsDir);
1011
+ }
939
1012
  function resolveSymlinkTarget(linkPath, linkTarget) {
940
1013
  return resolve(dirname(linkPath), linkTarget);
941
1014
  }
@@ -961,7 +1034,9 @@ async function resolveParentSymlinks(path) {
961
1034
  async function createSymlink(target, linkPath) {
962
1035
  try {
963
1036
  const resolvedTarget = resolve(target);
964
- if (resolvedTarget === resolve(linkPath)) return true;
1037
+ const resolvedLinkPath = resolve(linkPath);
1038
+ const [realTarget, realLinkPath] = await Promise.all([realpath(resolvedTarget).catch(() => resolvedTarget), realpath(resolvedLinkPath).catch(() => resolvedLinkPath)]);
1039
+ if (realTarget === realLinkPath) return true;
965
1040
  if (await resolveParentSymlinks(target) === await resolveParentSymlinks(linkPath)) return true;
966
1041
  try {
967
1042
  if ((await lstat(linkPath)).isSymbolicLink()) {
@@ -994,7 +1069,7 @@ async function installSkillForAgent(skill, agentType, options = {}) {
994
1069
  const skillName = sanitizeName(skill.name || basename(skill.path));
995
1070
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
996
1071
  const canonicalDir = join(canonicalBase, skillName);
997
- const agentBase = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1072
+ const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
998
1073
  const agentDir = join(agentBase, skillName);
999
1074
  const installMode = options.mode ?? "symlink";
1000
1075
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
@@ -1053,7 +1128,7 @@ async function installSkillForAgent(skill, agentType, options = {}) {
1053
1128
  };
1054
1129
  }
1055
1130
  }
1056
- const EXCLUDE_FILES = new Set(["README.md", "metadata.json"]);
1131
+ const EXCLUDE_FILES = new Set(["metadata.json"]);
1057
1132
  const EXCLUDE_DIRS = new Set([".git"]);
1058
1133
  const isExcluded = (name, isDirectory = false) => {
1059
1134
  if (EXCLUDE_FILES.has(name)) return true;
@@ -1089,10 +1164,10 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
1089
1164
  }
1090
1165
  }
1091
1166
  function getInstallPath(skillName, agentType, options = {}) {
1092
- const agent = agents[agentType];
1093
- const cwd = options.cwd || process.cwd();
1167
+ agents[agentType];
1168
+ options.cwd || process.cwd();
1094
1169
  const sanitized = sanitizeName(skillName);
1095
- const targetBase = options.global && agent.globalSkillsDir !== void 0 ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1170
+ const targetBase = getAgentBaseDir(agentType, options.global ?? false, options.cwd);
1096
1171
  const installPath = join(targetBase, sanitized);
1097
1172
  if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
1098
1173
  return installPath;
@@ -1118,7 +1193,7 @@ async function installRemoteSkillForAgent(skill, agentType, options = {}) {
1118
1193
  const skillName = sanitizeName(skill.installName);
1119
1194
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
1120
1195
  const canonicalDir = join(canonicalBase, skillName);
1121
- const agentBase = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1196
+ const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1122
1197
  const agentDir = join(agentBase, skillName);
1123
1198
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
1124
1199
  success: false,
@@ -1190,7 +1265,7 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1190
1265
  const skillName = sanitizeName(skill.installName);
1191
1266
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
1192
1267
  const canonicalDir = join(canonicalBase, skillName);
1193
- const agentBase = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1268
+ const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1194
1269
  const agentDir = join(agentBase, skillName);
1195
1270
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
1196
1271
  success: false,
@@ -1718,7 +1793,7 @@ async function fetchMintlifySkill(url) {
1718
1793
  }
1719
1794
  const AGENTS_DIR$1 = ".agents";
1720
1795
  const LOCK_FILE$1 = ".skill-lock.json";
1721
- const CURRENT_VERSION = 3;
1796
+ const CURRENT_VERSION$1 = 3;
1722
1797
  function getSkillLockPath$1() {
1723
1798
  return join(homedir(), AGENTS_DIR$1, LOCK_FILE$1);
1724
1799
  }
@@ -1728,7 +1803,7 @@ async function readSkillLock$1() {
1728
1803
  const content = await readFile(lockPath, "utf-8");
1729
1804
  const parsed = JSON.parse(content);
1730
1805
  if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLockFile();
1731
- if (parsed.version < CURRENT_VERSION) return createEmptyLockFile();
1806
+ if (parsed.version < CURRENT_VERSION$1) return createEmptyLockFile();
1732
1807
  return parsed;
1733
1808
  } catch (error) {
1734
1809
  return createEmptyLockFile();
@@ -1799,9 +1874,12 @@ async function removeSkillFromLock(skillName) {
1799
1874
  async function getSkillFromLock(skillName) {
1800
1875
  return (await readSkillLock$1()).skills[skillName] ?? null;
1801
1876
  }
1877
+ async function getAllLockedSkills() {
1878
+ return (await readSkillLock$1()).skills;
1879
+ }
1802
1880
  function createEmptyLockFile() {
1803
1881
  return {
1804
- version: CURRENT_VERSION,
1882
+ version: CURRENT_VERSION$1,
1805
1883
  skills: {},
1806
1884
  dismissed: {}
1807
1885
  };
@@ -1823,8 +1901,74 @@ async function saveSelectedAgents(agents) {
1823
1901
  lock.lastSelectedAgents = agents;
1824
1902
  await writeSkillLock(lock);
1825
1903
  }
1826
- var version$1 = "1.4.0";
1827
- const isCancelled = (value) => typeof value === "symbol";
1904
+ const LOCAL_LOCK_FILE = "skills-lock.json";
1905
+ const CURRENT_VERSION = 1;
1906
+ function getLocalLockPath(cwd) {
1907
+ return join(cwd || process.cwd(), LOCAL_LOCK_FILE);
1908
+ }
1909
+ async function readLocalLock(cwd) {
1910
+ const lockPath = getLocalLockPath(cwd);
1911
+ try {
1912
+ const content = await readFile(lockPath, "utf-8");
1913
+ const parsed = JSON.parse(content);
1914
+ if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
1915
+ if (parsed.version < CURRENT_VERSION) return createEmptyLocalLock();
1916
+ return parsed;
1917
+ } catch {
1918
+ return createEmptyLocalLock();
1919
+ }
1920
+ }
1921
+ async function writeLocalLock(lock, cwd) {
1922
+ const lockPath = getLocalLockPath(cwd);
1923
+ const sortedSkills = {};
1924
+ for (const key of Object.keys(lock.skills).sort()) sortedSkills[key] = lock.skills[key];
1925
+ const sorted = {
1926
+ version: lock.version,
1927
+ skills: sortedSkills
1928
+ };
1929
+ await writeFile(lockPath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
1930
+ }
1931
+ async function computeSkillFolderHash(skillDir) {
1932
+ const files = [];
1933
+ await collectFiles(skillDir, skillDir, files);
1934
+ files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
1935
+ const hash = createHash("sha256");
1936
+ for (const file of files) {
1937
+ hash.update(file.relativePath);
1938
+ hash.update(file.content);
1939
+ }
1940
+ return hash.digest("hex");
1941
+ }
1942
+ async function collectFiles(baseDir, currentDir, results) {
1943
+ const entries = await readdir(currentDir, { withFileTypes: true });
1944
+ await Promise.all(entries.map(async (entry) => {
1945
+ const fullPath = join(currentDir, entry.name);
1946
+ if (entry.isDirectory()) {
1947
+ if (entry.name === ".git" || entry.name === "node_modules") return;
1948
+ await collectFiles(baseDir, fullPath, results);
1949
+ } else if (entry.isFile()) {
1950
+ const content = await readFile(fullPath);
1951
+ const relativePath = relative(baseDir, fullPath).split("\\").join("/");
1952
+ results.push({
1953
+ relativePath,
1954
+ content
1955
+ });
1956
+ }
1957
+ }));
1958
+ }
1959
+ async function addSkillToLocalLock(skillName, entry, cwd) {
1960
+ const lock = await readLocalLock(cwd);
1961
+ lock.skills[skillName] = entry;
1962
+ await writeLocalLock(lock, cwd);
1963
+ }
1964
+ function createEmptyLocalLock() {
1965
+ return {
1966
+ version: CURRENT_VERSION,
1967
+ skills: {}
1968
+ };
1969
+ }
1970
+ var version$1 = "1.4.2";
1971
+ const isCancelled$1 = (value) => typeof value === "symbol";
1828
1972
  async function isSourcePrivate(source) {
1829
1973
  const ownerRepo = parseOwnerRepo(source);
1830
1974
  if (!ownerRepo) return false;
@@ -1875,7 +2019,7 @@ function buildSecurityLines(auditData, skills, source) {
1875
2019
  lines.push(`${import_picocolors.default.dim("Details:")} ${import_picocolors.default.dim(`https://skills.sh/${source}`)}`);
1876
2020
  return lines;
1877
2021
  }
1878
- function shortenPath$1(fullPath, cwd) {
2022
+ function shortenPath$2(fullPath, cwd) {
1879
2023
  const home = homedir();
1880
2024
  if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
1881
2025
  if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
@@ -1952,7 +2096,7 @@ async function promptForAgents(message, choices) {
1952
2096
  initialSelected: initialValues,
1953
2097
  required: true
1954
2098
  });
1955
- if (!isCancelled(selected)) try {
2099
+ if (!isCancelled$1(selected)) try {
1956
2100
  await saveSelectedAgents(selected);
1957
2101
  } catch {}
1958
2102
  return selected;
@@ -1983,7 +2127,7 @@ async function selectAgentsInteractive(options) {
1983
2127
  initialSelected: lastSelected ? lastSelected.filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)) : [],
1984
2128
  lockedSection: universalSection
1985
2129
  });
1986
- if (!isCancelled(selected)) try {
2130
+ if (!isCancelled$1(selected)) try {
1987
2131
  await saveSelectedAgents(selected);
1988
2132
  } catch {}
1989
2133
  return selected;
@@ -2040,7 +2184,7 @@ async function handleRemoteSkill(source, url, options, spinner) {
2040
2184
  M.info(`Valid agents: ${validAgents.join(", ")}`);
2041
2185
  process.exit(1);
2042
2186
  }
2043
- targetAgents = ensureUniversalAgents(options.agent);
2187
+ targetAgents = options.agent;
2044
2188
  } else {
2045
2189
  spinner.start("Loading agents...");
2046
2190
  const installedAgents = await detectInstalledAgents();
@@ -2092,8 +2236,8 @@ async function handleRemoteSkill(source, url, options, spinner) {
2092
2236
  }
2093
2237
  installGlobally = scope;
2094
2238
  }
2095
- let installMode = "symlink";
2096
- if (!options.yes) {
2239
+ let installMode = options.copy ? "copy" : "symlink";
2240
+ if (!options.copy && !options.yes) {
2097
2241
  const modeChoice = await ve({
2098
2242
  message: "Installation method",
2099
2243
  options: [{
@@ -2119,7 +2263,7 @@ async function handleRemoteSkill(source, url, options, spinner) {
2119
2263
  })));
2120
2264
  const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
2121
2265
  const summaryLines = [];
2122
- const shortCanonical = shortenPath$1(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
2266
+ const shortCanonical = shortenPath$2(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
2123
2267
  summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2124
2268
  summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2125
2269
  const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
@@ -2172,18 +2316,27 @@ async function handleRemoteSkill(source, url, options, spinner) {
2172
2316
  skillFolderHash
2173
2317
  });
2174
2318
  } catch {}
2319
+ if (successful.length > 0 && !installGlobally) try {
2320
+ const firstResult = successful[0];
2321
+ const computedHash = await computeSkillFolderHash(firstResult.canonicalPath || firstResult.path);
2322
+ await addSkillToLocalLock(remoteSkill.installName, {
2323
+ source: remoteSkill.sourceIdentifier,
2324
+ sourceType: remoteSkill.providerId,
2325
+ computedHash
2326
+ }, cwd);
2327
+ } catch {}
2175
2328
  if (successful.length > 0) {
2176
2329
  const resultLines = [];
2177
2330
  const firstResult = successful[0];
2178
2331
  if (firstResult.mode === "copy") {
2179
2332
  resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName} ${import_picocolors.default.dim("(copied)")}`);
2180
2333
  for (const r of successful) {
2181
- const shortPath = shortenPath$1(r.path, cwd);
2334
+ const shortPath = shortenPath$2(r.path, cwd);
2182
2335
  resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
2183
2336
  }
2184
2337
  } else {
2185
2338
  if (firstResult.canonicalPath) {
2186
- const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
2339
+ const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2187
2340
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2188
2341
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
2189
2342
  resultLines.push(...buildResultLines(successful, targetAgents));
@@ -2244,7 +2397,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2244
2397
  for (const s of skills) M.message(` - ${s.installName}`);
2245
2398
  process.exit(1);
2246
2399
  }
2247
- M.info(`Selected ${selectedSkills.length} skill${selectedSkills.length !== 1 ? "s" : ""}: ${selectedSkills.map((s) => import_picocolors.default.cyan(s.installName)).join(", ")}`);
2248
2400
  } else if (skills.length === 1) {
2249
2401
  selectedSkills = skills;
2250
2402
  const firstSkill = skills[0];
@@ -2302,7 +2454,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2302
2454
  targetAgents = selected;
2303
2455
  }
2304
2456
  else if (installedAgents.length === 1 || options.yes) {
2305
- targetAgents = installedAgents;
2457
+ targetAgents = ensureUniversalAgents(installedAgents);
2306
2458
  if (installedAgents.length === 1) {
2307
2459
  const firstAgent = installedAgents[0];
2308
2460
  M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
@@ -2337,8 +2489,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2337
2489
  }
2338
2490
  installGlobally = scope;
2339
2491
  }
2340
- let installMode = "symlink";
2341
- if (!options.yes) {
2492
+ let installMode = options.copy ? "copy" : "symlink";
2493
+ if (!options.copy && !options.yes) {
2342
2494
  const modeChoice = await ve({
2343
2495
  message: "Installation method",
2344
2496
  options: [{
@@ -2372,7 +2524,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2372
2524
  }
2373
2525
  for (const skill of selectedSkills) {
2374
2526
  if (summaryLines.length > 0) summaryLines.push("");
2375
- const shortCanonical = shortenPath$1(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2527
+ const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2376
2528
  summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2377
2529
  summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2378
2530
  if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
@@ -2429,6 +2581,21 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2429
2581
  });
2430
2582
  } catch {}
2431
2583
  }
2584
+ if (successful.length > 0 && !installGlobally) {
2585
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
2586
+ for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2587
+ const matchingResult = successful.find((r) => r.skill === skill.installName);
2588
+ const installDir = matchingResult?.canonicalPath || matchingResult?.path;
2589
+ if (installDir) {
2590
+ const computedHash = await computeSkillFolderHash(installDir);
2591
+ await addSkillToLocalLock(skill.installName, {
2592
+ source: sourceIdentifier,
2593
+ sourceType: "well-known",
2594
+ computedHash
2595
+ }, cwd);
2596
+ }
2597
+ } catch {}
2598
+ }
2432
2599
  if (successful.length > 0) {
2433
2600
  const bySkill = /* @__PURE__ */ new Map();
2434
2601
  for (const r of successful) {
@@ -2445,12 +2612,12 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2445
2612
  if (firstResult.mode === "copy") {
2446
2613
  resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
2447
2614
  for (const r of skillResults) {
2448
- const shortPath = shortenPath$1(r.path, cwd);
2615
+ const shortPath = shortenPath$2(r.path, cwd);
2449
2616
  resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
2450
2617
  }
2451
2618
  } else {
2452
2619
  if (firstResult.canonicalPath) {
2453
- const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
2620
+ const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2454
2621
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2455
2622
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
2456
2623
  resultLines.push(...buildResultLines(skillResults, targetAgents));
@@ -2536,7 +2703,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
2536
2703
  targetAgents = selected;
2537
2704
  }
2538
2705
  else if (installedAgents.length === 1 || options.yes) {
2539
- targetAgents = installedAgents;
2706
+ targetAgents = ensureUniversalAgents(installedAgents);
2540
2707
  if (installedAgents.length === 1) {
2541
2708
  const firstAgent = installedAgents[0];
2542
2709
  M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
@@ -2580,7 +2747,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
2580
2747
  const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
2581
2748
  const summaryLines = [];
2582
2749
  targetAgents.map((a) => agents[a].displayName);
2583
- const shortCanonical = shortenPath$1(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
2750
+ const shortCanonical = shortenPath$2(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
2584
2751
  summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2585
2752
  summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2586
2753
  const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
@@ -2632,7 +2799,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
2632
2799
  const resultLines = [];
2633
2800
  const firstResult = successful[0];
2634
2801
  if (firstResult.canonicalPath) {
2635
- const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
2802
+ const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2636
2803
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2637
2804
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
2638
2805
  resultLines.push(...buildResultLines(successful, targetAgents));
@@ -2732,9 +2899,29 @@ async function runAdd(args, options = {}) {
2732
2899
  if (options.list) {
2733
2900
  console.log();
2734
2901
  M.step(import_picocolors.default.bold("Available Skills"));
2735
- for (const skill of skills) {
2736
- M.message(` ${import_picocolors.default.cyan(getSkillDisplayName(skill))}`);
2737
- M.message(` ${import_picocolors.default.dim(skill.description)}`);
2902
+ const groupedSkills = {};
2903
+ const ungroupedSkills = [];
2904
+ for (const skill of skills) if (skill.pluginName) {
2905
+ const group = skill.pluginName;
2906
+ if (!groupedSkills[group]) groupedSkills[group] = [];
2907
+ groupedSkills[group].push(skill);
2908
+ } else ungroupedSkills.push(skill);
2909
+ const sortedGroups = Object.keys(groupedSkills).sort();
2910
+ for (const group of sortedGroups) {
2911
+ const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2912
+ console.log(import_picocolors.default.bold(title));
2913
+ for (const skill of groupedSkills[group]) {
2914
+ M.message(` ${import_picocolors.default.cyan(getSkillDisplayName(skill))}`);
2915
+ M.message(` ${import_picocolors.default.dim(skill.description)}`);
2916
+ }
2917
+ console.log();
2918
+ }
2919
+ if (ungroupedSkills.length > 0) {
2920
+ if (sortedGroups.length > 0) console.log(import_picocolors.default.bold("General"));
2921
+ for (const skill of ungroupedSkills) {
2922
+ M.message(` ${import_picocolors.default.cyan(getSkillDisplayName(skill))}`);
2923
+ M.message(` ${import_picocolors.default.dim(skill.description)}`);
2924
+ }
2738
2925
  }
2739
2926
  console.log();
2740
2927
  Se("Use --skill <name> to install specific skills");
@@ -2764,9 +2951,34 @@ async function runAdd(args, options = {}) {
2764
2951
  selectedSkills = skills;
2765
2952
  M.info(`Installing all ${skills.length} skills`);
2766
2953
  } else {
2767
- const selected = await multiselect({
2954
+ const sortedSkills = [...skills].sort((a, b) => {
2955
+ if (a.pluginName && !b.pluginName) return -1;
2956
+ if (!a.pluginName && b.pluginName) return 1;
2957
+ if (a.pluginName && b.pluginName && a.pluginName !== b.pluginName) return a.pluginName.localeCompare(b.pluginName);
2958
+ return getSkillDisplayName(a).localeCompare(getSkillDisplayName(b));
2959
+ });
2960
+ const hasGroups = sortedSkills.some((s) => s.pluginName);
2961
+ let selected;
2962
+ if (hasGroups) {
2963
+ const kebabToTitle = (s) => s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2964
+ const grouped = {};
2965
+ for (const s of sortedSkills) {
2966
+ const groupName = s.pluginName ? kebabToTitle(s.pluginName) : "Other";
2967
+ if (!grouped[groupName]) grouped[groupName] = [];
2968
+ grouped[groupName].push({
2969
+ value: s,
2970
+ label: getSkillDisplayName(s),
2971
+ hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
2972
+ });
2973
+ }
2974
+ selected = await be({
2975
+ message: `Select skills to install ${import_picocolors.default.dim("(space to toggle)")}`,
2976
+ options: grouped,
2977
+ required: true
2978
+ });
2979
+ } else selected = await multiselect({
2768
2980
  message: "Select skills to install",
2769
- options: skills.map((s) => ({
2981
+ options: sortedSkills.map((s) => ({
2770
2982
  value: s,
2771
2983
  label: getSkillDisplayName(s),
2772
2984
  hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
@@ -2818,7 +3030,7 @@ async function runAdd(args, options = {}) {
2818
3030
  targetAgents = selected;
2819
3031
  }
2820
3032
  else if (installedAgents.length === 1 || options.yes) {
2821
- targetAgents = installedAgents;
3033
+ targetAgents = ensureUniversalAgents(installedAgents);
2822
3034
  if (installedAgents.length === 1) {
2823
3035
  const firstAgent = installedAgents[0];
2824
3036
  M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
@@ -2855,8 +3067,8 @@ async function runAdd(args, options = {}) {
2855
3067
  }
2856
3068
  installGlobally = scope;
2857
3069
  }
2858
- let installMode = "symlink";
2859
- if (!options.yes) {
3070
+ let installMode = options.copy ? "copy" : "symlink";
3071
+ if (!options.copy && !options.yes) {
2860
3072
  const modeChoice = await ve({
2861
3073
  message: "Installation method",
2862
3074
  options: [{
@@ -2889,14 +3101,37 @@ async function runAdd(args, options = {}) {
2889
3101
  if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
2890
3102
  overwriteStatus.get(skillName).set(agent, installed);
2891
3103
  }
2892
- for (const skill of selectedSkills) {
2893
- if (summaryLines.length > 0) summaryLines.push("");
2894
- const shortCanonical = shortenPath$1(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
2895
- summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2896
- summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2897
- const skillOverwrites = overwriteStatus.get(skill.name);
2898
- const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
2899
- if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
3104
+ const groupedSummary = {};
3105
+ const ungroupedSummary = [];
3106
+ for (const skill of selectedSkills) if (skill.pluginName) {
3107
+ const group = skill.pluginName;
3108
+ if (!groupedSummary[group]) groupedSummary[group] = [];
3109
+ groupedSummary[group].push(skill);
3110
+ } else ungroupedSummary.push(skill);
3111
+ const printSkillSummary = (skills) => {
3112
+ for (const skill of skills) {
3113
+ if (summaryLines.length > 0) summaryLines.push("");
3114
+ const shortCanonical = shortenPath$2(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
3115
+ summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
3116
+ summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
3117
+ const skillOverwrites = overwriteStatus.get(skill.name);
3118
+ const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
3119
+ if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
3120
+ }
3121
+ };
3122
+ const sortedGroups = Object.keys(groupedSummary).sort();
3123
+ for (const group of sortedGroups) {
3124
+ const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3125
+ summaryLines.push("");
3126
+ summaryLines.push(import_picocolors.default.bold(title));
3127
+ printSkillSummary(groupedSummary[group]);
3128
+ }
3129
+ if (ungroupedSummary.length > 0) {
3130
+ if (sortedGroups.length > 0) {
3131
+ summaryLines.push("");
3132
+ summaryLines.push(import_picocolors.default.bold("General"));
3133
+ }
3134
+ printSkillSummary(ungroupedSummary);
2900
3135
  }
2901
3136
  console.log();
2902
3137
  Me(summaryLines.join("\n"), "Installation Summary");
@@ -2928,6 +3163,7 @@ async function runAdd(args, options = {}) {
2928
3163
  results.push({
2929
3164
  skill: getSkillDisplayName(skill),
2930
3165
  agent: agents[agent].displayName,
3166
+ pluginName: skill.pluginName,
2931
3167
  ...result
2932
3168
  });
2933
3169
  }
@@ -2980,37 +3216,76 @@ async function runAdd(args, options = {}) {
2980
3216
  sourceType: parsed.type,
2981
3217
  sourceUrl: parsed.url,
2982
3218
  skillPath: skillPathValue,
2983
- skillFolderHash
3219
+ skillFolderHash,
3220
+ pluginName: skill.pluginName
2984
3221
  });
2985
3222
  } catch {}
2986
3223
  }
2987
3224
  }
3225
+ if (successful.length > 0 && !installGlobally) {
3226
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
3227
+ for (const skill of selectedSkills) {
3228
+ const skillDisplayName = getSkillDisplayName(skill);
3229
+ if (successfulSkillNames.has(skillDisplayName)) try {
3230
+ const computedHash = await computeSkillFolderHash(skill.path);
3231
+ await addSkillToLocalLock(skill.name, {
3232
+ source: normalizedSource || parsed.url,
3233
+ sourceType: parsed.type,
3234
+ computedHash
3235
+ }, cwd);
3236
+ } catch {}
3237
+ }
3238
+ }
2988
3239
  if (successful.length > 0) {
2989
3240
  const bySkill = /* @__PURE__ */ new Map();
3241
+ const groupedResults = {};
3242
+ const ungroupedResults = [];
2990
3243
  for (const r of successful) {
2991
3244
  const skillResults = bySkill.get(r.skill) || [];
2992
3245
  skillResults.push(r);
2993
3246
  bySkill.set(r.skill, skillResults);
3247
+ if (skillResults.length === 1) if (r.pluginName) {
3248
+ const group = r.pluginName;
3249
+ if (!groupedResults[group]) groupedResults[group] = [];
3250
+ groupedResults[group].push(r);
3251
+ } else ungroupedResults.push(r);
2994
3252
  }
2995
3253
  const skillCount = bySkill.size;
2996
3254
  const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2997
3255
  const copiedAgents = symlinkFailures.map((r) => r.agent);
2998
3256
  const resultLines = [];
2999
- for (const [skillName, skillResults] of bySkill) {
3000
- const firstResult = skillResults[0];
3001
- if (firstResult.mode === "copy") {
3002
- resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
3003
- for (const r of skillResults) {
3004
- const shortPath = shortenPath$1(r.path, cwd);
3005
- resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
3257
+ const printSkillResults = (entries) => {
3258
+ for (const entry of entries) {
3259
+ const skillResults = bySkill.get(entry.skill) || [];
3260
+ const firstResult = skillResults[0];
3261
+ if (firstResult.mode === "copy") {
3262
+ resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill} ${import_picocolors.default.dim("(copied)")}`);
3263
+ for (const r of skillResults) {
3264
+ const shortPath = shortenPath$2(r.path, cwd);
3265
+ resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
3266
+ }
3267
+ } else {
3268
+ if (firstResult.canonicalPath) {
3269
+ const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
3270
+ resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
3271
+ } else resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill}`);
3272
+ resultLines.push(...buildResultLines(skillResults, targetAgents));
3006
3273
  }
3007
- } else {
3008
- if (firstResult.canonicalPath) {
3009
- const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
3010
- resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
3011
- } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
3012
- resultLines.push(...buildResultLines(skillResults, targetAgents));
3013
3274
  }
3275
+ };
3276
+ const sortedResultGroups = Object.keys(groupedResults).sort();
3277
+ for (const group of sortedResultGroups) {
3278
+ const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3279
+ resultLines.push("");
3280
+ resultLines.push(import_picocolors.default.bold(title));
3281
+ printSkillResults(groupedResults[group]);
3282
+ }
3283
+ if (ungroupedResults.length > 0) {
3284
+ if (sortedResultGroups.length > 0) {
3285
+ resultLines.push("");
3286
+ resultLines.push(import_picocolors.default.bold("General"));
3287
+ }
3288
+ printSkillResults(ungroupedResults);
3014
3289
  }
3015
3290
  const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
3016
3291
  Me(resultLines.join("\n"), title);
@@ -3113,6 +3388,7 @@ function parseAddOptions(args) {
3113
3388
  }
3114
3389
  i--;
3115
3390
  } else if (arg === "--full-depth") options.fullDepth = true;
3391
+ else if (arg === "--copy") options.copy = true;
3116
3392
  else if (arg && !arg.startsWith("-")) source.push(arg);
3117
3393
  }
3118
3394
  return {
@@ -3342,6 +3618,340 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
3342
3618
  else console.log(`${DIM$2}Discover more skills at${RESET$2} ${TEXT$1}https://skills.sh${RESET$2}`);
3343
3619
  console.log();
3344
3620
  }
3621
+ const isCancelled = (value) => typeof value === "symbol";
3622
+ function shortenPath$1(fullPath, cwd) {
3623
+ const home = homedir();
3624
+ if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
3625
+ if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
3626
+ return fullPath;
3627
+ }
3628
+ async function discoverNodeModuleSkills(cwd) {
3629
+ const nodeModulesDir = join(cwd, "node_modules");
3630
+ const skills = [];
3631
+ let topNames;
3632
+ try {
3633
+ topNames = await readdir(nodeModulesDir);
3634
+ } catch {
3635
+ return skills;
3636
+ }
3637
+ const processPackageDir = async (pkgDir, packageName) => {
3638
+ const rootSkill = await parseSkillMd(join(pkgDir, "SKILL.md"));
3639
+ if (rootSkill) {
3640
+ skills.push({
3641
+ ...rootSkill,
3642
+ packageName
3643
+ });
3644
+ return;
3645
+ }
3646
+ const searchDirs = [
3647
+ pkgDir,
3648
+ join(pkgDir, "skills"),
3649
+ join(pkgDir, ".agents", "skills")
3650
+ ];
3651
+ for (const searchDir of searchDirs) try {
3652
+ const entries = await readdir(searchDir);
3653
+ for (const name of entries) {
3654
+ const skillDir = join(searchDir, name);
3655
+ try {
3656
+ if (!(await stat(skillDir)).isDirectory()) continue;
3657
+ } catch {
3658
+ continue;
3659
+ }
3660
+ const skill = await parseSkillMd(join(skillDir, "SKILL.md"));
3661
+ if (skill) skills.push({
3662
+ ...skill,
3663
+ packageName
3664
+ });
3665
+ }
3666
+ } catch {}
3667
+ };
3668
+ await Promise.all(topNames.map(async (name) => {
3669
+ if (name.startsWith(".")) return;
3670
+ const fullPath = join(nodeModulesDir, name);
3671
+ try {
3672
+ if (!(await stat(fullPath)).isDirectory()) return;
3673
+ } catch {
3674
+ return;
3675
+ }
3676
+ if (name.startsWith("@")) try {
3677
+ const scopeNames = await readdir(fullPath);
3678
+ await Promise.all(scopeNames.map(async (scopedName) => {
3679
+ const scopedPath = join(fullPath, scopedName);
3680
+ try {
3681
+ if (!(await stat(scopedPath)).isDirectory()) return;
3682
+ } catch {
3683
+ return;
3684
+ }
3685
+ await processPackageDir(scopedPath, `${name}/${scopedName}`);
3686
+ }));
3687
+ } catch {}
3688
+ else await processPackageDir(fullPath, name);
3689
+ }));
3690
+ return skills;
3691
+ }
3692
+ async function runSync(args, options = {}) {
3693
+ const cwd = process.cwd();
3694
+ console.log();
3695
+ Ie(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills experimental_sync ")));
3696
+ const spinner = Y();
3697
+ spinner.start("Scanning node_modules for skills...");
3698
+ const discoveredSkills = await discoverNodeModuleSkills(cwd);
3699
+ if (discoveredSkills.length === 0) {
3700
+ spinner.stop(import_picocolors.default.yellow("No skills found"));
3701
+ Se(import_picocolors.default.dim("No SKILL.md files found in node_modules."));
3702
+ return;
3703
+ }
3704
+ spinner.stop(`Found ${import_picocolors.default.green(String(discoveredSkills.length))} skill${discoveredSkills.length > 1 ? "s" : ""} in node_modules`);
3705
+ for (const skill of discoveredSkills) {
3706
+ M.info(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`from ${skill.packageName}`)}`);
3707
+ if (skill.description) M.message(import_picocolors.default.dim(` ${skill.description}`));
3708
+ }
3709
+ const localLock = await readLocalLock(cwd);
3710
+ const toInstall = [];
3711
+ const upToDate = [];
3712
+ if (options.force) {
3713
+ toInstall.push(...discoveredSkills);
3714
+ M.info(import_picocolors.default.dim("Force mode: reinstalling all skills"));
3715
+ } else {
3716
+ for (const skill of discoveredSkills) {
3717
+ const existingEntry = localLock.skills[skill.name];
3718
+ if (existingEntry) {
3719
+ if (await computeSkillFolderHash(skill.path) === existingEntry.computedHash) {
3720
+ upToDate.push(skill.name);
3721
+ continue;
3722
+ }
3723
+ }
3724
+ toInstall.push(skill);
3725
+ }
3726
+ if (upToDate.length > 0) M.info(import_picocolors.default.dim(`${upToDate.length} skill${upToDate.length !== 1 ? "s" : ""} already up to date`));
3727
+ if (toInstall.length === 0) {
3728
+ console.log();
3729
+ Se(import_picocolors.default.green("All skills are up to date."));
3730
+ return;
3731
+ }
3732
+ }
3733
+ M.info(`${toInstall.length} skill${toInstall.length !== 1 ? "s" : ""} to install/update`);
3734
+ let targetAgents;
3735
+ const validAgents = Object.keys(agents);
3736
+ const universalAgents = getUniversalAgents();
3737
+ if (options.agent?.includes("*")) {
3738
+ targetAgents = validAgents;
3739
+ M.info(`Installing to all ${targetAgents.length} agents`);
3740
+ } else if (options.agent && options.agent.length > 0) {
3741
+ const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
3742
+ if (invalidAgents.length > 0) {
3743
+ M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
3744
+ M.info(`Valid agents: ${validAgents.join(", ")}`);
3745
+ process.exit(1);
3746
+ }
3747
+ targetAgents = options.agent;
3748
+ } else {
3749
+ spinner.start("Loading agents...");
3750
+ const installedAgents = await detectInstalledAgents();
3751
+ const totalAgents = Object.keys(agents).length;
3752
+ spinner.stop(`${totalAgents} agents`);
3753
+ if (installedAgents.length === 0) if (options.yes) {
3754
+ targetAgents = universalAgents;
3755
+ M.info("Installing to universal agents");
3756
+ } else {
3757
+ const selected = await searchMultiselect({
3758
+ message: "Which agents do you want to install to?",
3759
+ items: getNonUniversalAgents().map((a) => ({
3760
+ value: a,
3761
+ label: agents[a].displayName,
3762
+ hint: agents[a].skillsDir
3763
+ })),
3764
+ initialSelected: [],
3765
+ lockedSection: {
3766
+ title: "Universal (.agents/skills)",
3767
+ items: universalAgents.map((a) => ({
3768
+ value: a,
3769
+ label: agents[a].displayName
3770
+ }))
3771
+ }
3772
+ });
3773
+ if (isCancelled(selected)) {
3774
+ xe("Sync cancelled");
3775
+ process.exit(0);
3776
+ }
3777
+ targetAgents = selected;
3778
+ }
3779
+ else if (installedAgents.length === 1 || options.yes) {
3780
+ targetAgents = [...installedAgents];
3781
+ for (const ua of universalAgents) if (!targetAgents.includes(ua)) targetAgents.push(ua);
3782
+ } else {
3783
+ const selected = await searchMultiselect({
3784
+ message: "Which agents do you want to install to?",
3785
+ items: getNonUniversalAgents().filter((a) => installedAgents.includes(a)).map((a) => ({
3786
+ value: a,
3787
+ label: agents[a].displayName,
3788
+ hint: agents[a].skillsDir
3789
+ })),
3790
+ initialSelected: installedAgents.filter((a) => !universalAgents.includes(a)),
3791
+ lockedSection: {
3792
+ title: "Universal (.agents/skills)",
3793
+ items: universalAgents.map((a) => ({
3794
+ value: a,
3795
+ label: agents[a].displayName
3796
+ }))
3797
+ }
3798
+ });
3799
+ if (isCancelled(selected)) {
3800
+ xe("Sync cancelled");
3801
+ process.exit(0);
3802
+ }
3803
+ targetAgents = selected;
3804
+ }
3805
+ }
3806
+ const summaryLines = [];
3807
+ for (const skill of toInstall) {
3808
+ const shortCanonical = shortenPath$1(getCanonicalPath(skill.name, { global: false }), cwd);
3809
+ summaryLines.push(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`← ${skill.packageName}`)}`);
3810
+ summaryLines.push(` ${import_picocolors.default.dim(shortCanonical)}`);
3811
+ }
3812
+ console.log();
3813
+ Me(summaryLines.join("\n"), "Sync Summary");
3814
+ if (!options.yes) {
3815
+ const confirmed = await ye({ message: "Proceed with sync?" });
3816
+ if (pD(confirmed) || !confirmed) {
3817
+ xe("Sync cancelled");
3818
+ process.exit(0);
3819
+ }
3820
+ }
3821
+ spinner.start("Syncing skills...");
3822
+ const results = [];
3823
+ for (const skill of toInstall) for (const agent of targetAgents) {
3824
+ const result = await installSkillForAgent(skill, agent, {
3825
+ global: false,
3826
+ cwd,
3827
+ mode: "symlink"
3828
+ });
3829
+ results.push({
3830
+ skill: skill.name,
3831
+ packageName: skill.packageName,
3832
+ agent: agents[agent].displayName,
3833
+ success: result.success,
3834
+ path: result.path,
3835
+ canonicalPath: result.canonicalPath,
3836
+ error: result.error
3837
+ });
3838
+ }
3839
+ spinner.stop("Sync complete");
3840
+ const successful = results.filter((r) => r.success);
3841
+ const failed = results.filter((r) => !r.success);
3842
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
3843
+ for (const skill of toInstall) if (successfulSkillNames.has(skill.name)) try {
3844
+ const computedHash = await computeSkillFolderHash(skill.path);
3845
+ await addSkillToLocalLock(skill.name, {
3846
+ source: skill.packageName,
3847
+ sourceType: "node_modules",
3848
+ computedHash
3849
+ }, cwd);
3850
+ } catch {}
3851
+ console.log();
3852
+ if (successful.length > 0) {
3853
+ const bySkill = /* @__PURE__ */ new Map();
3854
+ for (const r of successful) {
3855
+ const skillResults = bySkill.get(r.skill) || [];
3856
+ skillResults.push(r);
3857
+ bySkill.set(r.skill, skillResults);
3858
+ }
3859
+ const resultLines = [];
3860
+ for (const [skillName, skillResults] of bySkill) {
3861
+ const firstResult = skillResults[0];
3862
+ const pkg = toInstall.find((s) => s.name === skillName)?.packageName;
3863
+ if (firstResult.canonicalPath) {
3864
+ const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
3865
+ resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim(`← ${pkg}`)}`);
3866
+ resultLines.push(` ${import_picocolors.default.dim(shortPath)}`);
3867
+ } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim(`← ${pkg}`)}`);
3868
+ }
3869
+ const skillCount = bySkill.size;
3870
+ const title = import_picocolors.default.green(`Synced ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
3871
+ Me(resultLines.join("\n"), title);
3872
+ }
3873
+ if (failed.length > 0) {
3874
+ console.log();
3875
+ M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
3876
+ for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
3877
+ }
3878
+ track({
3879
+ event: "experimental_sync",
3880
+ skillCount: String(toInstall.length),
3881
+ successCount: String(successfulSkillNames.size),
3882
+ agents: targetAgents.join(",")
3883
+ });
3884
+ console.log();
3885
+ Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
3886
+ }
3887
+ function parseSyncOptions(args) {
3888
+ const options = {};
3889
+ for (let i = 0; i < args.length; i++) {
3890
+ const arg = args[i];
3891
+ if (arg === "-y" || arg === "--yes") options.yes = true;
3892
+ else if (arg === "-f" || arg === "--force") options.force = true;
3893
+ else if (arg === "-a" || arg === "--agent") {
3894
+ options.agent = options.agent || [];
3895
+ i++;
3896
+ let nextArg = args[i];
3897
+ while (i < args.length && nextArg && !nextArg.startsWith("-")) {
3898
+ options.agent.push(nextArg);
3899
+ i++;
3900
+ nextArg = args[i];
3901
+ }
3902
+ i--;
3903
+ }
3904
+ }
3905
+ return { options };
3906
+ }
3907
+ async function runInstallFromLock(args) {
3908
+ const lock = await readLocalLock(process.cwd());
3909
+ const skillEntries = Object.entries(lock.skills);
3910
+ if (skillEntries.length === 0) {
3911
+ M.warn("No project skills found in skills-lock.json");
3912
+ M.info(`Add project-level skills with ${import_picocolors.default.cyan("npx skills add <package>")} (without ${import_picocolors.default.cyan("-g")})`);
3913
+ return;
3914
+ }
3915
+ const universalAgentNames = getUniversalAgents();
3916
+ const nodeModuleSkills = [];
3917
+ const bySource = /* @__PURE__ */ new Map();
3918
+ for (const [skillName, entry] of skillEntries) {
3919
+ if (entry.sourceType === "node_modules") {
3920
+ nodeModuleSkills.push(skillName);
3921
+ continue;
3922
+ }
3923
+ const existing = bySource.get(entry.source);
3924
+ if (existing) existing.skills.push(skillName);
3925
+ else bySource.set(entry.source, {
3926
+ sourceType: entry.sourceType,
3927
+ skills: [skillName]
3928
+ });
3929
+ }
3930
+ const remoteCount = skillEntries.length - nodeModuleSkills.length;
3931
+ if (remoteCount > 0) M.info(`Restoring ${import_picocolors.default.cyan(String(remoteCount))} skill${remoteCount !== 1 ? "s" : ""} from skills-lock.json into ${import_picocolors.default.dim(".agents/skills/")}`);
3932
+ for (const [source, { skills }] of bySource) try {
3933
+ await runAdd([source], {
3934
+ skill: skills,
3935
+ agent: universalAgentNames,
3936
+ yes: true
3937
+ });
3938
+ } catch (error) {
3939
+ M.error(`Failed to install from ${import_picocolors.default.cyan(source)}: ${error instanceof Error ? error.message : "Unknown error"}`);
3940
+ }
3941
+ if (nodeModuleSkills.length > 0) {
3942
+ M.info(`${import_picocolors.default.cyan(String(nodeModuleSkills.length))} skill${nodeModuleSkills.length !== 1 ? "s" : ""} from node_modules`);
3943
+ try {
3944
+ const { options: syncOptions } = parseSyncOptions(args);
3945
+ await runSync(args, {
3946
+ ...syncOptions,
3947
+ yes: true,
3948
+ agent: universalAgentNames
3949
+ });
3950
+ } catch (error) {
3951
+ M.error(`Failed to sync node_modules skills: ${error instanceof Error ? error.message : "Unknown error"}`);
3952
+ }
3953
+ }
3954
+ }
3345
3955
  const RESET$1 = "\x1B[0m";
3346
3956
  const BOLD$1 = "\x1B[1m";
3347
3957
  const DIM$1 = "\x1B[38;5;102m";
@@ -3389,6 +3999,7 @@ async function runList(args) {
3389
3999
  global: scope,
3390
4000
  agentFilter
3391
4001
  });
4002
+ const lockedSkills = await getAllLockedSkills();
3392
4003
  const cwd = process.cwd();
3393
4004
  const scopeLabel = scope ? "Global" : "Project";
3394
4005
  if (installedSkills.length === 0) {
@@ -3397,17 +4008,44 @@ async function runList(args) {
3397
4008
  else console.log(`${DIM$1}Try listing global skills with -g${RESET$1}`);
3398
4009
  return;
3399
4010
  }
3400
- function printSkill(skill) {
4011
+ function printSkill(skill, indent = false) {
4012
+ const prefix = indent ? " " : "";
3401
4013
  const shortPath = shortenPath(skill.canonicalPath, cwd);
3402
4014
  const agentNames = skill.agents.map((a) => agents[a].displayName);
3403
4015
  const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$1}`;
3404
- console.log(`${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
3405
- console.log(` ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
4016
+ console.log(`${prefix}${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
4017
+ console.log(`${prefix} ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
3406
4018
  }
3407
4019
  console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
3408
4020
  console.log();
3409
- for (const skill of installedSkills) printSkill(skill);
3410
- console.log();
4021
+ const groupedSkills = {};
4022
+ const ungroupedSkills = [];
4023
+ for (const skill of installedSkills) {
4024
+ const lockEntry = lockedSkills[skill.name];
4025
+ if (lockEntry?.pluginName) {
4026
+ const group = lockEntry.pluginName;
4027
+ if (!groupedSkills[group]) groupedSkills[group] = [];
4028
+ groupedSkills[group].push(skill);
4029
+ } else ungroupedSkills.push(skill);
4030
+ }
4031
+ if (Object.keys(groupedSkills).length > 0) {
4032
+ const sortedGroups = Object.keys(groupedSkills).sort();
4033
+ for (const group of sortedGroups) {
4034
+ const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4035
+ console.log(`${BOLD$1}${title}${RESET$1}`);
4036
+ const skills = groupedSkills[group];
4037
+ if (skills) for (const skill of skills) printSkill(skill, true);
4038
+ console.log();
4039
+ }
4040
+ if (ungroupedSkills.length > 0) {
4041
+ console.log(`${BOLD$1}General${RESET$1}`);
4042
+ for (const skill of ungroupedSkills) printSkill(skill, true);
4043
+ console.log();
4044
+ }
4045
+ } else {
4046
+ for (const skill of installedSkills) printSkill(skill);
4047
+ console.log();
4048
+ }
3411
4049
  }
3412
4050
  async function removeCommand(skillNames, options) {
3413
4051
  const isGlobal = options.global ?? false;
@@ -3472,10 +4110,8 @@ async function removeCommand(skillNames, options) {
3472
4110
  let targetAgents;
3473
4111
  if (options.agent && options.agent.length > 0) targetAgents = options.agent;
3474
4112
  else {
3475
- spinner.start("Detecting installed agents...");
3476
- targetAgents = await detectInstalledAgents();
3477
- if (targetAgents.length === 0) targetAgents = Object.keys(agents);
3478
- spinner.stop(`Targeting ${targetAgents.length} installed agent(s)`);
4113
+ targetAgents = Object.keys(agents);
4114
+ spinner.stop(`Targeting ${targetAgents.length} potential agent(s)`);
3479
4115
  }
3480
4116
  if (!options.yes) {
3481
4117
  console.log();
@@ -3491,25 +4127,42 @@ async function removeCommand(skillNames, options) {
3491
4127
  spinner.start("Removing skills...");
3492
4128
  const results = [];
3493
4129
  for (const skillName of selectedSkills) try {
4130
+ const canonicalPath = getCanonicalPath(skillName, {
4131
+ global: isGlobal,
4132
+ cwd
4133
+ });
3494
4134
  for (const agentKey of targetAgents) {
3495
4135
  const agent = agents[agentKey];
3496
4136
  const skillPath = getInstallPath(skillName, agentKey, {
3497
4137
  global: isGlobal,
3498
4138
  cwd
3499
4139
  });
3500
- try {
3501
- if (await lstat(skillPath).catch(() => null)) await rm(skillPath, {
3502
- recursive: true,
3503
- force: true
3504
- });
3505
- } catch (err) {
3506
- M.warn(`Could not remove skill from ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`);
4140
+ const pathsToCleanup = new Set([skillPath]);
4141
+ const sanitizedName = sanitizeName(skillName);
4142
+ if (isGlobal && agent.globalSkillsDir) pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
4143
+ else pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
4144
+ for (const pathToCleanup of pathsToCleanup) {
4145
+ if (pathToCleanup === canonicalPath) continue;
4146
+ try {
4147
+ if (await lstat(pathToCleanup).catch(() => null)) await rm(pathToCleanup, {
4148
+ recursive: true,
4149
+ force: true
4150
+ });
4151
+ } catch (err) {
4152
+ M.warn(`Could not remove skill from ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`);
4153
+ }
3507
4154
  }
3508
4155
  }
3509
- await rm(getCanonicalPath(skillName, {
4156
+ const remainingAgents = (await detectInstalledAgents()).filter((a) => !targetAgents.includes(a));
4157
+ let isStillUsed = false;
4158
+ for (const agentKey of remainingAgents) if (await lstat(getInstallPath(skillName, agentKey, {
3510
4159
  global: isGlobal,
3511
4160
  cwd
3512
- }), {
4161
+ })).catch(() => null)) {
4162
+ isStillUsed = true;
4163
+ break;
4164
+ }
4165
+ if (!isStillUsed) await rm(canonicalPath, {
3513
4166
  recursive: true,
3514
4167
  force: true
3515
4168
  });
@@ -3626,13 +4279,17 @@ function showBanner() {
3626
4279
  console.log();
3627
4280
  console.log(`${DIM}The open agent skills ecosystem${RESET}`);
3628
4281
  console.log();
3629
- console.log(` ${DIM}$${RESET} ${TEXT}npx skills add ${DIM}<package>${RESET} ${DIM}Install a skill${RESET}`);
3630
- console.log(` ${DIM}$${RESET} ${TEXT}npx skills list${RESET} ${DIM}List installed skills${RESET}`);
3631
- console.log(` ${DIM}$${RESET} ${TEXT}npx skills find ${DIM}[query]${RESET} ${DIM}Search for skills${RESET}`);
3632
- console.log(` ${DIM}$${RESET} ${TEXT}npx skills check${RESET} ${DIM}Check for updates${RESET}`);
3633
- console.log(` ${DIM}$${RESET} ${TEXT}npx skills update${RESET} ${DIM}Update all skills${RESET}`);
3634
- console.log(` ${DIM}$${RESET} ${TEXT}npx skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
3635
- console.log(` ${DIM}$${RESET} ${TEXT}npx skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
4282
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills add ${DIM}<package>${RESET} ${DIM}Add a new skill${RESET}`);
4283
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
4284
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills list${RESET} ${DIM}List installed skills${RESET}`);
4285
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills find ${DIM}[query]${RESET} ${DIM}Search for skills${RESET}`);
4286
+ console.log();
4287
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills check${RESET} ${DIM}Check for updates${RESET}`);
4288
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills update${RESET} ${DIM}Update all skills${RESET}`);
4289
+ console.log();
4290
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
4291
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
4292
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
3636
4293
  console.log();
3637
4294
  console.log(`${DIM}try:${RESET} npx skills add vercel-labs/agent-skills`);
3638
4295
  console.log();
@@ -3643,16 +4300,22 @@ function showHelp() {
3643
4300
  console.log(`
3644
4301
  ${BOLD}Usage:${RESET} skills <command> [options]
3645
4302
 
3646
- ${BOLD}Commands:${RESET}
3647
- add <package> Add a skill package
3648
- e.g. vercel-labs/agent-skills
3649
- https://github.com/vercel-labs/agent-skills
3650
- remove [skills] Remove installed skills
3651
- list, ls List installed skills
3652
- find [query] Search for skills interactively
3653
- init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
3654
- check Check for available skill updates
3655
- update Update all skills to latest versions
4303
+ ${BOLD}Manage Skills:${RESET}
4304
+ add <package> Add a skill package (alias: a)
4305
+ e.g. vercel-labs/agent-skills
4306
+ https://github.com/vercel-labs/agent-skills
4307
+ remove [skills] Remove installed skills
4308
+ list, ls List installed skills
4309
+ find [query] Search for skills interactively
4310
+
4311
+ ${BOLD}Updates:${RESET}
4312
+ check Check for available skill updates
4313
+ update Update all skills to latest versions
4314
+
4315
+ ${BOLD}Project:${RESET}
4316
+ experimental_install Restore skills from skills-lock.json
4317
+ init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
4318
+ experimental_sync Sync skills from node_modules into agent directories
3656
4319
 
3657
4320
  ${BOLD}Add Options:${RESET}
3658
4321
  -g, --global Install skill globally (user-level) instead of project-level
@@ -3660,6 +4323,7 @@ ${BOLD}Add Options:${RESET}
3660
4323
  -s, --skill <skills> Specify skill names to install (use '*' for all skills)
3661
4324
  -l, --list List available skills in the repository without installing
3662
4325
  -y, --yes Skip confirmation prompts
4326
+ --copy Copy files instead of symlinking to agent directories
3663
4327
  --all Shorthand for --skill '*' --agent '*' -y
3664
4328
  --full-depth Search all subdirectories even when a root SKILL.md exists
3665
4329
 
@@ -3670,6 +4334,10 @@ ${BOLD}Remove Options:${RESET}
3670
4334
  -y, --yes Skip confirmation prompts
3671
4335
  --all Shorthand for --skill '*' --agent '*' -y
3672
4336
 
4337
+ ${BOLD}Experimental Sync Options:${RESET}
4338
+ -a, --agent <agents> Specify agents to install to (use '*' for all agents)
4339
+ -y, --yes Skip confirmation prompts
4340
+
3673
4341
  ${BOLD}List Options:${RESET}
3674
4342
  -g, --global List global skills (default: project)
3675
4343
  -a, --agent <agents> Filter by specific agents
@@ -3683,17 +4351,20 @@ ${BOLD}Examples:${RESET}
3683
4351
  ${DIM}$${RESET} skills add vercel-labs/agent-skills -g
3684
4352
  ${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
3685
4353
  ${DIM}$${RESET} skills add vercel-labs/agent-skills --skill pr-review commit
3686
- ${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
3687
- ${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
4354
+ ${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
4355
+ ${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
3688
4356
  ${DIM}$${RESET} skills rm --global frontend-design
3689
- ${DIM}$${RESET} skills list ${DIM}# list all installed skills${RESET}
3690
- ${DIM}$${RESET} skills ls -g ${DIM}# list global skills only${RESET}
3691
- ${DIM}$${RESET} skills ls -a claude-code ${DIM}# filter by agent${RESET}
3692
- ${DIM}$${RESET} skills find ${DIM}# interactive search${RESET}
3693
- ${DIM}$${RESET} skills find typescript ${DIM}# search by keyword${RESET}
3694
- ${DIM}$${RESET} skills init my-skill
4357
+ ${DIM}$${RESET} skills list ${DIM}# list project skills${RESET}
4358
+ ${DIM}$${RESET} skills ls -g ${DIM}# list global skills${RESET}
4359
+ ${DIM}$${RESET} skills ls -a claude-code ${DIM}# filter by agent${RESET}
4360
+ ${DIM}$${RESET} skills find ${DIM}# interactive search${RESET}
4361
+ ${DIM}$${RESET} skills find typescript ${DIM}# search by keyword${RESET}
3695
4362
  ${DIM}$${RESET} skills check
3696
4363
  ${DIM}$${RESET} skills update
4364
+ ${DIM}$${RESET} skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
4365
+ ${DIM}$${RESET} skills init my-skill
4366
+ ${DIM}$${RESET} skills experimental_sync ${DIM}# sync from node_modules${RESET}
4367
+ ${DIM}$${RESET} skills experimental_sync -y ${DIM}# sync without prompts${RESET}
3697
4368
 
3698
4369
  Discover more skills at ${TEXT}https://skills.sh/${RESET}
3699
4370
  `);
@@ -3984,13 +4655,17 @@ async function main() {
3984
4655
  console.log();
3985
4656
  runInit(restArgs);
3986
4657
  break;
4658
+ case "experimental_install":
4659
+ showLogo();
4660
+ await runInstallFromLock(restArgs);
4661
+ break;
3987
4662
  case "i":
3988
4663
  case "install":
3989
4664
  case "a":
3990
4665
  case "add": {
3991
4666
  showLogo();
3992
- const { source, options } = parseAddOptions(restArgs);
3993
- await runAdd(source, options);
4667
+ const { source: addSource, options: addOpts } = parseAddOptions(restArgs);
4668
+ await runAdd(addSource, addOpts);
3994
4669
  break;
3995
4670
  }
3996
4671
  case "remove":
@@ -4003,6 +4678,12 @@ async function main() {
4003
4678
  const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
4004
4679
  await removeCommand(skills, removeOptions);
4005
4680
  break;
4681
+ case "experimental_sync": {
4682
+ showLogo();
4683
+ const { options: syncOptions } = parseSyncOptions(restArgs);
4684
+ await runSync(restArgs, syncOptions);
4685
+ break;
4686
+ }
4006
4687
  case "list":
4007
4688
  case "ls":
4008
4689
  await runList(restArgs);