bip-skills 1.4.4 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.mjs +576 -290
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -11,7 +11,7 @@ import "./_chunks/libs/esprima.mjs";
11
11
  import { t as xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
12
12
  import { execSync, spawn, spawnSync } from "child_process";
13
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
14
- import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
14
+ import { basename, dirname, isAbsolute, join, normalize, posix, relative, resolve, sep } from "path";
15
15
  import { homedir, platform, tmpdir } from "os";
16
16
  import { createHash } from "crypto";
17
17
  import { fileURLToPath } from "url";
@@ -30,9 +30,16 @@ const DEFAULT_SEARCH_API_BASE = DEFAULT_WELL_KNOWN_BASE_URL;
30
30
  const DEFAULT_GIT_HOST = "git.yonyou.com";
31
31
  const DEFAULT_GIT_BASE_URL = `https://${DEFAULT_GIT_HOST}`;
32
32
  const DEFAULT_GIT_FALLBACK_SSH_HOST = "git.yyrd.com";
33
+ const DEFAULT_INSTALL_REPORT_URL = "https://sun.yyuap.com/api/install-report";
33
34
  function isYyuapHost(hostname) {
34
35
  return hostname === "yyuap.com" || hostname.endsWith(".yyuap.com");
35
36
  }
37
+ function isYyrdHost(hostname) {
38
+ return hostname === "yyrd.com" || hostname.endsWith(".yyrd.com");
39
+ }
40
+ function isInternalSkillSourceHost(hostname) {
41
+ return isYyuapHost(hostname) || isYyrdHost(hostname);
42
+ }
36
43
  function isAllowedReportingUrl(url) {
37
44
  try {
38
45
  return isYyuapHost(new URL(url).hostname);
@@ -74,6 +81,20 @@ async function isRepoPrivate(owner, repo) {
74
81
  return null;
75
82
  }
76
83
  }
84
+ function extractSourceHostname(sourceUrl) {
85
+ if (sourceUrl.startsWith("http://") || sourceUrl.startsWith("https://") || sourceUrl.startsWith("ssh://") || sourceUrl.startsWith("git://")) try {
86
+ return new URL(sourceUrl).hostname;
87
+ } catch {
88
+ return null;
89
+ }
90
+ const scpLikeMatch = sourceUrl.match(/^(?:[^@]+@)?([^:/]+):.+$/);
91
+ if (scpLikeMatch) return scpLikeMatch[1] ?? null;
92
+ return null;
93
+ }
94
+ function isInternalSkillSourceUrl(sourceUrl) {
95
+ const hostname = extractSourceHostname(sourceUrl);
96
+ return hostname ? isInternalSkillSourceHost(hostname) : false;
97
+ }
77
98
  function isLocalPath(input) {
78
99
  return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
79
100
  }
@@ -375,12 +396,9 @@ var GitCloneError = class extends Error {
375
396
  };
376
397
  async function cloneRepo(url, ref) {
377
398
  const tempDir = await mkdtemp(join(tmpdir(), "skills-"));
378
- const git = esm_default({
379
- timeout: { block: CLONE_TIMEOUT_MS },
380
- env: {
381
- ...process.env,
382
- GIT_TERMINAL_PROMPT: "0"
383
- }
399
+ const git = esm_default({ timeout: { block: CLONE_TIMEOUT_MS } }).env({
400
+ ...process.env,
401
+ GIT_TERMINAL_PROMPT: "0"
384
402
  });
385
403
  const cloneOptions = ref ? [
386
404
  "--depth",
@@ -1400,6 +1418,7 @@ function getAllowedReportingUrl(envVarName) {
1400
1418
  }
1401
1419
  const TELEMETRY_URL = getAllowedReportingUrl("SKILLS_TELEMETRY_URL");
1402
1420
  const AUDIT_URL = getAllowedReportingUrl("SKILLS_AUDIT_URL");
1421
+ const INSTALL_REPORT_URL = isAllowedReportingUrl(DEFAULT_INSTALL_REPORT_URL) ? DEFAULT_INSTALL_REPORT_URL : null;
1403
1422
  let cliVersion = null;
1404
1423
  function isCI() {
1405
1424
  return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.BUILDKITE || process.env.JENKINS_URL || process.env.TEAMCITY_VERSION);
@@ -1428,17 +1447,24 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
1428
1447
  return null;
1429
1448
  }
1430
1449
  }
1431
- function track(data) {
1432
- if (!TELEMETRY_URL) return;
1450
+ function sendTelemetry(url, data) {
1433
1451
  if (!isEnabled()) return;
1434
1452
  try {
1435
1453
  const params = new URLSearchParams();
1436
1454
  if (cliVersion) params.set("v", cliVersion);
1437
1455
  if (isCI()) params.set("ci", "1");
1438
1456
  for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
1439
- fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
1457
+ fetch(`${url}?${params.toString()}`).catch(() => {});
1440
1458
  } catch {}
1441
1459
  }
1460
+ function track(data) {
1461
+ if (!TELEMETRY_URL) return;
1462
+ sendTelemetry(TELEMETRY_URL, data);
1463
+ }
1464
+ function trackInstallReport(data) {
1465
+ if (!INSTALL_REPORT_URL) return;
1466
+ sendTelemetry(INSTALL_REPORT_URL, data);
1467
+ }
1442
1468
  var ProviderRegistryImpl = class {
1443
1469
  providers = [];
1444
1470
  register(provider) {
@@ -1459,6 +1485,54 @@ var WellKnownProvider = class {
1459
1485
  displayName = "Well-Known Skills";
1460
1486
  WELL_KNOWN_PATH = ".well-known/skills";
1461
1487
  INDEX_FILE = "index.json";
1488
+ buildUrl(baseUrl, relativePath) {
1489
+ const normalizedRelative = relativePath.replace(/^\/+/, "");
1490
+ return new URL(normalizedRelative, `${baseUrl.replace(/\/?$/, "/")}`).toString();
1491
+ }
1492
+ getSkillRoot(entry) {
1493
+ if (entry.entry) {
1494
+ const normalizedEntry = posix.normalize(entry.entry).replace(/^\/+/, "");
1495
+ const entryDir = posix.dirname(normalizedEntry);
1496
+ return entryDir === "." ? "" : entryDir;
1497
+ }
1498
+ return entry.name;
1499
+ }
1500
+ resolveRepositoryUrl(index, resolvedBaseUrl, entry) {
1501
+ if (!entry.repositoryId) return `${resolvedBaseUrl.replace(/\/$/, "")}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`;
1502
+ return (index.repositories?.find((repo) => repo.id === entry.repositoryId))?.repository || `${resolvedBaseUrl.replace(/\/$/, "")}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`;
1503
+ }
1504
+ resolveInlineBaseUrl(repositoryUrl) {
1505
+ if (!repositoryUrl.startsWith("http://") && !repositoryUrl.startsWith("https://")) return null;
1506
+ try {
1507
+ const parsed = new URL(repositoryUrl);
1508
+ if (parsed.pathname.endsWith(`/${this.INDEX_FILE}`)) parsed.pathname = parsed.pathname.replace(new RegExp(`/${this.INDEX_FILE}$`), "");
1509
+ else parsed.pathname = parsed.pathname.replace(/\/$/, "");
1510
+ parsed.search = "";
1511
+ parsed.hash = "";
1512
+ return parsed.toString().replace(/\/$/, "");
1513
+ } catch {
1514
+ return null;
1515
+ }
1516
+ }
1517
+ resolveInstallTarget(index, resolvedBaseUrl, entry) {
1518
+ const repositoryUrl = this.resolveRepositoryUrl(index, resolvedBaseUrl, entry);
1519
+ const skillRoot = this.getSkillRoot(entry);
1520
+ const inlineBaseUrl = this.resolveInlineBaseUrl(repositoryUrl);
1521
+ if (inlineBaseUrl) {
1522
+ const skillMdRelativePath = entry.entry || posix.join(skillRoot, "SKILL.md");
1523
+ return {
1524
+ type: "inline",
1525
+ repositoryUrl,
1526
+ skillRoot,
1527
+ skillMdUrl: this.buildUrl(inlineBaseUrl, skillMdRelativePath)
1528
+ };
1529
+ }
1530
+ return {
1531
+ type: "repository",
1532
+ repositoryUrl,
1533
+ subpath: skillRoot
1534
+ };
1535
+ }
1462
1536
  match(url) {
1463
1537
  if (!url.startsWith("http://") && !url.startsWith("https://")) return { matches: false };
1464
1538
  try {
@@ -1501,14 +1575,17 @@ var WellKnownProvider = class {
1501
1575
  debugLog(`[well-known] index response body: ${responseText}`);
1502
1576
  const index = JSON.parse(responseText);
1503
1577
  if (!index.skills || !Array.isArray(index.skills)) continue;
1504
- let allValid = true;
1505
- for (const entry of index.skills) if (!this.isValidSkillEntry(entry)) {
1506
- allValid = false;
1507
- break;
1508
- }
1509
- if (allValid) return {
1510
- index,
1511
- resolvedBaseUrl: resolvedBase
1578
+ const totalSkillCount = index.skills.length;
1579
+ const validSkills = index.skills.filter((entry) => this.isValidSkillEntry(entry));
1580
+ const invalidSkillCount = totalSkillCount - validSkills.length;
1581
+ return {
1582
+ index: {
1583
+ ...index,
1584
+ skills: validSkills
1585
+ },
1586
+ resolvedBaseUrl: resolvedBase,
1587
+ totalSkillCount,
1588
+ invalidSkillCount
1512
1589
  };
1513
1590
  } catch {
1514
1591
  continue;
@@ -1532,6 +1609,12 @@ var WellKnownProvider = class {
1532
1609
  if (file.startsWith("/") || file.startsWith("\\") || file.includes("..")) return false;
1533
1610
  }
1534
1611
  if (!e.files.some((f) => typeof f === "string" && f.toLowerCase() === "skill.md")) return false;
1612
+ if (e.entry !== void 0) {
1613
+ if (typeof e.entry !== "string") return false;
1614
+ if (e.entry.startsWith("/") || e.entry.startsWith("\\") || e.entry.includes("..")) return false;
1615
+ if (!e.entry.toLowerCase().endsWith("/skill.md") && e.entry.toLowerCase() !== "skill.md") return false;
1616
+ }
1617
+ if (e.repositoryId !== void 0 && typeof e.repositoryId !== "string") return false;
1535
1618
  return true;
1536
1619
  }
1537
1620
  async fetchSkill(url, options = {}) {
@@ -1547,28 +1630,40 @@ var WellKnownProvider = class {
1547
1630
  if (!skillName) return null;
1548
1631
  const skillEntry = index.skills.find((s) => s.name === skillName);
1549
1632
  if (!skillEntry) return null;
1550
- return this.fetchSkillByEntry(resolvedBaseUrl, skillEntry);
1633
+ return this.fetchSkillByEntry(index, resolvedBaseUrl, skillEntry);
1551
1634
  } catch {
1552
1635
  return null;
1553
1636
  }
1554
1637
  }
1555
- async fetchSkillByEntry(baseUrl, entry) {
1638
+ async fetchSkillByEntry(index, baseUrl, entry) {
1556
1639
  try {
1557
- const skillBaseUrl = `${baseUrl.replace(/\/$/, "")}/${this.WELL_KNOWN_PATH}/${entry.name}`;
1558
- const skillMdUrl = `${skillBaseUrl}/SKILL.md`;
1559
- const response = await fetch(skillMdUrl);
1640
+ const installTarget = this.resolveInstallTarget(index, baseUrl, entry);
1641
+ const files = /* @__PURE__ */ new Map();
1642
+ if (installTarget.type === "repository") return {
1643
+ name: entry.name,
1644
+ description: entry.description,
1645
+ content: "",
1646
+ installName: entry.name,
1647
+ sourceUrl: installTarget.repositoryUrl,
1648
+ files,
1649
+ declaredFiles: [...entry.files],
1650
+ indexEntry: entry,
1651
+ installTarget
1652
+ };
1653
+ const response = await fetch(installTarget.skillMdUrl);
1560
1654
  if (!response.ok) return null;
1561
1655
  const content = await response.text();
1562
1656
  const { data } = (0, import_gray_matter.default)(content);
1563
1657
  if (!data.name || !data.description) return null;
1564
- const files = /* @__PURE__ */ new Map();
1565
1658
  files.set("SKILL.md", content);
1566
1659
  const filePromises = entry.files.filter((f) => f.toLowerCase() !== "skill.md").map(async (filePath) => {
1567
1660
  try {
1568
- const fileUrl = `${skillBaseUrl}/${filePath}`;
1661
+ const normalizedFilePath = filePath.replace(/^\/+/, "");
1662
+ const fileRelativePath = installTarget.skillRoot ? posix.join(installTarget.skillRoot, normalizedFilePath) : normalizedFilePath;
1663
+ const fileUrl = this.buildUrl(this.resolveInlineBaseUrl(installTarget.repositoryUrl) || baseUrl, fileRelativePath);
1569
1664
  const fileResponse = await fetch(fileUrl);
1570
1665
  if (fileResponse.ok) return {
1571
- path: filePath,
1666
+ path: normalizedFilePath,
1572
1667
  content: await fileResponse.text()
1573
1668
  };
1574
1669
  } catch {}
@@ -1581,24 +1676,51 @@ var WellKnownProvider = class {
1581
1676
  description: data.description,
1582
1677
  content,
1583
1678
  installName: entry.name,
1584
- sourceUrl: skillMdUrl,
1679
+ sourceUrl: installTarget.skillMdUrl,
1585
1680
  metadata: data.metadata,
1586
1681
  files,
1587
- indexEntry: entry
1682
+ declaredFiles: [...entry.files],
1683
+ indexEntry: entry,
1684
+ installTarget
1588
1685
  };
1589
1686
  } catch {
1590
1687
  return null;
1591
1688
  }
1592
1689
  }
1593
1690
  async fetchAllSkills(url, options = {}) {
1691
+ return (await this.fetchAllSkillsWithStatus(url, options)).skills;
1692
+ }
1693
+ async fetchAllSkillsWithStatus(url, options = {}) {
1594
1694
  try {
1595
1695
  const result = await this.fetchIndex(url, options);
1596
- if (!result) return [];
1597
- const { index, resolvedBaseUrl } = result;
1598
- const skillPromises = index.skills.map((entry) => this.fetchSkillByEntry(resolvedBaseUrl, entry));
1599
- return (await Promise.all(skillPromises)).filter((s) => s !== null);
1696
+ if (!result) return {
1697
+ skills: [],
1698
+ status: "index-not-found",
1699
+ totalSkillCount: 0,
1700
+ invalidSkillCount: 0
1701
+ };
1702
+ const { index, resolvedBaseUrl, totalSkillCount, invalidSkillCount } = result;
1703
+ if (index.skills.length === 0) return {
1704
+ skills: [],
1705
+ status: "no-valid-entries",
1706
+ totalSkillCount,
1707
+ invalidSkillCount
1708
+ };
1709
+ const skillPromises = index.skills.map((entry) => this.fetchSkillByEntry(index, resolvedBaseUrl, entry));
1710
+ const skills = (await Promise.all(skillPromises)).filter((s) => s !== null);
1711
+ return {
1712
+ skills,
1713
+ status: skills.length > 0 ? "ok" : "no-installable-skills",
1714
+ totalSkillCount,
1715
+ invalidSkillCount
1716
+ };
1600
1717
  } catch {
1601
- return [];
1718
+ return {
1719
+ skills: [],
1720
+ status: "index-not-found",
1721
+ totalSkillCount: 0,
1722
+ invalidSkillCount: 0
1723
+ };
1602
1724
  }
1603
1725
  }
1604
1726
  toRawUrl(url) {
@@ -1804,7 +1926,7 @@ function createEmptyLocalLock() {
1804
1926
  skills: {}
1805
1927
  };
1806
1928
  }
1807
- var version$1 = "1.4.4";
1929
+ var version$1 = "1.4.6";
1808
1930
  const isCancelled$1 = (value) => typeof value === "symbol";
1809
1931
  async function isSourcePrivate(source) {
1810
1932
  const ownerRepo = parseOwnerRepo(source);
@@ -1814,6 +1936,40 @@ async function isSourcePrivate(source) {
1814
1936
  function initTelemetry(version) {
1815
1937
  setVersion(version);
1816
1938
  }
1939
+ function getWellKnownFileCount(skill) {
1940
+ return skill.declaredFiles.length > 0 ? skill.declaredFiles.length : skill.files.size;
1941
+ }
1942
+ function getWellKnownEmptyStateMessage(status, totalSkillCount, invalidSkillCount) {
1943
+ switch (status) {
1944
+ case "no-valid-entries": return totalSkillCount > 0 ? `Found a skills index, but all ${totalSkillCount} entr${totalSkillCount === 1 ? "y is" : "ies are"} invalid. Check that each entry has a top-level SKILL.md in files[].` : "Found a skills index, but it does not contain any valid skill entries.";
1945
+ case "no-installable-skills":
1946
+ if (invalidSkillCount > 0) return `Found a skills index, but no installable skills could be loaded. ${invalidSkillCount} invalid entr${invalidSkillCount === 1 ? "y was" : "ies were"} skipped.`;
1947
+ return "Found a skills index, but no installable skills could be loaded from it.";
1948
+ default: return "No skills found at this URL. Make sure the server has a /.well-known/skills/index.json file.";
1949
+ }
1950
+ }
1951
+ async function prepareRepositoryBackedWellKnownSkill(skill) {
1952
+ if (skill.installTarget.type !== "repository") throw new Error("Skill is not repository-backed");
1953
+ const tempDir = await cloneRepo(skill.installTarget.repositoryUrl);
1954
+ try {
1955
+ const discovered = await discoverSkills(tempDir, skill.installTarget.subpath, {
1956
+ includeInternal: true,
1957
+ fullDepth: true
1958
+ });
1959
+ const matched = discovered.find((candidate) => candidate.name.toLowerCase() === skill.installName.toLowerCase()) || discovered[0];
1960
+ if (!matched) throw new Error(`No skill found at ${skill.installTarget.repositoryUrl}${skill.installTarget.subpath ? ` (${skill.installTarget.subpath})` : ""}`);
1961
+ return {
1962
+ preparedSkill: {
1963
+ ...matched,
1964
+ name: skill.installName
1965
+ },
1966
+ tempDir
1967
+ };
1968
+ } catch (error) {
1969
+ await cleanupTempDir(tempDir).catch(() => {});
1970
+ throw error;
1971
+ }
1972
+ }
1817
1973
  function riskLabel(risk) {
1818
1974
  switch (risk) {
1819
1975
  case "critical": return import_picocolors.default.red(import_picocolors.default.bold("Critical Risk"));
@@ -1970,6 +2126,33 @@ async function selectAgentsInteractive(options) {
1970
2126
  return selected;
1971
2127
  }
1972
2128
  setVersion(version$1);
2129
+ function resolveInstallOutcome(successCount, failCount) {
2130
+ if (failCount === 0) return "success";
2131
+ if (successCount === 0) return "failure";
2132
+ return "partial_failure";
2133
+ }
2134
+ function buildFailedDetails(results) {
2135
+ const failed = results.filter((result) => !result.success).map((result) => `${result.skill}@${result.agent}${result.error ? `:${result.error}` : ""}`);
2136
+ return failed.length > 0 ? JSON.stringify(failed) : void 0;
2137
+ }
2138
+ function reportInstallToSunIfNeeded(params) {
2139
+ if (!isInternalSkillSourceUrl(params.sourceUrl)) return;
2140
+ trackInstallReport({
2141
+ event: "install",
2142
+ source: params.source,
2143
+ sourceUrl: params.sourceUrl,
2144
+ skills: params.skills.join(","),
2145
+ agents: params.agents.join(","),
2146
+ ...params.installGlobally && { global: "1" },
2147
+ ...params.skillFiles && { skillFiles: JSON.stringify(params.skillFiles) },
2148
+ ...params.sourceType && { sourceType: params.sourceType },
2149
+ result: resolveInstallOutcome(params.successfulCount, params.failedCount),
2150
+ successCount: String(params.successfulCount),
2151
+ failCount: String(params.failedCount),
2152
+ ...params.failedDetails && { failedDetails: params.failedDetails },
2153
+ ...params.errorMessage && { errorMessage: params.errorMessage }
2154
+ });
2155
+ }
1973
2156
  function isBareSkillNameSource(source) {
1974
2157
  return /^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(source);
1975
2158
  }
@@ -1980,297 +2163,367 @@ function buildFallbackGitSshUrl(source) {
1980
2163
  return `git@${DEFAULT_GIT_FALLBACK_SSH_HOST}:${source.replace(/\.git$/, "")}.git`;
1981
2164
  }
1982
2165
  async function handleWellKnownSkills(source, url, options, spinner, allowFallback = false, fallbackToRoot = true) {
1983
- spinner.start("Discovering skills from well-known endpoint...");
1984
- const skills = await wellKnownProvider.fetchAllSkills(url, { fallbackToRoot });
1985
- if (skills.length === 0) {
1986
- if (allowFallback) {
1987
- spinner.stop(import_picocolors.default.yellow("Default well-known endpoint unavailable"));
1988
- return false;
1989
- }
1990
- spinner.stop(import_picocolors.default.red("No skills found"));
1991
- Se(import_picocolors.default.red("No skills found at this URL. Make sure the server has a /.well-known/skills/index.json file."));
1992
- process.exit(1);
1993
- }
1994
- spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
1995
- for (const skill of skills) {
1996
- M.info(`Skill: ${import_picocolors.default.cyan(skill.installName)}`);
1997
- M.message(import_picocolors.default.dim(skill.description));
1998
- if (skill.files.size > 1) M.message(import_picocolors.default.dim(` Files: ${Array.from(skill.files.keys()).join(", ")}`));
1999
- }
2000
- if (options.list) {
2001
- console.log();
2002
- M.step(import_picocolors.default.bold("Available Skills"));
2003
- for (const skill of skills) {
2004
- M.message(` ${import_picocolors.default.cyan(skill.installName)}`);
2005
- M.message(` ${import_picocolors.default.dim(skill.description)}`);
2006
- if (skill.files.size > 1) M.message(` ${import_picocolors.default.dim(`Files: ${skill.files.size}`)}`);
2007
- }
2008
- console.log();
2009
- Se("Run without --list to install");
2010
- process.exit(0);
2011
- }
2012
- let selectedSkills;
2013
- if (options.skill?.includes("*")) {
2014
- selectedSkills = skills;
2015
- M.info(`Installing all ${skills.length} skills`);
2016
- } else if (options.skill && options.skill.length > 0) {
2017
- selectedSkills = skills.filter((s) => options.skill.some((name) => s.installName.toLowerCase() === name.toLowerCase() || s.name.toLowerCase() === name.toLowerCase()));
2018
- if (selectedSkills.length === 0) {
2166
+ const shouldReportToSun = isInternalSkillSourceUrl(url);
2167
+ try {
2168
+ spinner.start("Discovering skills from well-known endpoint...");
2169
+ const discovery = await wellKnownProvider.fetchAllSkillsWithStatus(url, { fallbackToRoot });
2170
+ const skills = discovery.skills;
2171
+ if (skills.length === 0) {
2019
2172
  if (allowFallback) {
2020
- M.info(`Skill not found on default well-known endpoint: ${options.skill.join(", ")}`);
2173
+ spinner.stop(import_picocolors.default.yellow("Default well-known endpoint unavailable"));
2021
2174
  return false;
2022
2175
  }
2023
- M.error(`No matching skills found for: ${options.skill.join(", ")}`);
2024
- M.info("Available skills:");
2025
- for (const s of skills) M.message(` - ${s.installName}`);
2176
+ spinner.stop(import_picocolors.default.red("No usable skills found"));
2177
+ Se(import_picocolors.default.red(getWellKnownEmptyStateMessage(discovery.status, discovery.totalSkillCount, discovery.invalidSkillCount)));
2026
2178
  process.exit(1);
2027
2179
  }
2028
- } else if (skills.length === 1) {
2029
- selectedSkills = skills;
2030
- const firstSkill = skills[0];
2031
- M.info(`Skill: ${import_picocolors.default.cyan(firstSkill.installName)}`);
2032
- } else if (options.yes) {
2033
- selectedSkills = skills;
2034
- M.info(`Installing all ${skills.length} skills`);
2035
- } else {
2036
- const selected = await multiselect({
2037
- message: "Select skills to install",
2038
- options: skills.map((s) => ({
2039
- value: s,
2040
- label: s.installName,
2041
- hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
2042
- })),
2043
- required: true
2044
- });
2045
- if (pD(selected)) {
2046
- xe("Installation cancelled");
2047
- process.exit(0);
2180
+ spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2181
+ for (const skill of skills) {
2182
+ M.info(`Skill: ${import_picocolors.default.cyan(skill.installName)}`);
2183
+ M.message(import_picocolors.default.dim(skill.description));
2184
+ if (skill.files.size > 1) M.message(import_picocolors.default.dim(` Files: ${Array.from(skill.files.keys()).join(", ")}`));
2185
+ else if (skill.declaredFiles.length > 1) M.message(import_picocolors.default.dim(` Files: ${skill.declaredFiles.join(", ")}`));
2048
2186
  }
2049
- selectedSkills = selected;
2050
- }
2051
- let targetAgents;
2052
- const validAgents = Object.keys(agents);
2053
- if (options.agent?.includes("*")) {
2054
- targetAgents = validAgents;
2055
- M.info(`Installing to all ${targetAgents.length} agents`);
2056
- } else if (options.agent && options.agent.length > 0) {
2057
- const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
2058
- if (invalidAgents.length > 0) {
2059
- M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
2060
- M.info(`Valid agents: ${validAgents.join(", ")}`);
2061
- process.exit(1);
2187
+ if (options.list) {
2188
+ console.log();
2189
+ M.step(import_picocolors.default.bold("Available Skills"));
2190
+ for (const skill of skills) {
2191
+ M.message(` ${import_picocolors.default.cyan(skill.installName)}`);
2192
+ M.message(` ${import_picocolors.default.dim(skill.description)}`);
2193
+ const fileCount = getWellKnownFileCount(skill);
2194
+ if (fileCount > 1) M.message(` ${import_picocolors.default.dim(`Files: ${fileCount}`)}`);
2195
+ }
2196
+ console.log();
2197
+ Se("Run without --list to install");
2198
+ process.exit(0);
2062
2199
  }
2063
- targetAgents = options.agent;
2064
- } else {
2065
- spinner.start("Loading agents...");
2066
- const installedAgents = await detectInstalledAgents();
2067
- const totalAgents = Object.keys(agents).length;
2068
- spinner.stop(`${totalAgents} agents`);
2069
- if (installedAgents.length === 0) if (options.yes) {
2070
- targetAgents = validAgents;
2071
- M.info("Installing to all agents");
2200
+ let selectedSkills;
2201
+ if (options.skill?.includes("*")) {
2202
+ selectedSkills = skills;
2203
+ M.info(`Installing all ${skills.length} skills`);
2204
+ } else if (options.skill && options.skill.length > 0) {
2205
+ selectedSkills = skills.filter((s) => options.skill.some((name) => s.installName.toLowerCase() === name.toLowerCase() || s.name.toLowerCase() === name.toLowerCase()));
2206
+ if (selectedSkills.length === 0) {
2207
+ if (allowFallback) {
2208
+ M.info(`Skill not found on default well-known endpoint: ${options.skill.join(", ")}`);
2209
+ return false;
2210
+ }
2211
+ M.error(`No matching skills found for: ${options.skill.join(", ")}`);
2212
+ M.info("Available skills:");
2213
+ for (const s of skills) M.message(` - ${s.installName}`);
2214
+ process.exit(1);
2215
+ }
2216
+ } else if (skills.length === 1) {
2217
+ selectedSkills = skills;
2218
+ const firstSkill = skills[0];
2219
+ M.info(`Skill: ${import_picocolors.default.cyan(firstSkill.installName)}`);
2220
+ } else if (options.yes) {
2221
+ selectedSkills = skills;
2222
+ M.info(`Installing all ${skills.length} skills`);
2072
2223
  } else {
2073
- M.info("Select agents to install skills to");
2074
- const selected = await promptForAgents("Which agents do you want to install to?", Object.entries(agents).map(([key, config]) => ({
2075
- value: key,
2076
- label: config.displayName
2077
- })));
2224
+ const selected = await multiselect({
2225
+ message: "Select skills to install",
2226
+ options: skills.map((s) => ({
2227
+ value: s,
2228
+ label: s.installName,
2229
+ hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
2230
+ })),
2231
+ required: true
2232
+ });
2078
2233
  if (pD(selected)) {
2079
2234
  xe("Installation cancelled");
2080
2235
  process.exit(0);
2081
2236
  }
2082
- targetAgents = selected;
2237
+ selectedSkills = selected;
2083
2238
  }
2084
- else if (installedAgents.length === 1 || options.yes) {
2085
- targetAgents = ensureUniversalAgents(installedAgents);
2086
- if (installedAgents.length === 1) {
2087
- const firstAgent = installedAgents[0];
2088
- M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
2089
- } else M.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
2239
+ let targetAgents;
2240
+ const validAgents = Object.keys(agents);
2241
+ if (options.agent?.includes("*")) {
2242
+ targetAgents = validAgents;
2243
+ M.info(`Installing to all ${targetAgents.length} agents`);
2244
+ } else if (options.agent && options.agent.length > 0) {
2245
+ const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
2246
+ if (invalidAgents.length > 0) {
2247
+ M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
2248
+ M.info(`Valid agents: ${validAgents.join(", ")}`);
2249
+ process.exit(1);
2250
+ }
2251
+ targetAgents = options.agent;
2090
2252
  } else {
2091
- const selected = await selectAgentsInteractive({ global: options.global });
2092
- if (pD(selected)) {
2253
+ spinner.start("Loading agents...");
2254
+ const installedAgents = await detectInstalledAgents();
2255
+ const totalAgents = Object.keys(agents).length;
2256
+ spinner.stop(`${totalAgents} agents`);
2257
+ if (installedAgents.length === 0) if (options.yes) {
2258
+ targetAgents = validAgents;
2259
+ M.info("Installing to all agents");
2260
+ } else {
2261
+ M.info("Select agents to install skills to");
2262
+ const selected = await promptForAgents("Which agents do you want to install to?", Object.entries(agents).map(([key, config]) => ({
2263
+ value: key,
2264
+ label: config.displayName
2265
+ })));
2266
+ if (pD(selected)) {
2267
+ xe("Installation cancelled");
2268
+ process.exit(0);
2269
+ }
2270
+ targetAgents = selected;
2271
+ }
2272
+ else if (installedAgents.length === 1 || options.yes) {
2273
+ targetAgents = ensureUniversalAgents(installedAgents);
2274
+ if (installedAgents.length === 1) {
2275
+ const firstAgent = installedAgents[0];
2276
+ M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
2277
+ } else M.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
2278
+ } else {
2279
+ const selected = await selectAgentsInteractive({ global: options.global });
2280
+ if (pD(selected)) {
2281
+ xe("Installation cancelled");
2282
+ process.exit(0);
2283
+ }
2284
+ targetAgents = selected;
2285
+ }
2286
+ }
2287
+ let installGlobally = options.global ?? false;
2288
+ const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2289
+ if (options.global === void 0 && !options.yes && supportsGlobal) {
2290
+ const scope = await ve({
2291
+ message: "Installation scope",
2292
+ options: [{
2293
+ value: false,
2294
+ label: "Project",
2295
+ hint: "Install in current directory (committed with your project)"
2296
+ }, {
2297
+ value: true,
2298
+ label: "Global",
2299
+ hint: "Install in home directory (available across all projects)"
2300
+ }]
2301
+ });
2302
+ if (pD(scope)) {
2093
2303
  xe("Installation cancelled");
2094
2304
  process.exit(0);
2095
2305
  }
2096
- targetAgents = selected;
2306
+ installGlobally = scope;
2097
2307
  }
2098
- }
2099
- let installGlobally = options.global ?? false;
2100
- const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2101
- if (options.global === void 0 && !options.yes && supportsGlobal) {
2102
- const scope = await ve({
2103
- message: "Installation scope",
2104
- options: [{
2105
- value: false,
2106
- label: "Project",
2107
- hint: "Install in current directory (committed with your project)"
2108
- }, {
2109
- value: true,
2110
- label: "Global",
2111
- hint: "Install in home directory (available across all projects)"
2112
- }]
2113
- });
2114
- if (pD(scope)) {
2115
- xe("Installation cancelled");
2116
- process.exit(0);
2308
+ let installMode = options.copy ? "copy" : "symlink";
2309
+ if (!options.copy && !options.yes) {
2310
+ const modeChoice = await ve({
2311
+ message: "Installation method",
2312
+ options: [{
2313
+ value: "symlink",
2314
+ label: "Symlink (Recommended)",
2315
+ hint: "Single source of truth, easy updates"
2316
+ }, {
2317
+ value: "copy",
2318
+ label: "Copy to all agents",
2319
+ hint: "Independent copies for each agent"
2320
+ }]
2321
+ });
2322
+ if (pD(modeChoice)) {
2323
+ xe("Installation cancelled");
2324
+ process.exit(0);
2325
+ }
2326
+ installMode = modeChoice;
2117
2327
  }
2118
- installGlobally = scope;
2119
- }
2120
- let installMode = options.copy ? "copy" : "symlink";
2121
- if (!options.copy && !options.yes) {
2122
- const modeChoice = await ve({
2123
- message: "Installation method",
2124
- options: [{
2125
- value: "symlink",
2126
- label: "Symlink (Recommended)",
2127
- hint: "Single source of truth, easy updates"
2128
- }, {
2129
- value: "copy",
2130
- label: "Copy to all agents",
2131
- hint: "Independent copies for each agent"
2132
- }]
2133
- });
2134
- if (pD(modeChoice)) {
2135
- xe("Installation cancelled");
2136
- process.exit(0);
2328
+ const cwd = process.cwd();
2329
+ const summaryLines = [];
2330
+ targetAgents.map((a) => agents[a].displayName);
2331
+ const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
2332
+ skillName: skill.installName,
2333
+ agent,
2334
+ installed: await isSkillInstalled(skill.installName, agent, { global: installGlobally })
2335
+ }))));
2336
+ const overwriteStatus = /* @__PURE__ */ new Map();
2337
+ for (const { skillName, agent, installed } of overwriteChecks) {
2338
+ if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
2339
+ overwriteStatus.get(skillName).set(agent, installed);
2137
2340
  }
2138
- installMode = modeChoice;
2139
- }
2140
- const cwd = process.cwd();
2141
- const summaryLines = [];
2142
- targetAgents.map((a) => agents[a].displayName);
2143
- const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
2144
- skillName: skill.installName,
2145
- agent,
2146
- installed: await isSkillInstalled(skill.installName, agent, { global: installGlobally })
2147
- }))));
2148
- const overwriteStatus = /* @__PURE__ */ new Map();
2149
- for (const { skillName, agent, installed } of overwriteChecks) {
2150
- if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
2151
- overwriteStatus.get(skillName).set(agent, installed);
2152
- }
2153
- for (const skill of selectedSkills) {
2154
- if (summaryLines.length > 0) summaryLines.push("");
2155
- const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2156
- summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2157
- summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2158
- if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
2159
- const skillOverwrites = overwriteStatus.get(skill.installName);
2160
- const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
2161
- if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
2162
- }
2163
- console.log();
2164
- Me(summaryLines.join("\n"), "Installation Summary");
2165
- if (!options.yes) {
2166
- const confirmed = await ye({ message: "Proceed with installation?" });
2167
- if (pD(confirmed) || !confirmed) {
2168
- xe("Installation cancelled");
2169
- process.exit(0);
2341
+ for (const skill of selectedSkills) {
2342
+ if (summaryLines.length > 0) summaryLines.push("");
2343
+ const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2344
+ summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2345
+ summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2346
+ const fileCount = getWellKnownFileCount(skill);
2347
+ if (fileCount > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${fileCount}`);
2348
+ const skillOverwrites = overwriteStatus.get(skill.installName);
2349
+ const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
2350
+ if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
2170
2351
  }
2171
- }
2172
- spinner.start("Installing skills...");
2173
- const results = [];
2174
- for (const skill of selectedSkills) for (const agent of targetAgents) {
2175
- const result = await installWellKnownSkillForAgent(skill, agent, {
2176
- global: installGlobally,
2177
- mode: installMode
2352
+ console.log();
2353
+ Me(summaryLines.join("\n"), "Installation Summary");
2354
+ if (!options.yes) {
2355
+ const confirmed = await ye({ message: "Proceed with installation?" });
2356
+ if (pD(confirmed) || !confirmed) {
2357
+ xe("Installation cancelled");
2358
+ process.exit(0);
2359
+ }
2360
+ }
2361
+ const preparedRepositorySkills = /* @__PURE__ */ new Map();
2362
+ const repositoryPrepFailures = /* @__PURE__ */ new Map();
2363
+ const repositoryTempDirs = [];
2364
+ const repositoryBackedSkills = selectedSkills.filter((skill) => skill.installTarget.type === "repository");
2365
+ if (repositoryBackedSkills.length > 0) {
2366
+ spinner.start("Preparing repository-backed skills...");
2367
+ for (const skill of repositoryBackedSkills) try {
2368
+ const prepared = await prepareRepositoryBackedWellKnownSkill(skill);
2369
+ preparedRepositorySkills.set(skill.installName, prepared.preparedSkill);
2370
+ repositoryTempDirs.push(prepared.tempDir);
2371
+ } catch (error) {
2372
+ repositoryPrepFailures.set(skill.installName, error instanceof Error ? error.message : "Unknown error");
2373
+ }
2374
+ spinner.stop("Repository-backed skills prepared");
2375
+ }
2376
+ spinner.start("Installing skills...");
2377
+ const results = [];
2378
+ for (const skill of selectedSkills) for (const agent of targetAgents) {
2379
+ let installOutcome;
2380
+ const prepError = repositoryPrepFailures.get(skill.installName);
2381
+ if (prepError) installOutcome = {
2382
+ success: false,
2383
+ path: "",
2384
+ mode: installMode,
2385
+ error: prepError
2386
+ };
2387
+ else if (skill.installTarget.type === "repository") {
2388
+ const preparedSkill = preparedRepositorySkills.get(skill.installName);
2389
+ if (!preparedSkill) installOutcome = {
2390
+ success: false,
2391
+ path: "",
2392
+ mode: installMode,
2393
+ error: "Repository-backed skill was not prepared"
2394
+ };
2395
+ else installOutcome = await installSkillForAgent(preparedSkill, agent, {
2396
+ global: installGlobally,
2397
+ mode: installMode
2398
+ });
2399
+ } else installOutcome = await installWellKnownSkillForAgent(skill, agent, {
2400
+ global: installGlobally,
2401
+ mode: installMode
2402
+ });
2403
+ results.push({
2404
+ skill: skill.installName,
2405
+ agent: agents[agent].displayName,
2406
+ ...installOutcome
2407
+ });
2408
+ }
2409
+ await Promise.all(repositoryTempDirs.map((dir) => cleanupTempDir(dir).catch(() => {})));
2410
+ spinner.stop("Installation complete");
2411
+ console.log();
2412
+ const successful = results.filter((r) => r.success);
2413
+ const failed = results.filter((r) => !r.success);
2414
+ const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
2415
+ const skillFiles = {};
2416
+ for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
2417
+ if (await isSourcePrivate(sourceIdentifier) !== true) track({
2418
+ event: "install",
2419
+ source: sourceIdentifier,
2420
+ skills: selectedSkills.map((s) => s.installName).join(","),
2421
+ agents: targetAgents.join(","),
2422
+ ...installGlobally && { global: "1" },
2423
+ skillFiles: JSON.stringify(skillFiles),
2424
+ sourceType: "well-known"
2178
2425
  });
2179
- results.push({
2180
- skill: skill.installName,
2181
- agent: agents[agent].displayName,
2182
- ...result
2426
+ reportInstallToSunIfNeeded({
2427
+ source: sourceIdentifier,
2428
+ sourceUrl: url,
2429
+ skills: selectedSkills.map((s) => s.installName),
2430
+ agents: targetAgents,
2431
+ installGlobally,
2432
+ skillFiles,
2433
+ sourceType: "well-known",
2434
+ successfulCount: successful.length,
2435
+ failedCount: failed.length,
2436
+ failedDetails: buildFailedDetails(results)
2183
2437
  });
2184
- }
2185
- spinner.stop("Installation complete");
2186
- console.log();
2187
- const successful = results.filter((r) => r.success);
2188
- const failed = results.filter((r) => !r.success);
2189
- const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
2190
- const skillFiles = {};
2191
- for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
2192
- if (await isSourcePrivate(sourceIdentifier) !== true) track({
2193
- event: "install",
2194
- source: sourceIdentifier,
2195
- skills: selectedSkills.map((s) => s.installName).join(","),
2196
- agents: targetAgents.join(","),
2197
- ...installGlobally && { global: "1" },
2198
- skillFiles: JSON.stringify(skillFiles),
2199
- sourceType: "well-known"
2200
- });
2201
- if (successful.length > 0 && installGlobally) {
2202
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
2203
- for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2204
- await addSkillToLock(skill.installName, {
2205
- source: sourceIdentifier,
2206
- sourceType: "well-known",
2207
- sourceUrl: skill.sourceUrl,
2208
- skillFolderHash: ""
2209
- });
2210
- } catch {}
2211
- }
2212
- if (successful.length > 0 && !installGlobally) {
2213
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
2214
- for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2215
- const matchingResult = successful.find((r) => r.skill === skill.installName);
2216
- const installDir = matchingResult?.canonicalPath || matchingResult?.path;
2217
- if (installDir) {
2218
- const computedHash = await computeSkillFolderHash(installDir);
2219
- await addSkillToLocalLock(skill.installName, {
2438
+ if (successful.length > 0 && installGlobally) {
2439
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
2440
+ for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2441
+ await addSkillToLock(skill.installName, {
2220
2442
  source: sourceIdentifier,
2221
2443
  sourceType: "well-known",
2222
- computedHash
2223
- }, cwd);
2224
- }
2225
- } catch {}
2226
- }
2227
- if (successful.length > 0) {
2228
- const bySkill = /* @__PURE__ */ new Map();
2229
- for (const r of successful) {
2230
- const skillResults = bySkill.get(r.skill) || [];
2231
- skillResults.push(r);
2232
- bySkill.set(r.skill, skillResults);
2444
+ sourceUrl: skill.sourceUrl,
2445
+ skillFolderHash: ""
2446
+ });
2447
+ } catch {}
2233
2448
  }
2234
- const skillCount = bySkill.size;
2235
- const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2236
- const copiedAgents = symlinkFailures.map((r) => r.agent);
2237
- const resultLines = [];
2238
- for (const [skillName, skillResults] of bySkill) {
2239
- const firstResult = skillResults[0];
2240
- if (firstResult.mode === "copy") {
2241
- resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
2242
- for (const r of skillResults) {
2243
- const shortPath = shortenPath$2(r.path, cwd);
2244
- resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
2449
+ if (successful.length > 0 && !installGlobally) {
2450
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
2451
+ for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2452
+ const matchingResult = successful.find((r) => r.skill === skill.installName);
2453
+ const installDir = matchingResult?.canonicalPath || matchingResult?.path;
2454
+ if (installDir) {
2455
+ const computedHash = await computeSkillFolderHash(installDir);
2456
+ await addSkillToLocalLock(skill.installName, {
2457
+ source: sourceIdentifier,
2458
+ sourceType: "well-known",
2459
+ computedHash
2460
+ }, cwd);
2461
+ }
2462
+ } catch {}
2463
+ }
2464
+ if (successful.length > 0) {
2465
+ const bySkill = /* @__PURE__ */ new Map();
2466
+ for (const r of successful) {
2467
+ const skillResults = bySkill.get(r.skill) || [];
2468
+ skillResults.push(r);
2469
+ bySkill.set(r.skill, skillResults);
2470
+ }
2471
+ const skillCount = bySkill.size;
2472
+ const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2473
+ const copiedAgents = symlinkFailures.map((r) => r.agent);
2474
+ const resultLines = [];
2475
+ for (const [skillName, skillResults] of bySkill) {
2476
+ const firstResult = skillResults[0];
2477
+ if (firstResult.mode === "copy") {
2478
+ resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
2479
+ for (const r of skillResults) {
2480
+ const shortPath = shortenPath$2(r.path, cwd);
2481
+ resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
2482
+ }
2483
+ } else {
2484
+ if (firstResult.canonicalPath) {
2485
+ const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2486
+ resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2487
+ } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
2488
+ resultLines.push(...buildResultLines(skillResults, targetAgents));
2245
2489
  }
2246
- } else {
2247
- if (firstResult.canonicalPath) {
2248
- const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2249
- resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2250
- } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
2251
- resultLines.push(...buildResultLines(skillResults, targetAgents));
2490
+ }
2491
+ const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
2492
+ Me(resultLines.join("\n"), title);
2493
+ if (symlinkFailures.length > 0) {
2494
+ M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
2495
+ M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
2252
2496
  }
2253
2497
  }
2254
- const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
2255
- Me(resultLines.join("\n"), title);
2256
- if (symlinkFailures.length > 0) {
2257
- M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
2258
- M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
2498
+ if (failed.length > 0) {
2499
+ console.log();
2500
+ M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
2501
+ for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
2259
2502
  }
2260
- }
2261
- if (failed.length > 0) {
2262
2503
  console.log();
2263
- M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
2264
- for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
2504
+ Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2505
+ await promptForFindSkills(options, targetAgents);
2506
+ return true;
2507
+ } catch (error) {
2508
+ if (shouldReportToSun) reportInstallToSunIfNeeded({
2509
+ source: wellKnownProvider.getSourceIdentifier(url),
2510
+ sourceUrl: url,
2511
+ skills: options.skill || [],
2512
+ agents: options.agent || [],
2513
+ installGlobally: options.global ?? false,
2514
+ sourceType: "well-known",
2515
+ successfulCount: 0,
2516
+ failedCount: 1,
2517
+ errorMessage: error instanceof Error ? error.message : "Unknown error occurred"
2518
+ });
2519
+ throw error;
2265
2520
  }
2266
- console.log();
2267
- Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2268
- await promptForFindSkills(options, targetAgents);
2269
- return true;
2270
2521
  }
2271
2522
  async function runAdd(args, options = {}) {
2272
2523
  let source = args[0] || DEFAULT_WELL_KNOWN_BASE_URL;
2273
2524
  let installTipShown = false;
2525
+ let parsed = null;
2526
+ let installReportContext = null;
2274
2527
  const showInstallTip = () => {
2275
2528
  if (installTipShown) return;
2276
2529
  M.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
@@ -2300,7 +2553,7 @@ async function runAdd(args, options = {}) {
2300
2553
  M.info(`Falling back to git source: ${import_picocolors.default.cyan(source)}`);
2301
2554
  }
2302
2555
  spinner.start("Parsing source...");
2303
- const parsed = parseSource(source);
2556
+ parsed = parseSource(source);
2304
2557
  spinner.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
2305
2558
  if (parsed.skillFilter) {
2306
2559
  options.skill = options.skill || [];
@@ -2623,6 +2876,15 @@ async function runAdd(args, options = {}) {
2623
2876
  skillFiles[skill.name] = relativePath;
2624
2877
  }
2625
2878
  const normalizedSource = getOwnerRepo(parsed);
2879
+ installReportContext = {
2880
+ source: normalizedSource || parsed.url,
2881
+ sourceUrl: parsed.url,
2882
+ skills: selectedSkills.map((s) => s.name),
2883
+ agents: targetAgents,
2884
+ installGlobally,
2885
+ sourceType: parsed.type,
2886
+ skillFiles
2887
+ };
2626
2888
  if (normalizedSource) {
2627
2889
  const ownerRepo = parseOwnerRepo(normalizedSource);
2628
2890
  if (ownerRepo) {
@@ -2643,6 +2905,18 @@ async function runAdd(args, options = {}) {
2643
2905
  skillFiles: JSON.stringify(skillFiles)
2644
2906
  });
2645
2907
  }
2908
+ reportInstallToSunIfNeeded({
2909
+ source: installReportContext.source,
2910
+ sourceUrl: installReportContext.sourceUrl,
2911
+ skills: installReportContext.skills,
2912
+ agents: installReportContext.agents,
2913
+ installGlobally: installReportContext.installGlobally,
2914
+ sourceType: installReportContext.sourceType,
2915
+ skillFiles: installReportContext.skillFiles,
2916
+ successfulCount: successful.length,
2917
+ failedCount: failed.length,
2918
+ failedDetails: buildFailedDetails(results)
2919
+ });
2646
2920
  if (successful.length > 0 && installGlobally && normalizedSource) {
2647
2921
  const successfulSkillNames = new Set(successful.map((r) => r.skill));
2648
2922
  for (const skill of selectedSkills) {
@@ -2746,6 +3020,18 @@ async function runAdd(args, options = {}) {
2746
3020
  Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2747
3021
  await promptForFindSkills(options, targetAgents);
2748
3022
  } catch (error) {
3023
+ if (parsed) reportInstallToSunIfNeeded({
3024
+ source: getOwnerRepo(parsed) || parsed.url,
3025
+ sourceUrl: parsed.url,
3026
+ skills: installReportContext?.skills || [],
3027
+ agents: installReportContext?.agents || [],
3028
+ installGlobally: installReportContext?.installGlobally || false,
3029
+ sourceType: installReportContext?.sourceType || parsed.type,
3030
+ skillFiles: installReportContext?.skillFiles,
3031
+ successfulCount: 0,
3032
+ failedCount: 1,
3033
+ errorMessage: error instanceof Error ? error.message : "Unknown error occurred"
3034
+ });
2749
3035
  if (error instanceof GitCloneError) {
2750
3036
  M.error(import_picocolors.default.red("Failed to clone repository"));
2751
3037
  for (const line of error.message.split("\n")) M.message(import_picocolors.default.dim(line));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bip-skills",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {