bip-skills 1.4.3 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.mjs +397 -272
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -29,9 +29,17 @@ const DEFAULT_DISCOVERY_SITE_URL = DEFAULT_WELL_KNOWN_BASE_URL;
29
29
  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
+ const DEFAULT_GIT_FALLBACK_SSH_HOST = "git.yyrd.com";
33
+ const DEFAULT_INSTALL_REPORT_URL = "https://sun.yyuap.com/api/install-report";
32
34
  function isYyuapHost(hostname) {
33
35
  return hostname === "yyuap.com" || hostname.endsWith(".yyuap.com");
34
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
+ }
35
43
  function isAllowedReportingUrl(url) {
36
44
  try {
37
45
  return isYyuapHost(new URL(url).hostname);
@@ -73,6 +81,20 @@ async function isRepoPrivate(owner, repo) {
73
81
  return null;
74
82
  }
75
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
+ }
76
98
  function isLocalPath(input) {
77
99
  return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
78
100
  }
@@ -1399,6 +1421,7 @@ function getAllowedReportingUrl(envVarName) {
1399
1421
  }
1400
1422
  const TELEMETRY_URL = getAllowedReportingUrl("SKILLS_TELEMETRY_URL");
1401
1423
  const AUDIT_URL = getAllowedReportingUrl("SKILLS_AUDIT_URL");
1424
+ const INSTALL_REPORT_URL = isAllowedReportingUrl(DEFAULT_INSTALL_REPORT_URL) ? DEFAULT_INSTALL_REPORT_URL : null;
1402
1425
  let cliVersion = null;
1403
1426
  function isCI() {
1404
1427
  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);
@@ -1427,17 +1450,24 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
1427
1450
  return null;
1428
1451
  }
1429
1452
  }
1430
- function track(data) {
1431
- if (!TELEMETRY_URL) return;
1453
+ function sendTelemetry(url, data) {
1432
1454
  if (!isEnabled()) return;
1433
1455
  try {
1434
1456
  const params = new URLSearchParams();
1435
1457
  if (cliVersion) params.set("v", cliVersion);
1436
1458
  if (isCI()) params.set("ci", "1");
1437
1459
  for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
1438
- fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
1460
+ fetch(`${url}?${params.toString()}`).catch(() => {});
1439
1461
  } catch {}
1440
1462
  }
1463
+ function track(data) {
1464
+ if (!TELEMETRY_URL) return;
1465
+ sendTelemetry(TELEMETRY_URL, data);
1466
+ }
1467
+ function trackInstallReport(data) {
1468
+ if (!INSTALL_REPORT_URL) return;
1469
+ sendTelemetry(INSTALL_REPORT_URL, data);
1470
+ }
1441
1471
  var ProviderRegistryImpl = class {
1442
1472
  providers = [];
1443
1473
  register(provider) {
@@ -1476,15 +1506,16 @@ var WellKnownProvider = class {
1476
1506
  return { matches: false };
1477
1507
  }
1478
1508
  }
1479
- async fetchIndex(baseUrl) {
1509
+ async fetchIndex(baseUrl, options = {}) {
1480
1510
  try {
1511
+ const { fallbackToRoot = true } = options;
1481
1512
  const parsed = new URL(baseUrl);
1482
1513
  const basePath = parsed.pathname.replace(/\/$/, "");
1483
1514
  const urlsToTry = [{
1484
1515
  indexUrl: `${parsed.protocol}//${parsed.host}${basePath}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`,
1485
1516
  baseUrl: `${parsed.protocol}//${parsed.host}${basePath}`
1486
1517
  }];
1487
- if (basePath && basePath !== "") urlsToTry.push({
1518
+ if (fallbackToRoot && basePath && basePath !== "") urlsToTry.push({
1488
1519
  indexUrl: `${parsed.protocol}//${parsed.host}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`,
1489
1520
  baseUrl: `${parsed.protocol}//${parsed.host}`
1490
1521
  });
@@ -1532,10 +1563,10 @@ var WellKnownProvider = class {
1532
1563
  if (!e.files.some((f) => typeof f === "string" && f.toLowerCase() === "skill.md")) return false;
1533
1564
  return true;
1534
1565
  }
1535
- async fetchSkill(url) {
1566
+ async fetchSkill(url, options = {}) {
1536
1567
  try {
1537
1568
  const parsed = new URL(url);
1538
- const result = await this.fetchIndex(url);
1569
+ const result = await this.fetchIndex(url, options);
1539
1570
  if (!result) return null;
1540
1571
  const { index, resolvedBaseUrl } = result;
1541
1572
  let skillName = null;
@@ -1588,9 +1619,9 @@ var WellKnownProvider = class {
1588
1619
  return null;
1589
1620
  }
1590
1621
  }
1591
- async fetchAllSkills(url) {
1622
+ async fetchAllSkills(url, options = {}) {
1592
1623
  try {
1593
- const result = await this.fetchIndex(url);
1624
+ const result = await this.fetchIndex(url, options);
1594
1625
  if (!result) return [];
1595
1626
  const { index, resolvedBaseUrl } = result;
1596
1627
  const skillPromises = index.skills.map((entry) => this.fetchSkillByEntry(resolvedBaseUrl, entry));
@@ -1621,8 +1652,8 @@ var WellKnownProvider = class {
1621
1652
  return "unknown";
1622
1653
  }
1623
1654
  }
1624
- async hasSkillsIndex(url) {
1625
- return await this.fetchIndex(url) !== null;
1655
+ async hasSkillsIndex(url, options = {}) {
1656
+ return await this.fetchIndex(url, options) !== null;
1626
1657
  }
1627
1658
  };
1628
1659
  const wellKnownProvider = new WellKnownProvider();
@@ -1802,7 +1833,7 @@ function createEmptyLocalLock() {
1802
1833
  skills: {}
1803
1834
  };
1804
1835
  }
1805
- var version$1 = "1.4.3";
1836
+ var version$1 = "1.4.5";
1806
1837
  const isCancelled$1 = (value) => typeof value === "symbol";
1807
1838
  async function isSourcePrivate(source) {
1808
1839
  const ownerRepo = parseOwnerRepo(source);
@@ -1968,304 +1999,364 @@ async function selectAgentsInteractive(options) {
1968
1999
  return selected;
1969
2000
  }
1970
2001
  setVersion(version$1);
2002
+ function resolveInstallOutcome(successCount, failCount) {
2003
+ if (failCount === 0) return "success";
2004
+ if (successCount === 0) return "failure";
2005
+ return "partial_failure";
2006
+ }
2007
+ function buildFailedDetails(results) {
2008
+ const failed = results.filter((result) => !result.success).map((result) => `${result.skill}@${result.agent}${result.error ? `:${result.error}` : ""}`);
2009
+ return failed.length > 0 ? JSON.stringify(failed) : void 0;
2010
+ }
2011
+ function reportInstallToSunIfNeeded(params) {
2012
+ if (!isInternalSkillSourceUrl(params.sourceUrl)) return;
2013
+ trackInstallReport({
2014
+ event: "install",
2015
+ source: params.source,
2016
+ sourceUrl: params.sourceUrl,
2017
+ skills: params.skills.join(","),
2018
+ agents: params.agents.join(","),
2019
+ ...params.installGlobally && { global: "1" },
2020
+ ...params.skillFiles && { skillFiles: JSON.stringify(params.skillFiles) },
2021
+ ...params.sourceType && { sourceType: params.sourceType },
2022
+ result: resolveInstallOutcome(params.successfulCount, params.failedCount),
2023
+ successCount: String(params.successfulCount),
2024
+ failCount: String(params.failedCount),
2025
+ ...params.failedDetails && { failedDetails: params.failedDetails },
2026
+ ...params.errorMessage && { errorMessage: params.errorMessage }
2027
+ });
2028
+ }
1971
2029
  function isBareSkillNameSource(source) {
1972
2030
  return /^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(source);
1973
2031
  }
1974
2032
  function shouldProbeDefaultWellKnownPath(source) {
1975
2033
  return source.includes("/") && !source.includes("@") && !source.startsWith("http://") && !source.startsWith("https://") && !source.startsWith("git@") && !source.startsWith("ssh://") && !source.startsWith("./") && !source.startsWith("../") && source !== "." && source !== "..";
1976
2034
  }
1977
- async function handleWellKnownSkills(source, url, options, spinner, allowFallback = false) {
1978
- spinner.start("Discovering skills from well-known endpoint...");
1979
- const skills = await wellKnownProvider.fetchAllSkills(url);
1980
- if (skills.length === 0) {
1981
- if (allowFallback) {
1982
- spinner.stop(import_picocolors.default.yellow("Default well-known endpoint unavailable"));
1983
- return false;
1984
- }
1985
- spinner.stop(import_picocolors.default.red("No skills found"));
1986
- Se(import_picocolors.default.red("No skills found at this URL. Make sure the server has a /.well-known/skills/index.json file."));
1987
- process.exit(1);
1988
- }
1989
- spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
1990
- for (const skill of skills) {
1991
- M.info(`Skill: ${import_picocolors.default.cyan(skill.installName)}`);
1992
- M.message(import_picocolors.default.dim(skill.description));
1993
- if (skill.files.size > 1) M.message(import_picocolors.default.dim(` Files: ${Array.from(skill.files.keys()).join(", ")}`));
1994
- }
1995
- if (options.list) {
1996
- console.log();
1997
- M.step(import_picocolors.default.bold("Available Skills"));
1998
- for (const skill of skills) {
1999
- M.message(` ${import_picocolors.default.cyan(skill.installName)}`);
2000
- M.message(` ${import_picocolors.default.dim(skill.description)}`);
2001
- if (skill.files.size > 1) M.message(` ${import_picocolors.default.dim(`Files: ${skill.files.size}`)}`);
2002
- }
2003
- console.log();
2004
- Se("Run without --list to install");
2005
- process.exit(0);
2006
- }
2007
- let selectedSkills;
2008
- if (options.skill?.includes("*")) {
2009
- selectedSkills = skills;
2010
- M.info(`Installing all ${skills.length} skills`);
2011
- } else if (options.skill && options.skill.length > 0) {
2012
- selectedSkills = skills.filter((s) => options.skill.some((name) => s.installName.toLowerCase() === name.toLowerCase() || s.name.toLowerCase() === name.toLowerCase()));
2013
- if (selectedSkills.length === 0) {
2035
+ function buildFallbackGitSshUrl(source) {
2036
+ return `git@${DEFAULT_GIT_FALLBACK_SSH_HOST}:${source.replace(/\.git$/, "")}.git`;
2037
+ }
2038
+ async function handleWellKnownSkills(source, url, options, spinner, allowFallback = false, fallbackToRoot = true) {
2039
+ const shouldReportToSun = isInternalSkillSourceUrl(url);
2040
+ try {
2041
+ spinner.start("Discovering skills from well-known endpoint...");
2042
+ const skills = await wellKnownProvider.fetchAllSkills(url, { fallbackToRoot });
2043
+ if (skills.length === 0) {
2014
2044
  if (allowFallback) {
2015
- M.info(`Skill not found on default well-known endpoint: ${options.skill.join(", ")}`);
2045
+ spinner.stop(import_picocolors.default.yellow("Default well-known endpoint unavailable"));
2016
2046
  return false;
2017
2047
  }
2018
- M.error(`No matching skills found for: ${options.skill.join(", ")}`);
2019
- M.info("Available skills:");
2020
- for (const s of skills) M.message(` - ${s.installName}`);
2048
+ spinner.stop(import_picocolors.default.red("No skills found"));
2049
+ Se(import_picocolors.default.red("No skills found at this URL. Make sure the server has a /.well-known/skills/index.json file."));
2021
2050
  process.exit(1);
2022
2051
  }
2023
- } else if (skills.length === 1) {
2024
- selectedSkills = skills;
2025
- const firstSkill = skills[0];
2026
- M.info(`Skill: ${import_picocolors.default.cyan(firstSkill.installName)}`);
2027
- } else if (options.yes) {
2028
- selectedSkills = skills;
2029
- M.info(`Installing all ${skills.length} skills`);
2030
- } else {
2031
- const selected = await multiselect({
2032
- message: "Select skills to install",
2033
- options: skills.map((s) => ({
2034
- value: s,
2035
- label: s.installName,
2036
- hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
2037
- })),
2038
- required: true
2039
- });
2040
- if (pD(selected)) {
2041
- xe("Installation cancelled");
2042
- process.exit(0);
2052
+ spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2053
+ for (const skill of skills) {
2054
+ M.info(`Skill: ${import_picocolors.default.cyan(skill.installName)}`);
2055
+ M.message(import_picocolors.default.dim(skill.description));
2056
+ if (skill.files.size > 1) M.message(import_picocolors.default.dim(` Files: ${Array.from(skill.files.keys()).join(", ")}`));
2043
2057
  }
2044
- selectedSkills = selected;
2045
- }
2046
- let targetAgents;
2047
- const validAgents = Object.keys(agents);
2048
- if (options.agent?.includes("*")) {
2049
- targetAgents = validAgents;
2050
- M.info(`Installing to all ${targetAgents.length} agents`);
2051
- } else if (options.agent && options.agent.length > 0) {
2052
- const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
2053
- if (invalidAgents.length > 0) {
2054
- M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
2055
- M.info(`Valid agents: ${validAgents.join(", ")}`);
2056
- process.exit(1);
2058
+ if (options.list) {
2059
+ console.log();
2060
+ M.step(import_picocolors.default.bold("Available Skills"));
2061
+ for (const skill of skills) {
2062
+ M.message(` ${import_picocolors.default.cyan(skill.installName)}`);
2063
+ M.message(` ${import_picocolors.default.dim(skill.description)}`);
2064
+ if (skill.files.size > 1) M.message(` ${import_picocolors.default.dim(`Files: ${skill.files.size}`)}`);
2065
+ }
2066
+ console.log();
2067
+ Se("Run without --list to install");
2068
+ process.exit(0);
2057
2069
  }
2058
- targetAgents = options.agent;
2059
- } else {
2060
- spinner.start("Loading agents...");
2061
- const installedAgents = await detectInstalledAgents();
2062
- const totalAgents = Object.keys(agents).length;
2063
- spinner.stop(`${totalAgents} agents`);
2064
- if (installedAgents.length === 0) if (options.yes) {
2065
- targetAgents = validAgents;
2066
- M.info("Installing to all agents");
2070
+ let selectedSkills;
2071
+ if (options.skill?.includes("*")) {
2072
+ selectedSkills = skills;
2073
+ M.info(`Installing all ${skills.length} skills`);
2074
+ } else if (options.skill && options.skill.length > 0) {
2075
+ selectedSkills = skills.filter((s) => options.skill.some((name) => s.installName.toLowerCase() === name.toLowerCase() || s.name.toLowerCase() === name.toLowerCase()));
2076
+ if (selectedSkills.length === 0) {
2077
+ if (allowFallback) {
2078
+ M.info(`Skill not found on default well-known endpoint: ${options.skill.join(", ")}`);
2079
+ return false;
2080
+ }
2081
+ M.error(`No matching skills found for: ${options.skill.join(", ")}`);
2082
+ M.info("Available skills:");
2083
+ for (const s of skills) M.message(` - ${s.installName}`);
2084
+ process.exit(1);
2085
+ }
2086
+ } else if (skills.length === 1) {
2087
+ selectedSkills = skills;
2088
+ const firstSkill = skills[0];
2089
+ M.info(`Skill: ${import_picocolors.default.cyan(firstSkill.installName)}`);
2090
+ } else if (options.yes) {
2091
+ selectedSkills = skills;
2092
+ M.info(`Installing all ${skills.length} skills`);
2067
2093
  } else {
2068
- M.info("Select agents to install skills to");
2069
- const selected = await promptForAgents("Which agents do you want to install to?", Object.entries(agents).map(([key, config]) => ({
2070
- value: key,
2071
- label: config.displayName
2072
- })));
2094
+ const selected = await multiselect({
2095
+ message: "Select skills to install",
2096
+ options: skills.map((s) => ({
2097
+ value: s,
2098
+ label: s.installName,
2099
+ hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
2100
+ })),
2101
+ required: true
2102
+ });
2073
2103
  if (pD(selected)) {
2074
2104
  xe("Installation cancelled");
2075
2105
  process.exit(0);
2076
2106
  }
2077
- targetAgents = selected;
2107
+ selectedSkills = selected;
2078
2108
  }
2079
- else if (installedAgents.length === 1 || options.yes) {
2080
- targetAgents = ensureUniversalAgents(installedAgents);
2081
- if (installedAgents.length === 1) {
2082
- const firstAgent = installedAgents[0];
2083
- M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
2084
- } else M.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
2109
+ let targetAgents;
2110
+ const validAgents = Object.keys(agents);
2111
+ if (options.agent?.includes("*")) {
2112
+ targetAgents = validAgents;
2113
+ M.info(`Installing to all ${targetAgents.length} agents`);
2114
+ } else if (options.agent && options.agent.length > 0) {
2115
+ const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
2116
+ if (invalidAgents.length > 0) {
2117
+ M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
2118
+ M.info(`Valid agents: ${validAgents.join(", ")}`);
2119
+ process.exit(1);
2120
+ }
2121
+ targetAgents = options.agent;
2085
2122
  } else {
2086
- const selected = await selectAgentsInteractive({ global: options.global });
2087
- if (pD(selected)) {
2123
+ spinner.start("Loading agents...");
2124
+ const installedAgents = await detectInstalledAgents();
2125
+ const totalAgents = Object.keys(agents).length;
2126
+ spinner.stop(`${totalAgents} agents`);
2127
+ if (installedAgents.length === 0) if (options.yes) {
2128
+ targetAgents = validAgents;
2129
+ M.info("Installing to all agents");
2130
+ } else {
2131
+ M.info("Select agents to install skills to");
2132
+ const selected = await promptForAgents("Which agents do you want to install to?", Object.entries(agents).map(([key, config]) => ({
2133
+ value: key,
2134
+ label: config.displayName
2135
+ })));
2136
+ if (pD(selected)) {
2137
+ xe("Installation cancelled");
2138
+ process.exit(0);
2139
+ }
2140
+ targetAgents = selected;
2141
+ }
2142
+ else if (installedAgents.length === 1 || options.yes) {
2143
+ targetAgents = ensureUniversalAgents(installedAgents);
2144
+ if (installedAgents.length === 1) {
2145
+ const firstAgent = installedAgents[0];
2146
+ M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
2147
+ } else M.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
2148
+ } else {
2149
+ const selected = await selectAgentsInteractive({ global: options.global });
2150
+ if (pD(selected)) {
2151
+ xe("Installation cancelled");
2152
+ process.exit(0);
2153
+ }
2154
+ targetAgents = selected;
2155
+ }
2156
+ }
2157
+ let installGlobally = options.global ?? false;
2158
+ const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2159
+ if (options.global === void 0 && !options.yes && supportsGlobal) {
2160
+ const scope = await ve({
2161
+ message: "Installation scope",
2162
+ options: [{
2163
+ value: false,
2164
+ label: "Project",
2165
+ hint: "Install in current directory (committed with your project)"
2166
+ }, {
2167
+ value: true,
2168
+ label: "Global",
2169
+ hint: "Install in home directory (available across all projects)"
2170
+ }]
2171
+ });
2172
+ if (pD(scope)) {
2088
2173
  xe("Installation cancelled");
2089
2174
  process.exit(0);
2090
2175
  }
2091
- targetAgents = selected;
2176
+ installGlobally = scope;
2092
2177
  }
2093
- }
2094
- let installGlobally = options.global ?? false;
2095
- const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2096
- if (options.global === void 0 && !options.yes && supportsGlobal) {
2097
- const scope = await ve({
2098
- message: "Installation scope",
2099
- options: [{
2100
- value: false,
2101
- label: "Project",
2102
- hint: "Install in current directory (committed with your project)"
2103
- }, {
2104
- value: true,
2105
- label: "Global",
2106
- hint: "Install in home directory (available across all projects)"
2107
- }]
2108
- });
2109
- if (pD(scope)) {
2110
- xe("Installation cancelled");
2111
- process.exit(0);
2178
+ let installMode = options.copy ? "copy" : "symlink";
2179
+ if (!options.copy && !options.yes) {
2180
+ const modeChoice = await ve({
2181
+ message: "Installation method",
2182
+ options: [{
2183
+ value: "symlink",
2184
+ label: "Symlink (Recommended)",
2185
+ hint: "Single source of truth, easy updates"
2186
+ }, {
2187
+ value: "copy",
2188
+ label: "Copy to all agents",
2189
+ hint: "Independent copies for each agent"
2190
+ }]
2191
+ });
2192
+ if (pD(modeChoice)) {
2193
+ xe("Installation cancelled");
2194
+ process.exit(0);
2195
+ }
2196
+ installMode = modeChoice;
2112
2197
  }
2113
- installGlobally = scope;
2114
- }
2115
- let installMode = options.copy ? "copy" : "symlink";
2116
- if (!options.copy && !options.yes) {
2117
- const modeChoice = await ve({
2118
- message: "Installation method",
2119
- options: [{
2120
- value: "symlink",
2121
- label: "Symlink (Recommended)",
2122
- hint: "Single source of truth, easy updates"
2123
- }, {
2124
- value: "copy",
2125
- label: "Copy to all agents",
2126
- hint: "Independent copies for each agent"
2127
- }]
2128
- });
2129
- if (pD(modeChoice)) {
2130
- xe("Installation cancelled");
2131
- process.exit(0);
2198
+ const cwd = process.cwd();
2199
+ const summaryLines = [];
2200
+ targetAgents.map((a) => agents[a].displayName);
2201
+ const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
2202
+ skillName: skill.installName,
2203
+ agent,
2204
+ installed: await isSkillInstalled(skill.installName, agent, { global: installGlobally })
2205
+ }))));
2206
+ const overwriteStatus = /* @__PURE__ */ new Map();
2207
+ for (const { skillName, agent, installed } of overwriteChecks) {
2208
+ if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
2209
+ overwriteStatus.get(skillName).set(agent, installed);
2132
2210
  }
2133
- installMode = modeChoice;
2134
- }
2135
- const cwd = process.cwd();
2136
- const summaryLines = [];
2137
- targetAgents.map((a) => agents[a].displayName);
2138
- const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
2139
- skillName: skill.installName,
2140
- agent,
2141
- installed: await isSkillInstalled(skill.installName, agent, { global: installGlobally })
2142
- }))));
2143
- const overwriteStatus = /* @__PURE__ */ new Map();
2144
- for (const { skillName, agent, installed } of overwriteChecks) {
2145
- if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
2146
- overwriteStatus.get(skillName).set(agent, installed);
2147
- }
2148
- for (const skill of selectedSkills) {
2149
- if (summaryLines.length > 0) summaryLines.push("");
2150
- const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2151
- summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2152
- summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2153
- if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
2154
- const skillOverwrites = overwriteStatus.get(skill.installName);
2155
- const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
2156
- if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
2157
- }
2158
- console.log();
2159
- Me(summaryLines.join("\n"), "Installation Summary");
2160
- if (!options.yes) {
2161
- const confirmed = await ye({ message: "Proceed with installation?" });
2162
- if (pD(confirmed) || !confirmed) {
2163
- xe("Installation cancelled");
2164
- process.exit(0);
2211
+ for (const skill of selectedSkills) {
2212
+ if (summaryLines.length > 0) summaryLines.push("");
2213
+ const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2214
+ summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2215
+ summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2216
+ if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
2217
+ const skillOverwrites = overwriteStatus.get(skill.installName);
2218
+ const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
2219
+ if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
2165
2220
  }
2166
- }
2167
- spinner.start("Installing skills...");
2168
- const results = [];
2169
- for (const skill of selectedSkills) for (const agent of targetAgents) {
2170
- const result = await installWellKnownSkillForAgent(skill, agent, {
2171
- global: installGlobally,
2172
- mode: installMode
2221
+ console.log();
2222
+ Me(summaryLines.join("\n"), "Installation Summary");
2223
+ if (!options.yes) {
2224
+ const confirmed = await ye({ message: "Proceed with installation?" });
2225
+ if (pD(confirmed) || !confirmed) {
2226
+ xe("Installation cancelled");
2227
+ process.exit(0);
2228
+ }
2229
+ }
2230
+ spinner.start("Installing skills...");
2231
+ const results = [];
2232
+ for (const skill of selectedSkills) for (const agent of targetAgents) {
2233
+ const result = await installWellKnownSkillForAgent(skill, agent, {
2234
+ global: installGlobally,
2235
+ mode: installMode
2236
+ });
2237
+ results.push({
2238
+ skill: skill.installName,
2239
+ agent: agents[agent].displayName,
2240
+ ...result
2241
+ });
2242
+ }
2243
+ spinner.stop("Installation complete");
2244
+ console.log();
2245
+ const successful = results.filter((r) => r.success);
2246
+ const failed = results.filter((r) => !r.success);
2247
+ const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
2248
+ const skillFiles = {};
2249
+ for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
2250
+ if (await isSourcePrivate(sourceIdentifier) !== true) track({
2251
+ event: "install",
2252
+ source: sourceIdentifier,
2253
+ skills: selectedSkills.map((s) => s.installName).join(","),
2254
+ agents: targetAgents.join(","),
2255
+ ...installGlobally && { global: "1" },
2256
+ skillFiles: JSON.stringify(skillFiles),
2257
+ sourceType: "well-known"
2173
2258
  });
2174
- results.push({
2175
- skill: skill.installName,
2176
- agent: agents[agent].displayName,
2177
- ...result
2259
+ reportInstallToSunIfNeeded({
2260
+ source: sourceIdentifier,
2261
+ sourceUrl: url,
2262
+ skills: selectedSkills.map((s) => s.installName),
2263
+ agents: targetAgents,
2264
+ installGlobally,
2265
+ skillFiles,
2266
+ sourceType: "well-known",
2267
+ successfulCount: successful.length,
2268
+ failedCount: failed.length,
2269
+ failedDetails: buildFailedDetails(results)
2178
2270
  });
2179
- }
2180
- spinner.stop("Installation complete");
2181
- console.log();
2182
- const successful = results.filter((r) => r.success);
2183
- const failed = results.filter((r) => !r.success);
2184
- const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
2185
- const skillFiles = {};
2186
- for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
2187
- if (await isSourcePrivate(sourceIdentifier) !== true) track({
2188
- event: "install",
2189
- source: sourceIdentifier,
2190
- skills: selectedSkills.map((s) => s.installName).join(","),
2191
- agents: targetAgents.join(","),
2192
- ...installGlobally && { global: "1" },
2193
- skillFiles: JSON.stringify(skillFiles),
2194
- sourceType: "well-known"
2195
- });
2196
- if (successful.length > 0 && installGlobally) {
2197
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
2198
- for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2199
- await addSkillToLock(skill.installName, {
2200
- source: sourceIdentifier,
2201
- sourceType: "well-known",
2202
- sourceUrl: skill.sourceUrl,
2203
- skillFolderHash: ""
2204
- });
2205
- } catch {}
2206
- }
2207
- if (successful.length > 0 && !installGlobally) {
2208
- const successfulSkillNames = new Set(successful.map((r) => r.skill));
2209
- for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2210
- const matchingResult = successful.find((r) => r.skill === skill.installName);
2211
- const installDir = matchingResult?.canonicalPath || matchingResult?.path;
2212
- if (installDir) {
2213
- const computedHash = await computeSkillFolderHash(installDir);
2214
- await addSkillToLocalLock(skill.installName, {
2271
+ if (successful.length > 0 && installGlobally) {
2272
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
2273
+ for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2274
+ await addSkillToLock(skill.installName, {
2215
2275
  source: sourceIdentifier,
2216
2276
  sourceType: "well-known",
2217
- computedHash
2218
- }, cwd);
2219
- }
2220
- } catch {}
2221
- }
2222
- if (successful.length > 0) {
2223
- const bySkill = /* @__PURE__ */ new Map();
2224
- for (const r of successful) {
2225
- const skillResults = bySkill.get(r.skill) || [];
2226
- skillResults.push(r);
2227
- bySkill.set(r.skill, skillResults);
2277
+ sourceUrl: skill.sourceUrl,
2278
+ skillFolderHash: ""
2279
+ });
2280
+ } catch {}
2228
2281
  }
2229
- const skillCount = bySkill.size;
2230
- const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2231
- const copiedAgents = symlinkFailures.map((r) => r.agent);
2232
- const resultLines = [];
2233
- for (const [skillName, skillResults] of bySkill) {
2234
- const firstResult = skillResults[0];
2235
- if (firstResult.mode === "copy") {
2236
- resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
2237
- for (const r of skillResults) {
2238
- const shortPath = shortenPath$2(r.path, cwd);
2239
- resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
2282
+ if (successful.length > 0 && !installGlobally) {
2283
+ const successfulSkillNames = new Set(successful.map((r) => r.skill));
2284
+ for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
2285
+ const matchingResult = successful.find((r) => r.skill === skill.installName);
2286
+ const installDir = matchingResult?.canonicalPath || matchingResult?.path;
2287
+ if (installDir) {
2288
+ const computedHash = await computeSkillFolderHash(installDir);
2289
+ await addSkillToLocalLock(skill.installName, {
2290
+ source: sourceIdentifier,
2291
+ sourceType: "well-known",
2292
+ computedHash
2293
+ }, cwd);
2240
2294
  }
2241
- } else {
2242
- if (firstResult.canonicalPath) {
2243
- const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2244
- resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2245
- } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
2246
- resultLines.push(...buildResultLines(skillResults, targetAgents));
2295
+ } catch {}
2296
+ }
2297
+ if (successful.length > 0) {
2298
+ const bySkill = /* @__PURE__ */ new Map();
2299
+ for (const r of successful) {
2300
+ const skillResults = bySkill.get(r.skill) || [];
2301
+ skillResults.push(r);
2302
+ bySkill.set(r.skill, skillResults);
2303
+ }
2304
+ const skillCount = bySkill.size;
2305
+ const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2306
+ const copiedAgents = symlinkFailures.map((r) => r.agent);
2307
+ const resultLines = [];
2308
+ for (const [skillName, skillResults] of bySkill) {
2309
+ const firstResult = skillResults[0];
2310
+ if (firstResult.mode === "copy") {
2311
+ resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
2312
+ for (const r of skillResults) {
2313
+ const shortPath = shortenPath$2(r.path, cwd);
2314
+ resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
2315
+ }
2316
+ } else {
2317
+ if (firstResult.canonicalPath) {
2318
+ const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2319
+ resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2320
+ } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
2321
+ resultLines.push(...buildResultLines(skillResults, targetAgents));
2322
+ }
2323
+ }
2324
+ const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
2325
+ Me(resultLines.join("\n"), title);
2326
+ if (symlinkFailures.length > 0) {
2327
+ M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
2328
+ M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
2247
2329
  }
2248
2330
  }
2249
- const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
2250
- Me(resultLines.join("\n"), title);
2251
- if (symlinkFailures.length > 0) {
2252
- M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
2253
- M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
2331
+ if (failed.length > 0) {
2332
+ console.log();
2333
+ M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
2334
+ for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
2254
2335
  }
2255
- }
2256
- if (failed.length > 0) {
2257
2336
  console.log();
2258
- M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
2259
- for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
2337
+ Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2338
+ await promptForFindSkills(options, targetAgents);
2339
+ return true;
2340
+ } catch (error) {
2341
+ if (shouldReportToSun) reportInstallToSunIfNeeded({
2342
+ source: wellKnownProvider.getSourceIdentifier(url),
2343
+ sourceUrl: url,
2344
+ skills: options.skill || [],
2345
+ agents: options.agent || [],
2346
+ installGlobally: options.global ?? false,
2347
+ sourceType: "well-known",
2348
+ successfulCount: 0,
2349
+ failedCount: 1,
2350
+ errorMessage: error instanceof Error ? error.message : "Unknown error occurred"
2351
+ });
2352
+ throw error;
2260
2353
  }
2261
- console.log();
2262
- Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2263
- await promptForFindSkills(options, targetAgents);
2264
- return true;
2265
2354
  }
2266
2355
  async function runAdd(args, options = {}) {
2267
2356
  let source = args[0] || DEFAULT_WELL_KNOWN_BASE_URL;
2268
2357
  let installTipShown = false;
2358
+ let parsed = null;
2359
+ let installReportContext = null;
2269
2360
  const showInstallTip = () => {
2270
2361
  if (installTipShown) return;
2271
2362
  M.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
@@ -2285,16 +2376,17 @@ async function runAdd(args, options = {}) {
2285
2376
  if (source && isBareSkillNameSource(source)) {
2286
2377
  options.skill = options.skill || [];
2287
2378
  if (!options.skill.includes(source)) options.skill.push(source);
2288
- if (await handleWellKnownSkills(source, DEFAULT_WELL_KNOWN_BASE_URL, options, spinner, true)) return;
2379
+ if (await handleWellKnownSkills(source, DEFAULT_WELL_KNOWN_BASE_URL, options, spinner, true, true)) return;
2289
2380
  Se(import_picocolors.default.red(`No skills found for: ${source}`));
2290
2381
  process.exit(1);
2291
2382
  }
2292
2383
  if (source && shouldProbeDefaultWellKnownPath(source)) {
2293
- if (await handleWellKnownSkills(source, `${DEFAULT_WELL_KNOWN_BASE_URL}/${source.replace(/^\/+/, "")}`, options, spinner, true)) return;
2294
- M.info(`Falling back to source parsing: ${import_picocolors.default.cyan(source)}`);
2384
+ if (await handleWellKnownSkills(source, `${DEFAULT_WELL_KNOWN_BASE_URL}/${source.replace(/^\/+/, "")}`, options, spinner, true, false)) return;
2385
+ source = buildFallbackGitSshUrl(source);
2386
+ M.info(`Falling back to git source: ${import_picocolors.default.cyan(source)}`);
2295
2387
  }
2296
2388
  spinner.start("Parsing source...");
2297
- const parsed = parseSource(source);
2389
+ parsed = parseSource(source);
2298
2390
  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)}` : ""}`);
2299
2391
  if (parsed.skillFilter) {
2300
2392
  options.skill = options.skill || [];
@@ -2617,6 +2709,15 @@ async function runAdd(args, options = {}) {
2617
2709
  skillFiles[skill.name] = relativePath;
2618
2710
  }
2619
2711
  const normalizedSource = getOwnerRepo(parsed);
2712
+ installReportContext = {
2713
+ source: normalizedSource || parsed.url,
2714
+ sourceUrl: parsed.url,
2715
+ skills: selectedSkills.map((s) => s.name),
2716
+ agents: targetAgents,
2717
+ installGlobally,
2718
+ sourceType: parsed.type,
2719
+ skillFiles
2720
+ };
2620
2721
  if (normalizedSource) {
2621
2722
  const ownerRepo = parseOwnerRepo(normalizedSource);
2622
2723
  if (ownerRepo) {
@@ -2637,6 +2738,18 @@ async function runAdd(args, options = {}) {
2637
2738
  skillFiles: JSON.stringify(skillFiles)
2638
2739
  });
2639
2740
  }
2741
+ reportInstallToSunIfNeeded({
2742
+ source: installReportContext.source,
2743
+ sourceUrl: installReportContext.sourceUrl,
2744
+ skills: installReportContext.skills,
2745
+ agents: installReportContext.agents,
2746
+ installGlobally: installReportContext.installGlobally,
2747
+ sourceType: installReportContext.sourceType,
2748
+ skillFiles: installReportContext.skillFiles,
2749
+ successfulCount: successful.length,
2750
+ failedCount: failed.length,
2751
+ failedDetails: buildFailedDetails(results)
2752
+ });
2640
2753
  if (successful.length > 0 && installGlobally && normalizedSource) {
2641
2754
  const successfulSkillNames = new Set(successful.map((r) => r.skill));
2642
2755
  for (const skill of selectedSkills) {
@@ -2740,6 +2853,18 @@ async function runAdd(args, options = {}) {
2740
2853
  Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2741
2854
  await promptForFindSkills(options, targetAgents);
2742
2855
  } catch (error) {
2856
+ if (parsed) reportInstallToSunIfNeeded({
2857
+ source: getOwnerRepo(parsed) || parsed.url,
2858
+ sourceUrl: parsed.url,
2859
+ skills: installReportContext?.skills || [],
2860
+ agents: installReportContext?.agents || [],
2861
+ installGlobally: installReportContext?.installGlobally || false,
2862
+ sourceType: installReportContext?.sourceType || parsed.type,
2863
+ skillFiles: installReportContext?.skillFiles,
2864
+ successfulCount: 0,
2865
+ failedCount: 1,
2866
+ errorMessage: error instanceof Error ? error.message : "Unknown error occurred"
2867
+ });
2743
2868
  if (error instanceof GitCloneError) {
2744
2869
  M.error(import_picocolors.default.red("Failed to clone repository"));
2745
2870
  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.3",
3
+ "version": "1.4.5",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {