bip-skills 1.4.4 → 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 +379 -260
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -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
  }
@@ -1400,6 +1421,7 @@ function getAllowedReportingUrl(envVarName) {
1400
1421
  }
1401
1422
  const TELEMETRY_URL = getAllowedReportingUrl("SKILLS_TELEMETRY_URL");
1402
1423
  const AUDIT_URL = getAllowedReportingUrl("SKILLS_AUDIT_URL");
1424
+ const INSTALL_REPORT_URL = isAllowedReportingUrl(DEFAULT_INSTALL_REPORT_URL) ? DEFAULT_INSTALL_REPORT_URL : null;
1403
1425
  let cliVersion = null;
1404
1426
  function isCI() {
1405
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);
@@ -1428,17 +1450,24 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
1428
1450
  return null;
1429
1451
  }
1430
1452
  }
1431
- function track(data) {
1432
- if (!TELEMETRY_URL) return;
1453
+ function sendTelemetry(url, data) {
1433
1454
  if (!isEnabled()) return;
1434
1455
  try {
1435
1456
  const params = new URLSearchParams();
1436
1457
  if (cliVersion) params.set("v", cliVersion);
1437
1458
  if (isCI()) params.set("ci", "1");
1438
1459
  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(() => {});
1460
+ fetch(`${url}?${params.toString()}`).catch(() => {});
1440
1461
  } catch {}
1441
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
+ }
1442
1471
  var ProviderRegistryImpl = class {
1443
1472
  providers = [];
1444
1473
  register(provider) {
@@ -1804,7 +1833,7 @@ function createEmptyLocalLock() {
1804
1833
  skills: {}
1805
1834
  };
1806
1835
  }
1807
- var version$1 = "1.4.4";
1836
+ var version$1 = "1.4.5";
1808
1837
  const isCancelled$1 = (value) => typeof value === "symbol";
1809
1838
  async function isSourcePrivate(source) {
1810
1839
  const ownerRepo = parseOwnerRepo(source);
@@ -1970,6 +1999,33 @@ async function selectAgentsInteractive(options) {
1970
1999
  return selected;
1971
2000
  }
1972
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
+ }
1973
2029
  function isBareSkillNameSource(source) {
1974
2030
  return /^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(source);
1975
2031
  }
@@ -1980,297 +2036,327 @@ function buildFallbackGitSshUrl(source) {
1980
2036
  return `git@${DEFAULT_GIT_FALLBACK_SSH_HOST}:${source.replace(/\.git$/, "")}.git`;
1981
2037
  }
1982
2038
  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) {
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) {
2019
2044
  if (allowFallback) {
2020
- 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"));
2021
2046
  return false;
2022
2047
  }
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}`);
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."));
2026
2050
  process.exit(1);
2027
2051
  }
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);
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(", ")}`));
2048
2057
  }
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);
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);
2062
2069
  }
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");
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`);
2072
2093
  } 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
- })));
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
+ });
2078
2103
  if (pD(selected)) {
2079
2104
  xe("Installation cancelled");
2080
2105
  process.exit(0);
2081
2106
  }
2082
- targetAgents = selected;
2107
+ selectedSkills = selected;
2083
2108
  }
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(", ")}`);
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;
2090
2122
  } else {
2091
- const selected = await selectAgentsInteractive({ global: options.global });
2092
- 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)) {
2093
2173
  xe("Installation cancelled");
2094
2174
  process.exit(0);
2095
2175
  }
2096
- targetAgents = selected;
2176
+ installGlobally = scope;
2097
2177
  }
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);
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;
2117
2197
  }
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);
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);
2137
2210
  }
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);
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)}`);
2170
2220
  }
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
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"
2178
2258
  });
2179
- results.push({
2180
- skill: skill.installName,
2181
- agent: agents[agent].displayName,
2182
- ...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)
2183
2270
  });
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, {
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, {
2220
2275
  source: sourceIdentifier,
2221
2276
  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);
2277
+ sourceUrl: skill.sourceUrl,
2278
+ skillFolderHash: ""
2279
+ });
2280
+ } catch {}
2233
2281
  }
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}`);
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);
2245
2294
  }
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));
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."));
2252
2329
  }
2253
2330
  }
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."));
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)}`);
2259
2335
  }
2260
- }
2261
- if (failed.length > 0) {
2262
2336
  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)}`);
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;
2265
2353
  }
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
2354
  }
2271
2355
  async function runAdd(args, options = {}) {
2272
2356
  let source = args[0] || DEFAULT_WELL_KNOWN_BASE_URL;
2273
2357
  let installTipShown = false;
2358
+ let parsed = null;
2359
+ let installReportContext = null;
2274
2360
  const showInstallTip = () => {
2275
2361
  if (installTipShown) return;
2276
2362
  M.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
@@ -2300,7 +2386,7 @@ async function runAdd(args, options = {}) {
2300
2386
  M.info(`Falling back to git source: ${import_picocolors.default.cyan(source)}`);
2301
2387
  }
2302
2388
  spinner.start("Parsing source...");
2303
- const parsed = parseSource(source);
2389
+ parsed = parseSource(source);
2304
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)}` : ""}`);
2305
2391
  if (parsed.skillFilter) {
2306
2392
  options.skill = options.skill || [];
@@ -2623,6 +2709,15 @@ async function runAdd(args, options = {}) {
2623
2709
  skillFiles[skill.name] = relativePath;
2624
2710
  }
2625
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
+ };
2626
2721
  if (normalizedSource) {
2627
2722
  const ownerRepo = parseOwnerRepo(normalizedSource);
2628
2723
  if (ownerRepo) {
@@ -2643,6 +2738,18 @@ async function runAdd(args, options = {}) {
2643
2738
  skillFiles: JSON.stringify(skillFiles)
2644
2739
  });
2645
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
+ });
2646
2753
  if (successful.length > 0 && installGlobally && normalizedSource) {
2647
2754
  const successfulSkillNames = new Set(successful.map((r) => r.skill));
2648
2755
  for (const skill of selectedSkills) {
@@ -2746,6 +2853,18 @@ async function runAdd(args, options = {}) {
2746
2853
  Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2747
2854
  await promptForFindSkills(options, targetAgents);
2748
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
+ });
2749
2868
  if (error instanceof GitCloneError) {
2750
2869
  M.error(import_picocolors.default.red("Failed to clone repository"));
2751
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.4",
3
+ "version": "1.4.5",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {