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.
- package/dist/cli.mjs +576 -290
- 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
|
-
|
|
380
|
-
|
|
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
|
|
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(`${
|
|
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
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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
|
|
1558
|
-
const
|
|
1559
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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.
|
|
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
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
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
|
-
|
|
2173
|
+
spinner.stop(import_picocolors.default.yellow("Default well-known endpoint unavailable"));
|
|
2021
2174
|
return false;
|
|
2022
2175
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
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
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
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
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
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
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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
|
-
|
|
2237
|
+
selectedSkills = selected;
|
|
2083
2238
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
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
|
-
|
|
2092
|
-
|
|
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
|
-
|
|
2306
|
+
installGlobally = scope;
|
|
2097
2307
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
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
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
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
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
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
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
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
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
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
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
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
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
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
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
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
|
-
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
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
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
M.
|
|
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
|
-
|
|
2264
|
-
|
|
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
|
-
|
|
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));
|