itismyskillmarket 1.3.12 → 1.3.13
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/README.md +44 -0
- package/dist/index.js +906 -524
- package/gui/app.js +452 -0
- package/gui/index.html +14 -1
- package/gui/style.css +261 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1548,434 +1548,115 @@ import { createServer } from "http";
|
|
|
1548
1548
|
import { readFileSync, existsSync as existsSync4 } from "fs";
|
|
1549
1549
|
import { join as join4, extname, dirname } from "path";
|
|
1550
1550
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
}
|
|
1564
|
-
function setCache(key, data, ttlMs = 6e4) {
|
|
1565
|
-
cache.set(key, { data, expiry: Date.now() + ttlMs });
|
|
1566
|
-
}
|
|
1567
|
-
async function throttledMap(items, fn, concurrency = 3) {
|
|
1568
|
-
const results = [];
|
|
1569
|
-
for (let i = 0; i < items.length; i += concurrency) {
|
|
1570
|
-
const batch = items.slice(i, i + concurrency);
|
|
1571
|
-
const batchResults = await Promise.all(batch.map((item, idx) => fn(item, i + idx)));
|
|
1572
|
-
results.push(...batchResults);
|
|
1573
|
-
if (i + concurrency < items.length) {
|
|
1574
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
1551
|
+
|
|
1552
|
+
// src/commands/admin.ts
|
|
1553
|
+
import { execSync as execSync2 } from "child_process";
|
|
1554
|
+
async function fetchScopePackages() {
|
|
1555
|
+
const all = /* @__PURE__ */ new Set();
|
|
1556
|
+
for (const scope of SKILL_SCOPES) {
|
|
1557
|
+
try {
|
|
1558
|
+
const { packages } = await searchSkillmarketPackages({ from: 0, size: 100, keyword: scope });
|
|
1559
|
+
for (const p of packages) {
|
|
1560
|
+
if (p.startsWith(scope)) all.add(p);
|
|
1561
|
+
}
|
|
1562
|
+
} catch {
|
|
1575
1563
|
}
|
|
1576
1564
|
}
|
|
1577
|
-
return
|
|
1578
|
-
}
|
|
1579
|
-
var MIME_TYPES = {
|
|
1580
|
-
".html": "text/html; charset=utf-8",
|
|
1581
|
-
".js": "application/javascript; charset=utf-8",
|
|
1582
|
-
".css": "text/css; charset=utf-8",
|
|
1583
|
-
".json": "application/json; charset=utf-8",
|
|
1584
|
-
".png": "image/png",
|
|
1585
|
-
".svg": "image/svg+xml",
|
|
1586
|
-
".ico": "image/x-icon"
|
|
1587
|
-
};
|
|
1588
|
-
function jsonResponse(res, status, data) {
|
|
1589
|
-
res.writeHead(status, {
|
|
1590
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
1591
|
-
"Access-Control-Allow-Origin": "*"
|
|
1592
|
-
});
|
|
1593
|
-
res.end(JSON.stringify(data));
|
|
1594
|
-
}
|
|
1595
|
-
function parseBody(req) {
|
|
1596
|
-
return new Promise((resolve2, reject) => {
|
|
1597
|
-
let body = "";
|
|
1598
|
-
req.on("data", (chunk) => {
|
|
1599
|
-
body += chunk.toString();
|
|
1600
|
-
});
|
|
1601
|
-
req.on("end", () => {
|
|
1602
|
-
if (!body) return resolve2({});
|
|
1603
|
-
try {
|
|
1604
|
-
resolve2(JSON.parse(body));
|
|
1605
|
-
} catch {
|
|
1606
|
-
reject(new Error("Invalid JSON body"));
|
|
1607
|
-
}
|
|
1608
|
-
});
|
|
1609
|
-
req.on("error", reject);
|
|
1610
|
-
});
|
|
1565
|
+
return [...all].sort();
|
|
1611
1566
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
const cacheKey = `search:${search}:limit:${limit}`;
|
|
1622
|
-
let searchResult = getCached(cacheKey);
|
|
1623
|
-
if (!searchResult) {
|
|
1624
|
-
searchResult = await searchSkillmarketPackages({
|
|
1625
|
-
from: 0,
|
|
1626
|
-
size: 100,
|
|
1627
|
-
// 一次拉取更多,避免分页
|
|
1628
|
-
keyword: search || void 0
|
|
1629
|
-
});
|
|
1630
|
-
setCache(cacheKey, searchResult, 3e4);
|
|
1631
|
-
}
|
|
1632
|
-
const { packages, total } = searchResult;
|
|
1633
|
-
let fetchErrors = 0;
|
|
1634
|
-
const skillDetails = await throttledMap(packages, async (pkgName) => {
|
|
1567
|
+
async function adminList() {
|
|
1568
|
+
console.log("\n\u{1F50D} Fetching all published skills...\n");
|
|
1569
|
+
const packages = await fetchScopePackages();
|
|
1570
|
+
if (packages.length === 0) {
|
|
1571
|
+
console.log("No published skills found.");
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
const details = await Promise.all(
|
|
1575
|
+
packages.map(async (pkg) => {
|
|
1635
1576
|
try {
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
if (info) setCache(pkgCacheKey, info, 3e4);
|
|
1641
|
-
}
|
|
1642
|
-
if (!info) {
|
|
1643
|
-
fetchErrors++;
|
|
1644
|
-
return null;
|
|
1645
|
-
}
|
|
1646
|
-
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
1647
|
-
const pkg = info.versions?.[latestVersion];
|
|
1648
|
-
const meta = pkg?.skillmarket;
|
|
1577
|
+
const info = await fetchNpmPackage(pkg);
|
|
1578
|
+
if (!info) return null;
|
|
1579
|
+
const ver = info["dist-tags"]?.latest || "?";
|
|
1580
|
+
const p = info.versions?.[ver];
|
|
1649
1581
|
return {
|
|
1650
|
-
id: meta?.id || info.name.replace(/^@[^/]+\//, ""),
|
|
1651
1582
|
name: info.name,
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
platforms:
|
|
1656
|
-
|
|
1657
|
-
homepage: pkg?.homepage || "",
|
|
1658
|
-
repository: pkg?.repository?.url || ""
|
|
1583
|
+
version: ver,
|
|
1584
|
+
description: p?.description || "",
|
|
1585
|
+
hasSkillmarket: !!p?.skillmarket,
|
|
1586
|
+
platforms: (p?.skillmarket?.platforms || []).join(", "),
|
|
1587
|
+
updated: info.time?.[ver] || ""
|
|
1659
1588
|
};
|
|
1660
1589
|
} catch {
|
|
1661
|
-
fetchErrors++;
|
|
1662
1590
|
return null;
|
|
1663
1591
|
}
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
}
|
|
1677
|
-
};
|
|
1678
|
-
API_ROUTES.GET["/api/installed"] = async (_req, res, _url) => {
|
|
1679
|
-
try {
|
|
1680
|
-
const skills = await getInstalledSkills();
|
|
1681
|
-
jsonResponse(res, 200, skills.map((s) => ({
|
|
1682
|
-
id: s.id,
|
|
1683
|
-
displayName: s.id,
|
|
1684
|
-
version: s.version,
|
|
1685
|
-
installedAt: s.installedAt,
|
|
1686
|
-
platforms: s.platforms
|
|
1687
|
-
})));
|
|
1688
|
-
} catch (err) {
|
|
1689
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
1592
|
+
})
|
|
1593
|
+
);
|
|
1594
|
+
const valid = details.filter(Boolean);
|
|
1595
|
+
console.log(`\u{1F4E6} ${valid.length} published skill(s):
|
|
1596
|
+
`);
|
|
1597
|
+
valid.sort((a, b) => a.name.localeCompare(b.name));
|
|
1598
|
+
for (const d of valid) {
|
|
1599
|
+
const flag = d.hasSkillmarket ? "\u2705" : "\u{1F4E6}";
|
|
1600
|
+
console.log(` ${flag} ${d.name}@${d.version}`);
|
|
1601
|
+
if (d.description) console.log(` ${d.description.slice(0, 80)}`);
|
|
1602
|
+
if (d.platforms) console.log(` Platforms: ${d.platforms}`);
|
|
1603
|
+
console.log();
|
|
1690
1604
|
}
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
id: adapter.id,
|
|
1702
|
-
name: adapter.name,
|
|
1703
|
-
available: !!isAvailable,
|
|
1704
|
-
installedCount: Array.isArray(installed) ? installed.length : 0
|
|
1705
|
-
};
|
|
1706
|
-
})
|
|
1707
|
-
);
|
|
1708
|
-
jsonResponse(res, 200, platforms);
|
|
1709
|
-
} catch (err) {
|
|
1710
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
1605
|
+
}
|
|
1606
|
+
async function adminInfo(skillId) {
|
|
1607
|
+
console.log(`
|
|
1608
|
+
\u{1F50D} Fetching detailed info for "${skillId}"...
|
|
1609
|
+
`);
|
|
1610
|
+
const info = await fetchSkillPackage(skillId);
|
|
1611
|
+
if (!info) {
|
|
1612
|
+
console.error(`\u274C Skill "${skillId}" not found in any configured scope.`);
|
|
1613
|
+
console.log(` Scopes checked: ${SKILL_SCOPES.join(", ")}`);
|
|
1614
|
+
process.exit(1);
|
|
1711
1615
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1616
|
+
const latestVer = info["dist-tags"]?.latest || "unknown";
|
|
1617
|
+
const latestPkg = info.versions?.[latestVer];
|
|
1618
|
+
const meta = latestPkg?.skillmarket;
|
|
1619
|
+
const allVersions = Object.keys(info.versions || {});
|
|
1620
|
+
console.log(`\u{1F4E6} ${info.name}`);
|
|
1621
|
+
console.log(` Description: ${info.description || "N/A"}`);
|
|
1622
|
+
console.log(` Latest: ${latestVer}`);
|
|
1623
|
+
console.log(` Total versions: ${allVersions.length}`);
|
|
1624
|
+
console.log(` Modified: ${info.time?.modified || "N/A"}`);
|
|
1625
|
+
console.log(` Author: ${info.author?.name || latestPkg?.author?.name || "N/A"}`);
|
|
1626
|
+
console.log(` License: ${info.license || latestPkg?.license || "N/A"}`);
|
|
1627
|
+
if (meta) {
|
|
1628
|
+
console.log(`
|
|
1629
|
+
\u{1F4CB} SkillMarket Metadata:`);
|
|
1630
|
+
if (meta.id) console.log(` ID: ${meta.id}`);
|
|
1631
|
+
if (meta.displayName) console.log(` Display Name: ${meta.displayName}`);
|
|
1632
|
+
if (meta.description) console.log(` Description: ${meta.description}`);
|
|
1633
|
+
if (meta.platforms && meta.platforms.length > 0) {
|
|
1634
|
+
console.log(` Platforms: ${meta.platforms.join(", ")}`);
|
|
1635
|
+
const unknown = meta.platforms.filter((p) => !PLATFORMS.includes(p));
|
|
1636
|
+
if (unknown.length > 0) {
|
|
1637
|
+
console.log(` \u26A0\uFE0F Unknown platforms: ${unknown.join(", ")}`);
|
|
1638
|
+
}
|
|
1719
1639
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
}
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
version: latestVersion,
|
|
1741
|
-
platforms: meta?.platforms || [],
|
|
1742
|
-
versions: recentVersions,
|
|
1743
|
-
author: info.author?.name || pkg?.author?.name || "",
|
|
1744
|
-
license: info.license || "",
|
|
1745
|
-
homepage: pkg?.homepage || "",
|
|
1746
|
-
repository: pkg?.repository?.url || "",
|
|
1747
|
-
readme: info.readme || ""
|
|
1748
|
-
});
|
|
1749
|
-
} catch (err) {
|
|
1750
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
1751
|
-
}
|
|
1752
|
-
};
|
|
1753
|
-
API_ROUTES.GET["/api/config"] = async (_req, res, _url) => {
|
|
1754
|
-
jsonResponse(res, 200, {
|
|
1755
|
-
npmScope: NPM_SCOPE,
|
|
1756
|
-
npmScopeFallback: NPM_SCOPE_FALLBACK,
|
|
1757
|
-
npmRegistry: NPM_REGISTRY,
|
|
1758
|
-
skmUrl: SKM_URL,
|
|
1759
|
-
skillScopes: SKILL_SCOPES
|
|
1760
|
-
});
|
|
1761
|
-
};
|
|
1762
|
-
API_ROUTES.POST["/api/install"] = async (req, res, _url) => {
|
|
1763
|
-
try {
|
|
1764
|
-
const body = await parseBody(req);
|
|
1765
|
-
const skillId = String(body.skillId || "");
|
|
1766
|
-
const version = body.version ? String(body.version) : void 0;
|
|
1767
|
-
const platform = body.platform ? String(body.platform) : void 0;
|
|
1768
|
-
if (!skillId) {
|
|
1769
|
-
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
await installSkill(skillId, version, {
|
|
1773
|
-
platforms: platform ? [platform] : void 0,
|
|
1774
|
-
force: true
|
|
1775
|
-
});
|
|
1776
|
-
jsonResponse(res, 200, { success: true, message: `${skillId} installed successfully` });
|
|
1777
|
-
} catch (err) {
|
|
1778
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
1779
|
-
}
|
|
1780
|
-
};
|
|
1781
|
-
API_ROUTES.POST["/api/uninstall"] = async (req, res, _url) => {
|
|
1782
|
-
try {
|
|
1783
|
-
const body = await parseBody(req);
|
|
1784
|
-
const skillId = String(body.skillId || "");
|
|
1785
|
-
const platform = body.platform ? String(body.platform) : void 0;
|
|
1786
|
-
if (!skillId) {
|
|
1787
|
-
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
await uninstallSkill(skillId, {
|
|
1791
|
-
platforms: platform ? [platform] : void 0,
|
|
1792
|
-
yes: true
|
|
1793
|
-
});
|
|
1794
|
-
jsonResponse(res, 200, { success: true, message: `${skillId} uninstalled successfully` });
|
|
1795
|
-
} catch (err) {
|
|
1796
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
1797
|
-
}
|
|
1798
|
-
};
|
|
1799
|
-
API_ROUTES.POST["/api/update"] = async (req, res, _url) => {
|
|
1800
|
-
try {
|
|
1801
|
-
const body = await parseBody(req);
|
|
1802
|
-
const skillId = body.skillId ? String(body.skillId) : void 0;
|
|
1803
|
-
await updateSkill(skillId);
|
|
1804
|
-
const msg = skillId ? `${skillId} updated successfully` : "All skills updated successfully";
|
|
1805
|
-
jsonResponse(res, 200, { success: true, message: msg });
|
|
1806
|
-
} catch (err) {
|
|
1807
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
1808
|
-
}
|
|
1809
|
-
};
|
|
1810
|
-
function serveStaticFile(res, filePath) {
|
|
1811
|
-
if (!existsSync4(filePath)) {
|
|
1812
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1813
|
-
res.end("Not Found");
|
|
1814
|
-
return;
|
|
1815
|
-
}
|
|
1816
|
-
const content = readFileSync(filePath);
|
|
1817
|
-
const ext = extname(filePath);
|
|
1818
|
-
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
1819
|
-
res.writeHead(200, {
|
|
1820
|
-
"Content-Type": mime,
|
|
1821
|
-
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
1822
|
-
"Pragma": "no-cache",
|
|
1823
|
-
"Expires": "0"
|
|
1824
|
-
});
|
|
1825
|
-
res.end(content);
|
|
1826
|
-
}
|
|
1827
|
-
async function handleRequest(req, res) {
|
|
1828
|
-
const method = req.method || "GET";
|
|
1829
|
-
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
1830
|
-
const pathname = url.pathname;
|
|
1831
|
-
const routeHandler = API_ROUTES[method]?.[pathname];
|
|
1832
|
-
if (routeHandler) {
|
|
1833
|
-
try {
|
|
1834
|
-
await routeHandler(req, res, url);
|
|
1835
|
-
} catch (err) {
|
|
1836
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
1837
|
-
}
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
1840
|
-
if (pathname.startsWith("/api/")) {
|
|
1841
|
-
jsonResponse(res, 404, { error: `Unknown API endpoint: ${method} ${pathname}` });
|
|
1842
|
-
return;
|
|
1843
|
-
}
|
|
1844
|
-
const filePath = join4(guiDir, pathname === "/" ? "index.html" : pathname);
|
|
1845
|
-
if (filePath.startsWith(guiDir)) {
|
|
1846
|
-
serveStaticFile(res, filePath);
|
|
1847
|
-
} else {
|
|
1848
|
-
res.writeHead(403);
|
|
1849
|
-
res.end("Forbidden");
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
function startGuiServer(port = 18770) {
|
|
1853
|
-
const server = createServer(handleRequest);
|
|
1854
|
-
server.listen(port, () => {
|
|
1855
|
-
console.log(`
|
|
1856
|
-
\u{1F680} SkillMarket GUI started!`);
|
|
1857
|
-
console.log(` Local: http://localhost:${port}`);
|
|
1858
|
-
console.log(`
|
|
1859
|
-
Press Ctrl+C to stop
|
|
1860
|
-
`);
|
|
1861
|
-
});
|
|
1862
|
-
server.on("error", (err) => {
|
|
1863
|
-
if (err.code === "EADDRINUSE") {
|
|
1864
|
-
console.error(`\u274C Port ${port} is already in use. Try: skm gui ${port + 1}`);
|
|
1865
|
-
} else {
|
|
1866
|
-
console.error("\u274C Failed to start GUI server:", err.message);
|
|
1867
|
-
}
|
|
1868
|
-
process.exit(1);
|
|
1869
|
-
});
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
// src/commands/admin.ts
|
|
1873
|
-
async function fetchScopePackages() {
|
|
1874
|
-
const all = /* @__PURE__ */ new Set();
|
|
1875
|
-
for (const scope of SKILL_SCOPES) {
|
|
1876
|
-
try {
|
|
1877
|
-
const { packages } = await searchSkillmarketPackages({ from: 0, size: 100, keyword: scope });
|
|
1878
|
-
for (const p of packages) {
|
|
1879
|
-
if (p.startsWith(scope)) all.add(p);
|
|
1880
|
-
}
|
|
1881
|
-
} catch {
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
return [...all].sort();
|
|
1885
|
-
}
|
|
1886
|
-
async function adminList() {
|
|
1887
|
-
console.log("\n\u{1F50D} Fetching all published skills...\n");
|
|
1888
|
-
const packages = await fetchScopePackages();
|
|
1889
|
-
if (packages.length === 0) {
|
|
1890
|
-
console.log("No published skills found.");
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
const details = await Promise.all(
|
|
1894
|
-
packages.map(async (pkg) => {
|
|
1895
|
-
try {
|
|
1896
|
-
const info = await fetchNpmPackage(pkg);
|
|
1897
|
-
if (!info) return null;
|
|
1898
|
-
const ver = info["dist-tags"]?.latest || "?";
|
|
1899
|
-
const p = info.versions?.[ver];
|
|
1900
|
-
return {
|
|
1901
|
-
name: info.name,
|
|
1902
|
-
version: ver,
|
|
1903
|
-
description: p?.description || "",
|
|
1904
|
-
hasSkillmarket: !!p?.skillmarket,
|
|
1905
|
-
platforms: (p?.skillmarket?.platforms || []).join(", "),
|
|
1906
|
-
updated: info.time?.[ver] || ""
|
|
1907
|
-
};
|
|
1908
|
-
} catch {
|
|
1909
|
-
return null;
|
|
1910
|
-
}
|
|
1911
|
-
})
|
|
1912
|
-
);
|
|
1913
|
-
const valid = details.filter(Boolean);
|
|
1914
|
-
console.log(`\u{1F4E6} ${valid.length} published skill(s):
|
|
1915
|
-
`);
|
|
1916
|
-
valid.sort((a, b) => a.name.localeCompare(b.name));
|
|
1917
|
-
for (const d of valid) {
|
|
1918
|
-
const flag = d.hasSkillmarket ? "\u2705" : "\u{1F4E6}";
|
|
1919
|
-
console.log(` ${flag} ${d.name}@${d.version}`);
|
|
1920
|
-
if (d.description) console.log(` ${d.description.slice(0, 80)}`);
|
|
1921
|
-
if (d.platforms) console.log(` Platforms: ${d.platforms}`);
|
|
1922
|
-
console.log();
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
async function adminInfo(skillId) {
|
|
1926
|
-
console.log(`
|
|
1927
|
-
\u{1F50D} Fetching detailed info for "${skillId}"...
|
|
1928
|
-
`);
|
|
1929
|
-
const info = await fetchSkillPackage(skillId);
|
|
1930
|
-
if (!info) {
|
|
1931
|
-
console.error(`\u274C Skill "${skillId}" not found in any configured scope.`);
|
|
1932
|
-
console.log(` Scopes checked: ${SKILL_SCOPES.join(", ")}`);
|
|
1933
|
-
process.exit(1);
|
|
1934
|
-
}
|
|
1935
|
-
const latestVer = info["dist-tags"]?.latest || "unknown";
|
|
1936
|
-
const latestPkg = info.versions?.[latestVer];
|
|
1937
|
-
const meta = latestPkg?.skillmarket;
|
|
1938
|
-
const allVersions = Object.keys(info.versions || {});
|
|
1939
|
-
console.log(`\u{1F4E6} ${info.name}`);
|
|
1940
|
-
console.log(` Description: ${info.description || "N/A"}`);
|
|
1941
|
-
console.log(` Latest: ${latestVer}`);
|
|
1942
|
-
console.log(` Total versions: ${allVersions.length}`);
|
|
1943
|
-
console.log(` Modified: ${info.time?.modified || "N/A"}`);
|
|
1944
|
-
console.log(` Author: ${info.author?.name || latestPkg?.author?.name || "N/A"}`);
|
|
1945
|
-
console.log(` License: ${info.license || latestPkg?.license || "N/A"}`);
|
|
1946
|
-
if (meta) {
|
|
1947
|
-
console.log(`
|
|
1948
|
-
\u{1F4CB} SkillMarket Metadata:`);
|
|
1949
|
-
if (meta.id) console.log(` ID: ${meta.id}`);
|
|
1950
|
-
if (meta.displayName) console.log(` Display Name: ${meta.displayName}`);
|
|
1951
|
-
if (meta.description) console.log(` Description: ${meta.description}`);
|
|
1952
|
-
if (meta.platforms && meta.platforms.length > 0) {
|
|
1953
|
-
console.log(` Platforms: ${meta.platforms.join(", ")}`);
|
|
1954
|
-
const unknown = meta.platforms.filter((p) => !PLATFORMS.includes(p));
|
|
1955
|
-
if (unknown.length > 0) {
|
|
1956
|
-
console.log(` \u26A0\uFE0F Unknown platforms: ${unknown.join(", ")}`);
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
} else {
|
|
1960
|
-
console.log(`
|
|
1961
|
-
\u26A0\uFE0F No SkillMarket metadata (not a skillmarket skill)`);
|
|
1962
|
-
}
|
|
1963
|
-
console.log(`
|
|
1964
|
-
\u{1F4C5} Version History:`);
|
|
1965
|
-
for (const ver of allVersions.slice().reverse()) {
|
|
1966
|
-
const v = info.versions?.[ver];
|
|
1967
|
-
const time = info.time?.[ver] ? new Date(info.time[ver]).toLocaleDateString() : "?";
|
|
1968
|
-
const size = v?.dist?.unpackedSize ? ` (${(v.dist.unpackedSize / 1024).toFixed(1)} KB)` : "";
|
|
1969
|
-
const tag = ver === latestVer ? " \u2190 latest" : "";
|
|
1970
|
-
console.log(` ${ver}${tag} \u2014 ${time}${size}`);
|
|
1971
|
-
}
|
|
1972
|
-
const tags = info["dist-tags"] || {};
|
|
1973
|
-
const otherTags = Object.entries(tags).filter(([k]) => k !== "latest");
|
|
1974
|
-
if (otherTags.length > 0) {
|
|
1975
|
-
console.log(`
|
|
1976
|
-
\u{1F3F7}\uFE0F dist-tags:`);
|
|
1977
|
-
for (const [tag, ver] of otherTags) {
|
|
1978
|
-
console.log(` ${tag}: ${ver}`);
|
|
1640
|
+
} else {
|
|
1641
|
+
console.log(`
|
|
1642
|
+
\u26A0\uFE0F No SkillMarket metadata (not a skillmarket skill)`);
|
|
1643
|
+
}
|
|
1644
|
+
console.log(`
|
|
1645
|
+
\u{1F4C5} Version History:`);
|
|
1646
|
+
for (const ver of allVersions.slice().reverse()) {
|
|
1647
|
+
const v = info.versions?.[ver];
|
|
1648
|
+
const time = info.time?.[ver] ? new Date(info.time[ver]).toLocaleDateString() : "?";
|
|
1649
|
+
const size = v?.dist?.unpackedSize ? ` (${(v.dist.unpackedSize / 1024).toFixed(1)} KB)` : "";
|
|
1650
|
+
const tag = ver === latestVer ? " \u2190 latest" : "";
|
|
1651
|
+
console.log(` ${ver}${tag} \u2014 ${time}${size}`);
|
|
1652
|
+
}
|
|
1653
|
+
const tags = info["dist-tags"] || {};
|
|
1654
|
+
const otherTags = Object.entries(tags).filter(([k]) => k !== "latest");
|
|
1655
|
+
if (otherTags.length > 0) {
|
|
1656
|
+
console.log(`
|
|
1657
|
+
\u{1F3F7}\uFE0F dist-tags:`);
|
|
1658
|
+
for (const [tag, ver] of otherTags) {
|
|
1659
|
+
console.log(` ${tag}: ${ver}`);
|
|
1979
1660
|
}
|
|
1980
1661
|
}
|
|
1981
1662
|
console.log(`
|
|
@@ -2075,128 +1756,753 @@ async function adminStats() {
|
|
|
2075
1756
|
mostRecent = { name, date: modTime };
|
|
2076
1757
|
}
|
|
2077
1758
|
}
|
|
2078
|
-
console.log(`\u{1F4E6} Total published skills: ${totalSkills}`);
|
|
2079
|
-
console.log(`\u{1F4DD} Total versions: ${totalVersions}`);
|
|
2080
|
-
console.log(` Avg versions/skill: ${(totalVersions / totalSkills).toFixed(1)}`);
|
|
2081
|
-
console.log(`\u{1F4CB} Skills with skillmarket metadata: ${withMetadata}/${totalSkills}`);
|
|
2082
|
-
console.log(`\u{1F4BE} Total unpacked size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
|
|
2083
|
-
console.log(`\u{1F527} Platforms covered: ${platformSet.size} (${[...platformSet].join(", ")})`);
|
|
2084
|
-
console.log(`\u{1F3C6} Most versions: ${mostVersions.name} (${mostVersions.count})`);
|
|
2085
|
-
if (mostRecent.date) {
|
|
2086
|
-
console.log(`\u{1F550} Most recent update: ${mostRecent.name} (${new Date(mostRecent.date).toLocaleDateString()})`);
|
|
1759
|
+
console.log(`\u{1F4E6} Total published skills: ${totalSkills}`);
|
|
1760
|
+
console.log(`\u{1F4DD} Total versions: ${totalVersions}`);
|
|
1761
|
+
console.log(` Avg versions/skill: ${(totalVersions / totalSkills).toFixed(1)}`);
|
|
1762
|
+
console.log(`\u{1F4CB} Skills with skillmarket metadata: ${withMetadata}/${totalSkills}`);
|
|
1763
|
+
console.log(`\u{1F4BE} Total unpacked size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
|
|
1764
|
+
console.log(`\u{1F527} Platforms covered: ${platformSet.size} (${[...platformSet].join(", ")})`);
|
|
1765
|
+
console.log(`\u{1F3C6} Most versions: ${mostVersions.name} (${mostVersions.count})`);
|
|
1766
|
+
if (mostRecent.date) {
|
|
1767
|
+
console.log(`\u{1F550} Most recent update: ${mostRecent.name} (${new Date(mostRecent.date).toLocaleDateString()})`);
|
|
1768
|
+
}
|
|
1769
|
+
console.log(`\u{1F517} Registry: ${NPM_REGISTRY}`);
|
|
1770
|
+
console.log(`
|
|
1771
|
+
Configured scopes: ${SKILL_SCOPES.join(", ")}`);
|
|
1772
|
+
console.log();
|
|
1773
|
+
}
|
|
1774
|
+
async function adminVerify(skillId) {
|
|
1775
|
+
console.log(`
|
|
1776
|
+
\u{1F50D} Verifying published skill "${skillId}"...
|
|
1777
|
+
`);
|
|
1778
|
+
const info = await fetchSkillPackage(skillId);
|
|
1779
|
+
if (!info) {
|
|
1780
|
+
console.error(`\u274C Skill "${skillId}" not found.`);
|
|
1781
|
+
process.exit(1);
|
|
1782
|
+
}
|
|
1783
|
+
let passed = 0;
|
|
1784
|
+
let failed = 0;
|
|
1785
|
+
let warnings = 0;
|
|
1786
|
+
const nameValid = /^@[^/]+\/[^/]+$/.test(info.name);
|
|
1787
|
+
if (nameValid) {
|
|
1788
|
+
console.log(`\u2705 Package name format: ${info.name}`);
|
|
1789
|
+
passed++;
|
|
1790
|
+
} else {
|
|
1791
|
+
console.log(`\u26A0\uFE0F Package name format unusual: ${info.name}`);
|
|
1792
|
+
warnings++;
|
|
1793
|
+
}
|
|
1794
|
+
const tags = info["dist-tags"] || {};
|
|
1795
|
+
if (tags.latest) {
|
|
1796
|
+
console.log(`\u2705 dist-tags.latest: ${tags.latest}`);
|
|
1797
|
+
passed++;
|
|
1798
|
+
} else {
|
|
1799
|
+
console.log(`\u274C dist-tags.latest missing`);
|
|
1800
|
+
failed++;
|
|
1801
|
+
}
|
|
1802
|
+
const latestVer = tags.latest;
|
|
1803
|
+
const latestPkg = latestVer ? info.versions?.[latestVer] : void 0;
|
|
1804
|
+
if (latestPkg) {
|
|
1805
|
+
console.log(`\u2705 Latest version ${latestVer} exists in versions`);
|
|
1806
|
+
passed++;
|
|
1807
|
+
} else {
|
|
1808
|
+
console.log(`\u274C Latest version ${latestVer} not found in versions object`);
|
|
1809
|
+
failed++;
|
|
1810
|
+
}
|
|
1811
|
+
const meta = latestPkg?.skillmarket;
|
|
1812
|
+
if (meta) {
|
|
1813
|
+
console.log(`\u2705 Has skillmarket metadata`);
|
|
1814
|
+
const checks = [
|
|
1815
|
+
{ name: "id", ok: !!meta.id },
|
|
1816
|
+
{ name: "displayName", ok: !!meta.displayName },
|
|
1817
|
+
{ name: "platforms (array)", ok: Array.isArray(meta.platforms) && meta.platforms.length > 0 }
|
|
1818
|
+
];
|
|
1819
|
+
for (const c of checks) {
|
|
1820
|
+
if (c.ok) {
|
|
1821
|
+
console.log(` \u2705 skillmarket.${c.name}`);
|
|
1822
|
+
passed++;
|
|
1823
|
+
} else {
|
|
1824
|
+
console.log(` \u26A0\uFE0F skillmarket.${c.name} missing or empty`);
|
|
1825
|
+
warnings++;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
if (meta.platforms) {
|
|
1829
|
+
const unknown = meta.platforms.filter(
|
|
1830
|
+
(p) => !PLATFORMS.includes(p)
|
|
1831
|
+
);
|
|
1832
|
+
if (unknown.length > 0) {
|
|
1833
|
+
console.log(` \u26A0\uFE0F Unknown platforms: ${unknown.join(", ")}`);
|
|
1834
|
+
warnings++;
|
|
1835
|
+
} else {
|
|
1836
|
+
console.log(` \u2705 All platforms recognized`);
|
|
1837
|
+
passed++;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
} else {
|
|
1841
|
+
console.log(`\u26A0\uFE0F No skillmarket metadata (not a skillmarket-formatted skill)`);
|
|
1842
|
+
warnings++;
|
|
1843
|
+
}
|
|
1844
|
+
if (latestPkg?.description) {
|
|
1845
|
+
console.log(`\u2705 Has description (${latestPkg.description.length} chars)`);
|
|
1846
|
+
passed++;
|
|
1847
|
+
} else {
|
|
1848
|
+
console.log(`\u26A0\uFE0F No description`);
|
|
1849
|
+
warnings++;
|
|
1850
|
+
}
|
|
1851
|
+
if (info.license || latestPkg?.license) {
|
|
1852
|
+
console.log(`\u2705 License: ${info.license || latestPkg?.license}`);
|
|
1853
|
+
passed++;
|
|
1854
|
+
} else {
|
|
1855
|
+
console.log(`\u26A0\uFE0F No license specified`);
|
|
1856
|
+
warnings++;
|
|
1857
|
+
}
|
|
1858
|
+
if (latestPkg?.dist?.unpackedSize) {
|
|
1859
|
+
const sizeKB = (latestPkg.dist.unpackedSize / 1024).toFixed(1);
|
|
1860
|
+
console.log(`\u2705 Package size: ${sizeKB} KB (unpacked)`);
|
|
1861
|
+
passed++;
|
|
1862
|
+
}
|
|
1863
|
+
const versionCount = Object.keys(info.versions || {}).length;
|
|
1864
|
+
console.log(`\u2139\uFE0F Total versions: ${versionCount}`);
|
|
1865
|
+
const total = passed + failed;
|
|
1866
|
+
console.log(`
|
|
1867
|
+
\u{1F4CA} Verification Result:`);
|
|
1868
|
+
console.log(` \u2705 Passed: ${passed}`);
|
|
1869
|
+
console.log(` \u26A0\uFE0F Warnings: ${warnings}`);
|
|
1870
|
+
console.log(` \u274C Failed: ${failed}`);
|
|
1871
|
+
if (failed === 0) {
|
|
1872
|
+
console.log(`
|
|
1873
|
+
\u2705 Skill "${skillId}" is valid!
|
|
1874
|
+
`);
|
|
1875
|
+
} else {
|
|
1876
|
+
console.log(`
|
|
1877
|
+
\u26A0\uFE0F Skill "${skillId}" has issues that need attention.
|
|
1878
|
+
`);
|
|
1879
|
+
process.exitCode = 1;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
async function resolveFullPackageName(skillId) {
|
|
1883
|
+
if (skillId.startsWith("@")) {
|
|
1884
|
+
const info = await fetchNpmPackage(skillId);
|
|
1885
|
+
if (info) return skillId;
|
|
1886
|
+
throw new Error(`Package "${skillId}" not found in npm registry`);
|
|
1887
|
+
}
|
|
1888
|
+
for (const scope of SKILL_SCOPES) {
|
|
1889
|
+
const fullName = `${scope}/${skillId}`;
|
|
1890
|
+
const info = await fetchNpmPackage(fullName);
|
|
1891
|
+
if (info) return fullName;
|
|
1892
|
+
}
|
|
1893
|
+
throw new Error(
|
|
1894
|
+
`Could not resolve "${skillId}" to any known scope.
|
|
1895
|
+
Scopes checked: ${SKILL_SCOPES.join(", ")}`
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1898
|
+
function npmExec(command) {
|
|
1899
|
+
try {
|
|
1900
|
+
return execSync2(command, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
1901
|
+
} catch (err) {
|
|
1902
|
+
const msg = err.stderr?.toString() || err.message || "Unknown error";
|
|
1903
|
+
throw new Error(`npm command failed: ${msg.trim()}`);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
async function adminDeprecate(skillId, options = {}) {
|
|
1907
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
1908
|
+
const version = options.version || "";
|
|
1909
|
+
const message = options.message || "This skill is deprecated. Please use an alternative.";
|
|
1910
|
+
const target = version ? `${pkgName}@${version}` : pkgName;
|
|
1911
|
+
console.log(`
|
|
1912
|
+
\u26A0\uFE0F Deprecating ${target}...
|
|
1913
|
+
`);
|
|
1914
|
+
npmExec(`npm deprecate "${target}" "${message}"`);
|
|
1915
|
+
console.log(`\u2705 Successfully deprecated ${target}`);
|
|
1916
|
+
console.log(` Message: "${message}"
|
|
1917
|
+
`);
|
|
1918
|
+
}
|
|
1919
|
+
async function adminUnpublish(skillId, options = {}) {
|
|
1920
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
1921
|
+
const version = options.version;
|
|
1922
|
+
let target;
|
|
1923
|
+
if (version) {
|
|
1924
|
+
target = `${pkgName}@${version}`;
|
|
1925
|
+
console.log(`
|
|
1926
|
+
\u{1F5D1}\uFE0F Unpublishing ${target}...
|
|
1927
|
+
`);
|
|
1928
|
+
} else {
|
|
1929
|
+
target = pkgName;
|
|
1930
|
+
console.log(`
|
|
1931
|
+
\u{1F5D1}\uFE0F Unpublishing entire package ${target}...
|
|
1932
|
+
`);
|
|
1933
|
+
if (!options.force) {
|
|
1934
|
+
throw new Error(
|
|
1935
|
+
"Unpublishing entire package requires --force flag.\n Usage: skm admin unpublish <skill> --force\n Or unpublish a specific version: skm admin unpublish <skill> --version <ver>"
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
const forceFlag = options.force ? " --force" : "";
|
|
1940
|
+
npmExec(`npm unpublish "${target}"${forceFlag}`);
|
|
1941
|
+
if (version) {
|
|
1942
|
+
console.log(`\u2705 Successfully unpublished ${target}
|
|
1943
|
+
`);
|
|
1944
|
+
} else {
|
|
1945
|
+
console.log(`\u2705 Successfully unpublished entire package ${target}
|
|
1946
|
+
`);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
async function adminTagSet(skillId, tag, version) {
|
|
1950
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
1951
|
+
console.log(`
|
|
1952
|
+
\u{1F3F7}\uFE0F Setting dist-tag "${tag}" for ${pkgName}@${version}...
|
|
1953
|
+
`);
|
|
1954
|
+
npmExec(`npm dist-tag add "${pkgName}@${version}" "${tag}"`);
|
|
1955
|
+
console.log(`\u2705 dist-tag "${tag}" set to ${pkgName}@${version}
|
|
1956
|
+
`);
|
|
1957
|
+
}
|
|
1958
|
+
async function adminTagRemove(skillId, tag) {
|
|
1959
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
1960
|
+
console.log(`
|
|
1961
|
+
\u{1F3F7}\uFE0F Removing dist-tag "${tag}" from ${pkgName}...
|
|
1962
|
+
`);
|
|
1963
|
+
npmExec(`npm dist-tag rm "${pkgName}" "${tag}"`);
|
|
1964
|
+
console.log(`\u2705 dist-tag "${tag}" removed from ${pkgName}
|
|
1965
|
+
`);
|
|
1966
|
+
}
|
|
1967
|
+
async function adminTagList(skillId) {
|
|
1968
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
1969
|
+
console.log(`
|
|
1970
|
+
\u{1F3F7}\uFE0F dist-tags for ${pkgName}:
|
|
1971
|
+
`);
|
|
1972
|
+
const output = npmExec(`npm dist-tag ls "${pkgName}"`);
|
|
1973
|
+
if (!output) {
|
|
1974
|
+
console.log(" (no dist-tags found)\n");
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
const lines = output.split("\n").filter(Boolean);
|
|
1978
|
+
for (const line of lines) {
|
|
1979
|
+
const [tag, version] = line.split(": ").map((s) => s.trim());
|
|
1980
|
+
const isLatest = tag === "latest" ? " \u2190 latest" : "";
|
|
1981
|
+
console.log(` ${tag}: ${version}${isLatest}`);
|
|
1982
|
+
}
|
|
1983
|
+
console.log();
|
|
1984
|
+
}
|
|
1985
|
+
async function adminOwnerAdd(skillId, user) {
|
|
1986
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
1987
|
+
console.log(`
|
|
1988
|
+
\u{1F465} Adding owner "${user}" to ${pkgName}...
|
|
1989
|
+
`);
|
|
1990
|
+
npmExec(`npm owner add "${user}" "${pkgName}"`);
|
|
1991
|
+
console.log(`\u2705 Owner "${user}" added to ${pkgName}
|
|
1992
|
+
`);
|
|
1993
|
+
}
|
|
1994
|
+
async function adminOwnerRemove(skillId, user) {
|
|
1995
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
1996
|
+
console.log(`
|
|
1997
|
+
\u{1F465} Removing owner "${user}" from ${pkgName}...
|
|
1998
|
+
`);
|
|
1999
|
+
npmExec(`npm owner rm "${user}" "${pkgName}"`);
|
|
2000
|
+
console.log(`\u2705 Owner "${user}" removed from ${pkgName}
|
|
2001
|
+
`);
|
|
2002
|
+
}
|
|
2003
|
+
async function adminAccess(skillId, level) {
|
|
2004
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
2005
|
+
console.log(`
|
|
2006
|
+
\u{1F512} Setting access for ${pkgName} to "${level}"...
|
|
2007
|
+
`);
|
|
2008
|
+
npmExec(`npm access "${level}" "${pkgName}"`);
|
|
2009
|
+
console.log(`\u2705 Access for ${pkgName} set to "${level}"
|
|
2010
|
+
`);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// src/commands/ui.ts
|
|
2014
|
+
var __filename2 = fileURLToPath3(import.meta.url);
|
|
2015
|
+
var __dirname2 = dirname(__filename2);
|
|
2016
|
+
var guiDir = join4(__dirname2, "..", "gui");
|
|
2017
|
+
var cache = /* @__PURE__ */ new Map();
|
|
2018
|
+
function getCached(key) {
|
|
2019
|
+
const entry = cache.get(key);
|
|
2020
|
+
if (!entry) return null;
|
|
2021
|
+
if (Date.now() > entry.expiry) {
|
|
2022
|
+
cache.delete(key);
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
return entry.data;
|
|
2026
|
+
}
|
|
2027
|
+
function setCache(key, data, ttlMs = 6e4) {
|
|
2028
|
+
cache.set(key, { data, expiry: Date.now() + ttlMs });
|
|
2029
|
+
}
|
|
2030
|
+
async function throttledMap(items, fn, concurrency = 3) {
|
|
2031
|
+
const results = [];
|
|
2032
|
+
for (let i = 0; i < items.length; i += concurrency) {
|
|
2033
|
+
const batch = items.slice(i, i + concurrency);
|
|
2034
|
+
const batchResults = await Promise.all(batch.map((item, idx) => fn(item, i + idx)));
|
|
2035
|
+
results.push(...batchResults);
|
|
2036
|
+
if (i + concurrency < items.length) {
|
|
2037
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
return results;
|
|
2041
|
+
}
|
|
2042
|
+
var MIME_TYPES = {
|
|
2043
|
+
".html": "text/html; charset=utf-8",
|
|
2044
|
+
".js": "application/javascript; charset=utf-8",
|
|
2045
|
+
".css": "text/css; charset=utf-8",
|
|
2046
|
+
".json": "application/json; charset=utf-8",
|
|
2047
|
+
".png": "image/png",
|
|
2048
|
+
".svg": "image/svg+xml",
|
|
2049
|
+
".ico": "image/x-icon"
|
|
2050
|
+
};
|
|
2051
|
+
function jsonResponse(res, status, data) {
|
|
2052
|
+
res.writeHead(status, {
|
|
2053
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
2054
|
+
"Access-Control-Allow-Origin": "*"
|
|
2055
|
+
});
|
|
2056
|
+
res.end(JSON.stringify(data));
|
|
2057
|
+
}
|
|
2058
|
+
function parseBody(req) {
|
|
2059
|
+
return new Promise((resolve2, reject) => {
|
|
2060
|
+
let body = "";
|
|
2061
|
+
req.on("data", (chunk) => {
|
|
2062
|
+
body += chunk.toString();
|
|
2063
|
+
});
|
|
2064
|
+
req.on("end", () => {
|
|
2065
|
+
if (!body) return resolve2({});
|
|
2066
|
+
try {
|
|
2067
|
+
resolve2(JSON.parse(body));
|
|
2068
|
+
} catch {
|
|
2069
|
+
reject(new Error("Invalid JSON body"));
|
|
2070
|
+
}
|
|
2071
|
+
});
|
|
2072
|
+
req.on("error", reject);
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
var API_ROUTES = {
|
|
2076
|
+
GET: {},
|
|
2077
|
+
POST: {}
|
|
2078
|
+
};
|
|
2079
|
+
API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
|
|
2080
|
+
try {
|
|
2081
|
+
const page = Math.max(1, parseInt(url.searchParams.get("page") || "1"));
|
|
2082
|
+
const limit = Math.min(100, Math.max(1, parseInt(url.searchParams.get("limit") || "20")));
|
|
2083
|
+
const search = url.searchParams.get("search") || "";
|
|
2084
|
+
const cacheKey = `search:${search}:limit:${limit}`;
|
|
2085
|
+
let searchResult = getCached(cacheKey);
|
|
2086
|
+
if (!searchResult) {
|
|
2087
|
+
searchResult = await searchSkillmarketPackages({
|
|
2088
|
+
from: 0,
|
|
2089
|
+
size: 100,
|
|
2090
|
+
// 一次拉取更多,避免分页
|
|
2091
|
+
keyword: search || void 0
|
|
2092
|
+
});
|
|
2093
|
+
setCache(cacheKey, searchResult, 3e4);
|
|
2094
|
+
}
|
|
2095
|
+
const { packages, total } = searchResult;
|
|
2096
|
+
let fetchErrors = 0;
|
|
2097
|
+
const skillDetails = await throttledMap(packages, async (pkgName) => {
|
|
2098
|
+
try {
|
|
2099
|
+
const pkgCacheKey = `pkg:${pkgName}`;
|
|
2100
|
+
let info = getCached(pkgCacheKey);
|
|
2101
|
+
if (!info) {
|
|
2102
|
+
info = await fetchNpmPackage(pkgName);
|
|
2103
|
+
if (info) setCache(pkgCacheKey, info, 3e4);
|
|
2104
|
+
}
|
|
2105
|
+
if (!info) {
|
|
2106
|
+
fetchErrors++;
|
|
2107
|
+
return null;
|
|
2108
|
+
}
|
|
2109
|
+
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
2110
|
+
const pkg = info.versions?.[latestVersion];
|
|
2111
|
+
const meta = pkg?.skillmarket;
|
|
2112
|
+
return {
|
|
2113
|
+
id: meta?.id || info.name.replace(/^@[^/]+\//, ""),
|
|
2114
|
+
name: info.name,
|
|
2115
|
+
displayName: meta?.displayName || info.name,
|
|
2116
|
+
version: latestVersion,
|
|
2117
|
+
description: pkg?.description || "",
|
|
2118
|
+
platforms: meta?.platforms || [],
|
|
2119
|
+
author: info.author?.name || pkg?.author?.name || "",
|
|
2120
|
+
homepage: pkg?.homepage || "",
|
|
2121
|
+
repository: pkg?.repository?.url || ""
|
|
2122
|
+
};
|
|
2123
|
+
} catch {
|
|
2124
|
+
fetchErrors++;
|
|
2125
|
+
return null;
|
|
2126
|
+
}
|
|
2127
|
+
}, 3);
|
|
2128
|
+
const skills = skillDetails.filter(Boolean);
|
|
2129
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
2130
|
+
jsonResponse(res, 200, { skills, page, totalPages, total, fetchErrors });
|
|
2131
|
+
} catch (err) {
|
|
2132
|
+
jsonResponse(res, 500, {
|
|
2133
|
+
error: String(err),
|
|
2134
|
+
skills: [],
|
|
2135
|
+
page: 1,
|
|
2136
|
+
totalPages: 1,
|
|
2137
|
+
total: 0
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
};
|
|
2141
|
+
API_ROUTES.GET["/api/installed"] = async (_req, res, _url) => {
|
|
2142
|
+
try {
|
|
2143
|
+
const skills = await getInstalledSkills();
|
|
2144
|
+
jsonResponse(res, 200, skills.map((s) => ({
|
|
2145
|
+
id: s.id,
|
|
2146
|
+
displayName: s.id,
|
|
2147
|
+
version: s.version,
|
|
2148
|
+
installedAt: s.installedAt,
|
|
2149
|
+
platforms: s.platforms
|
|
2150
|
+
})));
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
API_ROUTES.GET["/api/platforms"] = async (_req, res, _url) => {
|
|
2156
|
+
try {
|
|
2157
|
+
const available = await detectPlatforms();
|
|
2158
|
+
const allAdapters = getAllAdapters();
|
|
2159
|
+
const platforms = await Promise.all(
|
|
2160
|
+
allAdapters.map(async (adapter) => {
|
|
2161
|
+
const isAvailable = available.find((a) => a.id === adapter.id);
|
|
2162
|
+
const installed = await adapter.listInstalled();
|
|
2163
|
+
return {
|
|
2164
|
+
id: adapter.id,
|
|
2165
|
+
name: adapter.name,
|
|
2166
|
+
available: !!isAvailable,
|
|
2167
|
+
installedCount: Array.isArray(installed) ? installed.length : 0
|
|
2168
|
+
};
|
|
2169
|
+
})
|
|
2170
|
+
);
|
|
2171
|
+
jsonResponse(res, 200, platforms);
|
|
2172
|
+
} catch (err) {
|
|
2173
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2174
|
+
}
|
|
2175
|
+
};
|
|
2176
|
+
API_ROUTES.GET["/api/skill-info"] = async (_req, res, url) => {
|
|
2177
|
+
try {
|
|
2178
|
+
const skillName = url.searchParams.get("skill") || "";
|
|
2179
|
+
if (!skillName) {
|
|
2180
|
+
jsonResponse(res, 400, { error: 'Missing "skill" query parameter' });
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
const cacheKey = `skill-info:${skillName}`;
|
|
2184
|
+
let info = getCached(cacheKey);
|
|
2185
|
+
if (!info) {
|
|
2186
|
+
info = await fetchNpmPackage(skillName);
|
|
2187
|
+
if (info) setCache(cacheKey, info, 3e4);
|
|
2188
|
+
}
|
|
2189
|
+
if (!info) {
|
|
2190
|
+
jsonResponse(res, 404, { error: `Skill "${skillName}" not found` });
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
2194
|
+
const pkg = latestVersion ? info.versions?.[latestVersion] : void 0;
|
|
2195
|
+
const meta = pkg?.skillmarket;
|
|
2196
|
+
const versionKeys = Object.keys(info.versions || {});
|
|
2197
|
+
const recentVersions = versionKeys.slice(-20);
|
|
2198
|
+
jsonResponse(res, 200, {
|
|
2199
|
+
id: meta?.id || info.name.replace(/^@[^/]+\//, ""),
|
|
2200
|
+
name: info.name,
|
|
2201
|
+
displayName: meta?.displayName || info.name,
|
|
2202
|
+
description: pkg?.description || "",
|
|
2203
|
+
version: latestVersion,
|
|
2204
|
+
platforms: meta?.platforms || [],
|
|
2205
|
+
versions: recentVersions,
|
|
2206
|
+
author: info.author?.name || pkg?.author?.name || "",
|
|
2207
|
+
license: info.license || "",
|
|
2208
|
+
homepage: pkg?.homepage || "",
|
|
2209
|
+
repository: pkg?.repository?.url || "",
|
|
2210
|
+
readme: info.readme || ""
|
|
2211
|
+
});
|
|
2212
|
+
} catch (err) {
|
|
2213
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
API_ROUTES.GET["/api/config"] = async (_req, res, _url) => {
|
|
2217
|
+
jsonResponse(res, 200, {
|
|
2218
|
+
npmScope: NPM_SCOPE,
|
|
2219
|
+
npmScopeFallback: NPM_SCOPE_FALLBACK,
|
|
2220
|
+
npmRegistry: NPM_REGISTRY,
|
|
2221
|
+
skmUrl: SKM_URL,
|
|
2222
|
+
skillScopes: SKILL_SCOPES
|
|
2223
|
+
});
|
|
2224
|
+
};
|
|
2225
|
+
API_ROUTES.POST["/api/install"] = async (req, res, _url) => {
|
|
2226
|
+
try {
|
|
2227
|
+
const body = await parseBody(req);
|
|
2228
|
+
const skillId = String(body.skillId || "");
|
|
2229
|
+
const version = body.version ? String(body.version) : void 0;
|
|
2230
|
+
const platform = body.platform ? String(body.platform) : void 0;
|
|
2231
|
+
if (!skillId) {
|
|
2232
|
+
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
await installSkill(skillId, version, {
|
|
2236
|
+
platforms: platform ? [platform] : void 0,
|
|
2237
|
+
force: true
|
|
2238
|
+
});
|
|
2239
|
+
jsonResponse(res, 200, { success: true, message: `${skillId} installed successfully` });
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
API_ROUTES.POST["/api/uninstall"] = async (req, res, _url) => {
|
|
2245
|
+
try {
|
|
2246
|
+
const body = await parseBody(req);
|
|
2247
|
+
const skillId = String(body.skillId || "");
|
|
2248
|
+
const platform = body.platform ? String(body.platform) : void 0;
|
|
2249
|
+
if (!skillId) {
|
|
2250
|
+
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
await uninstallSkill(skillId, {
|
|
2254
|
+
platforms: platform ? [platform] : void 0,
|
|
2255
|
+
yes: true
|
|
2256
|
+
});
|
|
2257
|
+
jsonResponse(res, 200, { success: true, message: `${skillId} uninstalled successfully` });
|
|
2258
|
+
} catch (err) {
|
|
2259
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
API_ROUTES.GET["/api/admin/stats"] = async (_req, res, _url) => {
|
|
2263
|
+
try {
|
|
2264
|
+
const packages = await fetchScopePackages();
|
|
2265
|
+
const infos = (await Promise.all(
|
|
2266
|
+
packages.map(async (pkg) => {
|
|
2267
|
+
try {
|
|
2268
|
+
const info = await fetchNpmPackage(pkg);
|
|
2269
|
+
return info ? { name: pkg, info } : null;
|
|
2270
|
+
} catch {
|
|
2271
|
+
return null;
|
|
2272
|
+
}
|
|
2273
|
+
})
|
|
2274
|
+
)).filter(Boolean);
|
|
2275
|
+
let totalVersions = 0;
|
|
2276
|
+
let totalSize = 0;
|
|
2277
|
+
const platformSet = /* @__PURE__ */ new Set();
|
|
2278
|
+
let withMetadata = 0;
|
|
2279
|
+
for (const { info } of infos) {
|
|
2280
|
+
const versions = Object.keys(info.versions || {});
|
|
2281
|
+
totalVersions += versions.length;
|
|
2282
|
+
const latestVer = info["dist-tags"]?.latest;
|
|
2283
|
+
const latestPkg = latestVer ? info.versions?.[latestVer] : void 0;
|
|
2284
|
+
const meta = latestPkg?.skillmarket;
|
|
2285
|
+
if (meta) {
|
|
2286
|
+
withMetadata++;
|
|
2287
|
+
if (meta.platforms) meta.platforms.forEach((p) => platformSet.add(p));
|
|
2288
|
+
}
|
|
2289
|
+
if (latestPkg?.dist?.unpackedSize) totalSize += latestPkg.dist.unpackedSize;
|
|
2290
|
+
}
|
|
2291
|
+
jsonResponse(res, 200, {
|
|
2292
|
+
totalSkills: infos.length,
|
|
2293
|
+
totalVersions,
|
|
2294
|
+
averageVersions: infos.length > 0 ? (totalVersions / infos.length).toFixed(1) : "0",
|
|
2295
|
+
withMetadata,
|
|
2296
|
+
totalSizeMB: (totalSize / 1024 / 1024).toFixed(2),
|
|
2297
|
+
platformCount: platformSet.size,
|
|
2298
|
+
platforms: [...platformSet]
|
|
2299
|
+
});
|
|
2300
|
+
} catch (err) {
|
|
2301
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2302
|
+
}
|
|
2303
|
+
};
|
|
2304
|
+
API_ROUTES.POST["/api/admin/deprecate"] = async (req, res, _url) => {
|
|
2305
|
+
try {
|
|
2306
|
+
const body = await parseBody(req);
|
|
2307
|
+
const skillId = String(body.skillId || "");
|
|
2308
|
+
const version = body.version ? String(body.version) : "";
|
|
2309
|
+
const message = body.message ? String(body.message) : "";
|
|
2310
|
+
if (!skillId) {
|
|
2311
|
+
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
2315
|
+
const target = version ? `${pkgName}@${version}` : pkgName;
|
|
2316
|
+
const deprecateMsg = message || "This skill is deprecated. Please use an alternative.";
|
|
2317
|
+
npmExec(`npm deprecate "${target}" "${deprecateMsg}"`);
|
|
2318
|
+
jsonResponse(res, 200, { success: true, message: `${target} deprecated` });
|
|
2319
|
+
} catch (err) {
|
|
2320
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
API_ROUTES.POST["/api/admin/unpublish"] = async (req, res, _url) => {
|
|
2324
|
+
try {
|
|
2325
|
+
const body = await parseBody(req);
|
|
2326
|
+
const skillId = String(body.skillId || "");
|
|
2327
|
+
const version = body.version ? String(body.version) : "";
|
|
2328
|
+
const force = !!body.force;
|
|
2329
|
+
if (!skillId) {
|
|
2330
|
+
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
2334
|
+
let target;
|
|
2335
|
+
if (version) {
|
|
2336
|
+
target = `${pkgName}@${version}`;
|
|
2337
|
+
} else {
|
|
2338
|
+
if (!force) {
|
|
2339
|
+
jsonResponse(res, 400, { error: "Unpublishing entire package requires force=true" });
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
target = pkgName;
|
|
2343
|
+
}
|
|
2344
|
+
const forceFlag = force ? " --force" : "";
|
|
2345
|
+
npmExec(`npm unpublish "${target}"${forceFlag}`);
|
|
2346
|
+
jsonResponse(res, 200, { success: true, message: `${target} unpublished` });
|
|
2347
|
+
} catch (err) {
|
|
2348
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2349
|
+
}
|
|
2350
|
+
};
|
|
2351
|
+
API_ROUTES.POST["/api/admin/tag"] = async (req, res, _url) => {
|
|
2352
|
+
try {
|
|
2353
|
+
const body = await parseBody(req);
|
|
2354
|
+
const skillId = String(body.skillId || "");
|
|
2355
|
+
const action = String(body.action || "");
|
|
2356
|
+
const tag = body.tag ? String(body.tag) : "";
|
|
2357
|
+
const version = body.version ? String(body.version) : "";
|
|
2358
|
+
if (!skillId || !action) {
|
|
2359
|
+
jsonResponse(res, 400, { error: "Missing skillId or action" });
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
2363
|
+
if (action === "set") {
|
|
2364
|
+
if (!tag || !version) {
|
|
2365
|
+
jsonResponse(res, 400, { error: "Tag set requires tag and version" });
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
npmExec(`npm dist-tag add "${pkgName}@${version}" "${tag}"`);
|
|
2369
|
+
jsonResponse(res, 200, { success: true, message: `Tag "${tag}" set to ${pkgName}@${version}` });
|
|
2370
|
+
} else if (action === "rm") {
|
|
2371
|
+
if (!tag) {
|
|
2372
|
+
jsonResponse(res, 400, { error: "Tag rm requires tag" });
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
npmExec(`npm dist-tag rm "${pkgName}" "${tag}"`);
|
|
2376
|
+
jsonResponse(res, 200, { success: true, message: `Tag "${tag}" removed from ${pkgName}` });
|
|
2377
|
+
} else if (action === "ls") {
|
|
2378
|
+
const output = npmExec(`npm dist-tag ls "${pkgName}"`);
|
|
2379
|
+
const tags = {};
|
|
2380
|
+
if (output) {
|
|
2381
|
+
output.split("\n").filter(Boolean).forEach((line) => {
|
|
2382
|
+
const parts = line.split(": ");
|
|
2383
|
+
if (parts.length >= 2) tags[parts[0].trim()] = parts[1].trim();
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
jsonResponse(res, 200, { success: true, tags, packageName: pkgName });
|
|
2387
|
+
} else {
|
|
2388
|
+
jsonResponse(res, 400, { error: `Unknown action: ${action} (use set/rm/ls)` });
|
|
2389
|
+
}
|
|
2390
|
+
} catch (err) {
|
|
2391
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2087
2392
|
}
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2393
|
+
};
|
|
2394
|
+
API_ROUTES.POST["/api/admin/owner"] = async (req, res, _url) => {
|
|
2395
|
+
try {
|
|
2396
|
+
const body = await parseBody(req);
|
|
2397
|
+
const skillId = String(body.skillId || "");
|
|
2398
|
+
const action = String(body.action || "");
|
|
2399
|
+
const user = String(body.user || "");
|
|
2400
|
+
if (!skillId || !action || !user) {
|
|
2401
|
+
jsonResponse(res, 400, { error: "Missing skillId, action, or user" });
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
2405
|
+
const npmAction = action === "add" ? "add" : "rm";
|
|
2406
|
+
npmExec(`npm owner ${npmAction} "${user}" "${pkgName}"`);
|
|
2407
|
+
jsonResponse(res, 200, {
|
|
2408
|
+
success: true,
|
|
2409
|
+
message: `Owner ${npmAction === "add" ? "added" : "removed"}: ${user} ${npmAction === "add" ? "to" : "from"} ${pkgName}`
|
|
2410
|
+
});
|
|
2411
|
+
} catch (err) {
|
|
2412
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2101
2413
|
}
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2414
|
+
};
|
|
2415
|
+
API_ROUTES.POST["/api/admin/access"] = async (req, res, _url) => {
|
|
2416
|
+
try {
|
|
2417
|
+
const body = await parseBody(req);
|
|
2418
|
+
const skillId = String(body.skillId || "");
|
|
2419
|
+
const level = String(body.level || "");
|
|
2420
|
+
if (!skillId || !level) {
|
|
2421
|
+
jsonResponse(res, 400, { error: "Missing skillId or level" });
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
if (level !== "public" && level !== "restricted") {
|
|
2425
|
+
jsonResponse(res, 400, { error: 'Level must be "public" or "restricted"' });
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
const pkgName = await resolveFullPackageName(skillId);
|
|
2429
|
+
npmExec(`npm access "${level}" "${pkgName}"`);
|
|
2430
|
+
jsonResponse(res, 200, { success: true, message: `Access for ${pkgName} set to "${level}"` });
|
|
2431
|
+
} catch (err) {
|
|
2432
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2112
2433
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2434
|
+
};
|
|
2435
|
+
API_ROUTES.POST["/api/update"] = async (req, res, _url) => {
|
|
2436
|
+
try {
|
|
2437
|
+
const body = await parseBody(req);
|
|
2438
|
+
const skillId = body.skillId ? String(body.skillId) : void 0;
|
|
2439
|
+
await updateSkill(skillId);
|
|
2440
|
+
const msg = skillId ? `${skillId} updated successfully` : "All skills updated successfully";
|
|
2441
|
+
jsonResponse(res, 200, { success: true, message: msg });
|
|
2442
|
+
} catch (err) {
|
|
2443
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2120
2444
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
if (
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
console.log(`\u274C Latest version ${latestVer} not found in versions object`);
|
|
2128
|
-
failed++;
|
|
2445
|
+
};
|
|
2446
|
+
function serveStaticFile(res, filePath) {
|
|
2447
|
+
if (!existsSync4(filePath)) {
|
|
2448
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
2449
|
+
res.end("Not Found");
|
|
2450
|
+
return;
|
|
2129
2451
|
}
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
);
|
|
2151
|
-
if (unknown.length > 0) {
|
|
2152
|
-
console.log(` \u26A0\uFE0F Unknown platforms: ${unknown.join(", ")}`);
|
|
2153
|
-
warnings++;
|
|
2154
|
-
} else {
|
|
2155
|
-
console.log(` \u2705 All platforms recognized`);
|
|
2156
|
-
passed++;
|
|
2157
|
-
}
|
|
2452
|
+
const content = readFileSync(filePath);
|
|
2453
|
+
const ext = extname(filePath);
|
|
2454
|
+
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
2455
|
+
res.writeHead(200, {
|
|
2456
|
+
"Content-Type": mime,
|
|
2457
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
2458
|
+
"Pragma": "no-cache",
|
|
2459
|
+
"Expires": "0"
|
|
2460
|
+
});
|
|
2461
|
+
res.end(content);
|
|
2462
|
+
}
|
|
2463
|
+
async function handleRequest(req, res) {
|
|
2464
|
+
const method = req.method || "GET";
|
|
2465
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
2466
|
+
const pathname = url.pathname;
|
|
2467
|
+
const routeHandler = API_ROUTES[method]?.[pathname];
|
|
2468
|
+
if (routeHandler) {
|
|
2469
|
+
try {
|
|
2470
|
+
await routeHandler(req, res, url);
|
|
2471
|
+
} catch (err) {
|
|
2472
|
+
jsonResponse(res, 500, { error: String(err) });
|
|
2158
2473
|
}
|
|
2159
|
-
|
|
2160
|
-
console.log(`\u26A0\uFE0F No skillmarket metadata (not a skillmarket-formatted skill)`);
|
|
2161
|
-
warnings++;
|
|
2474
|
+
return;
|
|
2162
2475
|
}
|
|
2163
|
-
if (
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
} else {
|
|
2167
|
-
console.log(`\u26A0\uFE0F No description`);
|
|
2168
|
-
warnings++;
|
|
2476
|
+
if (pathname.startsWith("/api/")) {
|
|
2477
|
+
jsonResponse(res, 404, { error: `Unknown API endpoint: ${method} ${pathname}` });
|
|
2478
|
+
return;
|
|
2169
2479
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2480
|
+
const filePath = join4(guiDir, pathname === "/" ? "index.html" : pathname);
|
|
2481
|
+
if (filePath.startsWith(guiDir)) {
|
|
2482
|
+
serveStaticFile(res, filePath);
|
|
2173
2483
|
} else {
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
}
|
|
2177
|
-
if (latestPkg?.dist?.unpackedSize) {
|
|
2178
|
-
const sizeKB = (latestPkg.dist.unpackedSize / 1024).toFixed(1);
|
|
2179
|
-
console.log(`\u2705 Package size: ${sizeKB} KB (unpacked)`);
|
|
2180
|
-
passed++;
|
|
2484
|
+
res.writeHead(403);
|
|
2485
|
+
res.end("Forbidden");
|
|
2181
2486
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
const
|
|
2185
|
-
|
|
2186
|
-
\u{1F4CA} Verification Result:`);
|
|
2187
|
-
console.log(` \u2705 Passed: ${passed}`);
|
|
2188
|
-
console.log(` \u26A0\uFE0F Warnings: ${warnings}`);
|
|
2189
|
-
console.log(` \u274C Failed: ${failed}`);
|
|
2190
|
-
if (failed === 0) {
|
|
2487
|
+
}
|
|
2488
|
+
function startGuiServer(port = 18770) {
|
|
2489
|
+
const server = createServer(handleRequest);
|
|
2490
|
+
server.listen(port, () => {
|
|
2191
2491
|
console.log(`
|
|
2192
|
-
\
|
|
2193
|
-
`);
|
|
2194
|
-
} else {
|
|
2492
|
+
\u{1F680} SkillMarket GUI started!`);
|
|
2493
|
+
console.log(` Local: http://localhost:${port}`);
|
|
2195
2494
|
console.log(`
|
|
2196
|
-
|
|
2495
|
+
Press Ctrl+C to stop
|
|
2197
2496
|
`);
|
|
2198
|
-
|
|
2199
|
-
|
|
2497
|
+
});
|
|
2498
|
+
server.on("error", (err) => {
|
|
2499
|
+
if (err.code === "EADDRINUSE") {
|
|
2500
|
+
console.error(`\u274C Port ${port} is already in use. Try: skm gui ${port + 1}`);
|
|
2501
|
+
} else {
|
|
2502
|
+
console.error("\u274C Failed to start GUI server:", err.message);
|
|
2503
|
+
}
|
|
2504
|
+
process.exit(1);
|
|
2505
|
+
});
|
|
2200
2506
|
}
|
|
2201
2507
|
|
|
2202
2508
|
// src/cli.ts
|
|
@@ -2432,4 +2738,80 @@ admin.command("verify <skill>").description("Verify a published skill structure
|
|
|
2432
2738
|
process.exit(1);
|
|
2433
2739
|
}
|
|
2434
2740
|
});
|
|
2741
|
+
admin.command("deprecate <skill>").description("Deprecate a published skill (or specific version)").option("-v, --version <version>", "Deprecate a specific version only").option("-m, --message <message>", "Deprecation message").action(async (skill, opts) => {
|
|
2742
|
+
try {
|
|
2743
|
+
await adminDeprecate(skill, {
|
|
2744
|
+
version: opts.version,
|
|
2745
|
+
message: opts.message
|
|
2746
|
+
});
|
|
2747
|
+
} catch (err) {
|
|
2748
|
+
console.error("Admin deprecate failed:", err);
|
|
2749
|
+
process.exit(1);
|
|
2750
|
+
}
|
|
2751
|
+
});
|
|
2752
|
+
admin.command("unpublish <skill>").description("Unpublish a skill (or specific version) from npm").option("-v, --version <version>", "Unpublish a specific version only").option("-f, --force", "Force unpublish entire package").action(async (skill, opts) => {
|
|
2753
|
+
try {
|
|
2754
|
+
await adminUnpublish(skill, {
|
|
2755
|
+
version: opts.version,
|
|
2756
|
+
force: opts.force
|
|
2757
|
+
});
|
|
2758
|
+
} catch (err) {
|
|
2759
|
+
console.error("Admin unpublish failed:", err);
|
|
2760
|
+
process.exit(1);
|
|
2761
|
+
}
|
|
2762
|
+
});
|
|
2763
|
+
var adminTag = admin.command("tag").description("Manage dist-tags for a skill");
|
|
2764
|
+
adminTag.command("set <skill> <tag> <version>").description("Set a dist-tag for a specific version").action(async (skill, tag, version) => {
|
|
2765
|
+
try {
|
|
2766
|
+
await adminTagSet(skill, tag, version);
|
|
2767
|
+
} catch (err) {
|
|
2768
|
+
console.error("Admin tag set failed:", err);
|
|
2769
|
+
process.exit(1);
|
|
2770
|
+
}
|
|
2771
|
+
});
|
|
2772
|
+
adminTag.command("rm <skill> <tag>").description("Remove a dist-tag").action(async (skill, tag) => {
|
|
2773
|
+
try {
|
|
2774
|
+
await adminTagRemove(skill, tag);
|
|
2775
|
+
} catch (err) {
|
|
2776
|
+
console.error("Admin tag rm failed:", err);
|
|
2777
|
+
process.exit(1);
|
|
2778
|
+
}
|
|
2779
|
+
});
|
|
2780
|
+
adminTag.command("ls <skill>").description("List all dist-tags for a skill").action(async (skill) => {
|
|
2781
|
+
try {
|
|
2782
|
+
await adminTagList(skill);
|
|
2783
|
+
} catch (err) {
|
|
2784
|
+
console.error("Admin tag ls failed:", err);
|
|
2785
|
+
process.exit(1);
|
|
2786
|
+
}
|
|
2787
|
+
});
|
|
2788
|
+
var adminOwner = admin.command("owner").description("Manage package owners/maintainers");
|
|
2789
|
+
adminOwner.command("add <skill> <user>").description("Add an owner to a skill package").action(async (skill, user) => {
|
|
2790
|
+
try {
|
|
2791
|
+
await adminOwnerAdd(skill, user);
|
|
2792
|
+
} catch (err) {
|
|
2793
|
+
console.error("Admin owner add failed:", err);
|
|
2794
|
+
process.exit(1);
|
|
2795
|
+
}
|
|
2796
|
+
});
|
|
2797
|
+
adminOwner.command("rm <skill> <user>").description("Remove an owner from a skill package").action(async (skill, user) => {
|
|
2798
|
+
try {
|
|
2799
|
+
await adminOwnerRemove(skill, user);
|
|
2800
|
+
} catch (err) {
|
|
2801
|
+
console.error("Admin owner rm failed:", err);
|
|
2802
|
+
process.exit(1);
|
|
2803
|
+
}
|
|
2804
|
+
});
|
|
2805
|
+
admin.command("access <skill> <level>").description("Set package access (public|restricted)").action(async (skill, level) => {
|
|
2806
|
+
try {
|
|
2807
|
+
if (level !== "public" && level !== "restricted") {
|
|
2808
|
+
console.error('\u274C Access level must be "public" or "restricted"');
|
|
2809
|
+
process.exit(1);
|
|
2810
|
+
}
|
|
2811
|
+
await adminAccess(skill, level);
|
|
2812
|
+
} catch (err) {
|
|
2813
|
+
console.error("Admin access failed:", err);
|
|
2814
|
+
process.exit(1);
|
|
2815
|
+
}
|
|
2816
|
+
});
|
|
2435
2817
|
program.parse();
|