skillverse 0.1.5 → 0.1.8
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/bin.js +611 -159
- package/dist/bin.js.map +1 -1
- package/dist/index.js +599 -159
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/prisma/dev.db +0 -0
- package/public/assets/index-C5PzeRR6.css +1 -0
- package/public/assets/index-CHREpWKn.js +23 -0
- package/public/index.html +2 -2
- package/public/assets/index-DVybgO9W.js +0 -20
- package/public/assets/index-li8hN2px.css +0 -1
package/dist/index.js
CHANGED
|
@@ -713,6 +713,32 @@ var init_skillService = __esm({
|
|
|
713
713
|
});
|
|
714
714
|
}
|
|
715
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Sync skills between filesystem and database:
|
|
718
|
+
* 1. Remove orphaned database records (skills in DB but not on disk)
|
|
719
|
+
* 2. Import new skills from disk (skills on disk but not in DB)
|
|
720
|
+
*/
|
|
721
|
+
async syncSkillsWithFileSystem() {
|
|
722
|
+
const { skills: skillsDir } = getPaths();
|
|
723
|
+
const dbSkills = await prisma.skill.findMany();
|
|
724
|
+
let removedCount = 0;
|
|
725
|
+
for (const skill of dbSkills) {
|
|
726
|
+
if (!existsSync2(skill.storagePath)) {
|
|
727
|
+
console.log(`\u{1F5D1}\uFE0F Removing orphaned skill from database: ${skill.name} (path: ${skill.storagePath})`);
|
|
728
|
+
try {
|
|
729
|
+
await prisma.skill.delete({ where: { id: skill.id } });
|
|
730
|
+
removedCount++;
|
|
731
|
+
} catch (error) {
|
|
732
|
+
console.error(`Failed to remove orphaned skill ${skill.name}:`, error);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (removedCount > 0) {
|
|
737
|
+
console.log(`\u2705 Removed ${removedCount} orphaned skill(s) from database.`);
|
|
738
|
+
}
|
|
739
|
+
const importedCount = await this.scanForSkills();
|
|
740
|
+
return { removedCount, importedCount: importedCount || 0 };
|
|
741
|
+
}
|
|
716
742
|
async scanForSkills() {
|
|
717
743
|
const { skills: skillsDir } = getPaths();
|
|
718
744
|
console.log(`Scanning for skills in ${skillsDir}...`);
|
|
@@ -790,8 +816,8 @@ import express from "express";
|
|
|
790
816
|
import cors from "cors";
|
|
791
817
|
import dotenv2 from "dotenv";
|
|
792
818
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
793
|
-
import { dirname as dirname3, join as
|
|
794
|
-
import { mkdir as
|
|
819
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
820
|
+
import { mkdir as mkdir6 } from "fs/promises";
|
|
795
821
|
import { existsSync as existsSync9 } from "fs";
|
|
796
822
|
|
|
797
823
|
// src/routes/skills.ts
|
|
@@ -1092,7 +1118,7 @@ ${relativeSkillsPath}/
|
|
|
1092
1118
|
if (!existsSync3(skillsPath)) {
|
|
1093
1119
|
return [];
|
|
1094
1120
|
}
|
|
1095
|
-
const { readdir: readdir2, stat: stat2, readFile:
|
|
1121
|
+
const { readdir: readdir2, stat: stat2, readFile: readFile4 } = await import("fs/promises");
|
|
1096
1122
|
const items = await readdir2(skillsPath);
|
|
1097
1123
|
const existingSkills = [];
|
|
1098
1124
|
for (const item of items) {
|
|
@@ -1133,7 +1159,7 @@ ${relativeSkillsPath}/
|
|
|
1133
1159
|
if (!existsSync3(SKILLS_DIR)) {
|
|
1134
1160
|
await mkdir2(SKILLS_DIR, { recursive: true });
|
|
1135
1161
|
}
|
|
1136
|
-
const { rename, readFile:
|
|
1162
|
+
const { rename, readFile: readFile4, cp: cp2 } = await import("fs/promises");
|
|
1137
1163
|
const migrated = [];
|
|
1138
1164
|
const errors = [];
|
|
1139
1165
|
for (const skillName of skillNames) {
|
|
@@ -1166,7 +1192,7 @@ ${relativeSkillsPath}/
|
|
|
1166
1192
|
if (existsSync3(skillMdPath)) {
|
|
1167
1193
|
try {
|
|
1168
1194
|
const matter2 = await import("gray-matter");
|
|
1169
|
-
const fileContent = await
|
|
1195
|
+
const fileContent = await readFile4(skillMdPath, "utf-8");
|
|
1170
1196
|
const parsed = matter2.default(fileContent);
|
|
1171
1197
|
description = parsed.data.description || "";
|
|
1172
1198
|
metadata = parsed.data;
|
|
@@ -1248,6 +1274,18 @@ router.get("/", async (req, res, next) => {
|
|
|
1248
1274
|
next(error);
|
|
1249
1275
|
}
|
|
1250
1276
|
});
|
|
1277
|
+
router.post("/sync", async (req, res, next) => {
|
|
1278
|
+
try {
|
|
1279
|
+
const result = await skillService.syncSkillsWithFileSystem();
|
|
1280
|
+
res.json({
|
|
1281
|
+
success: true,
|
|
1282
|
+
data: result,
|
|
1283
|
+
message: `Synced: removed ${result.removedCount} orphan(s), imported ${result.importedCount} new skill(s)`
|
|
1284
|
+
});
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
next(error);
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1251
1289
|
router.get("/:id", async (req, res, next) => {
|
|
1252
1290
|
try {
|
|
1253
1291
|
const skill = await skillService.getSkillById(req.params.id);
|
|
@@ -1424,7 +1462,7 @@ router.post("/:id/refresh-metadata", async (req, res, next) => {
|
|
|
1424
1462
|
});
|
|
1425
1463
|
router.get("/:id/skill-md", async (req, res, next) => {
|
|
1426
1464
|
try {
|
|
1427
|
-
const { readFile:
|
|
1465
|
+
const { readFile: readFile4 } = await import("fs/promises");
|
|
1428
1466
|
const skill = await skillService.getSkillById(req.params.id);
|
|
1429
1467
|
const skillMdPath = join4(skill.storagePath, "SKILL.md");
|
|
1430
1468
|
if (!existsSync4(skillMdPath)) {
|
|
@@ -1436,7 +1474,7 @@ router.get("/:id/skill-md", async (req, res, next) => {
|
|
|
1436
1474
|
}
|
|
1437
1475
|
});
|
|
1438
1476
|
}
|
|
1439
|
-
const content = await
|
|
1477
|
+
const content = await readFile4(skillMdPath, "utf-8");
|
|
1440
1478
|
res.json({
|
|
1441
1479
|
success: true,
|
|
1442
1480
|
data: {
|
|
@@ -1591,7 +1629,6 @@ router2.post("/:id/migrate-skills", async (req, res, next) => {
|
|
|
1591
1629
|
var workspaces_default = router2;
|
|
1592
1630
|
|
|
1593
1631
|
// src/routes/marketplace.ts
|
|
1594
|
-
init_db();
|
|
1595
1632
|
init_skillService();
|
|
1596
1633
|
import { Router as Router3 } from "express";
|
|
1597
1634
|
|
|
@@ -1678,41 +1715,278 @@ var bundleService = {
|
|
|
1678
1715
|
// src/routes/marketplace.ts
|
|
1679
1716
|
init_errorHandler();
|
|
1680
1717
|
init_dist();
|
|
1718
|
+
init_db();
|
|
1681
1719
|
import { existsSync as existsSync6 } from "fs";
|
|
1720
|
+
import { join as join6 } from "path";
|
|
1721
|
+
import { mkdir as mkdir5, rm as rm5, writeFile } from "fs/promises";
|
|
1722
|
+
|
|
1723
|
+
// src/services/githubMarketplaceRegistry.ts
|
|
1724
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
1725
|
+
import { createHash } from "crypto";
|
|
1726
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1727
|
+
function sha256Hex(buf) {
|
|
1728
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
1729
|
+
}
|
|
1730
|
+
function nowIso() {
|
|
1731
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1732
|
+
}
|
|
1733
|
+
function normalizeIndex(index) {
|
|
1734
|
+
const base = {
|
|
1735
|
+
version: typeof index?.version === "number" ? index.version : 1,
|
|
1736
|
+
updatedAt: typeof index?.updatedAt === "string" ? index.updatedAt : nowIso(),
|
|
1737
|
+
skills: Array.isArray(index?.skills) ? index.skills : []
|
|
1738
|
+
};
|
|
1739
|
+
return base;
|
|
1740
|
+
}
|
|
1741
|
+
async function requireOctokit() {
|
|
1742
|
+
const mod = await import("octokit");
|
|
1743
|
+
return mod.Octokit;
|
|
1744
|
+
}
|
|
1745
|
+
var GitHubMarketplaceRegistry = class {
|
|
1746
|
+
constructor(cfg) {
|
|
1747
|
+
this.cfg = cfg;
|
|
1748
|
+
}
|
|
1749
|
+
getIndexPath() {
|
|
1750
|
+
return this.cfg.indexPath || "index.json";
|
|
1751
|
+
}
|
|
1752
|
+
async octokit() {
|
|
1753
|
+
const Octokit = await requireOctokit();
|
|
1754
|
+
return new Octokit({ auth: this.cfg.token });
|
|
1755
|
+
}
|
|
1756
|
+
async fetchIndexFromPages() {
|
|
1757
|
+
const resp = await fetch(this.cfg.indexUrl, { method: "GET" });
|
|
1758
|
+
if (!resp.ok) {
|
|
1759
|
+
throw new Error(`Failed to fetch marketplace index from Pages: ${resp.status} ${resp.statusText}`);
|
|
1760
|
+
}
|
|
1761
|
+
const json = await resp.json();
|
|
1762
|
+
return normalizeIndex(json);
|
|
1763
|
+
}
|
|
1764
|
+
async readIndexFromRepo() {
|
|
1765
|
+
const octokit = await this.octokit();
|
|
1766
|
+
const path = this.getIndexPath();
|
|
1767
|
+
const res = await octokit.rest.repos.getContent({
|
|
1768
|
+
owner: this.cfg.owner,
|
|
1769
|
+
repo: this.cfg.repo,
|
|
1770
|
+
path,
|
|
1771
|
+
ref: this.cfg.branch
|
|
1772
|
+
});
|
|
1773
|
+
if (Array.isArray(res.data) || res.data.type !== "file") {
|
|
1774
|
+
throw new Error(`Invalid index path in repo: ${path}`);
|
|
1775
|
+
}
|
|
1776
|
+
const content = Buffer2.from(res.data.content, res.data.encoding).toString("utf-8");
|
|
1777
|
+
const parsed = JSON.parse(content);
|
|
1778
|
+
return { index: normalizeIndex(parsed), sha: res.data.sha };
|
|
1779
|
+
}
|
|
1780
|
+
async upsertIndexToRepo(index, previousSha) {
|
|
1781
|
+
const octokit = await this.octokit();
|
|
1782
|
+
const path = this.getIndexPath();
|
|
1783
|
+
const body = JSON.stringify(
|
|
1784
|
+
{
|
|
1785
|
+
...index,
|
|
1786
|
+
updatedAt: nowIso()
|
|
1787
|
+
},
|
|
1788
|
+
null,
|
|
1789
|
+
2
|
|
1790
|
+
);
|
|
1791
|
+
const contentBase64 = Buffer2.from(body, "utf-8").toString("base64");
|
|
1792
|
+
await octokit.rest.repos.createOrUpdateFileContents({
|
|
1793
|
+
owner: this.cfg.owner,
|
|
1794
|
+
repo: this.cfg.repo,
|
|
1795
|
+
branch: this.cfg.branch,
|
|
1796
|
+
path,
|
|
1797
|
+
message: `chore(marketplace): update ${path}`,
|
|
1798
|
+
content: contentBase64,
|
|
1799
|
+
...previousSha ? { sha: previousSha } : {}
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
async createReleaseWithAsset(params) {
|
|
1803
|
+
const octokit = await this.octokit();
|
|
1804
|
+
const bundleBuf = await readFile3(params.bundlePath);
|
|
1805
|
+
const sha256 = sha256Hex(bundleBuf);
|
|
1806
|
+
const bundleSize = bundleBuf.byteLength;
|
|
1807
|
+
const safeName = params.skillName.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1808
|
+
const safeVer = params.skillVersion.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1809
|
+
const tag = `skillverse-marketplace/${safeName}/v${safeVer}/${Date.now()}`;
|
|
1810
|
+
const release = await octokit.rest.repos.createRelease({
|
|
1811
|
+
owner: this.cfg.owner,
|
|
1812
|
+
repo: this.cfg.repo,
|
|
1813
|
+
tag_name: tag,
|
|
1814
|
+
name: `SkillVerse Marketplace: ${params.skillName}@${params.skillVersion}`,
|
|
1815
|
+
body: params.description || "",
|
|
1816
|
+
draft: false,
|
|
1817
|
+
prerelease: false,
|
|
1818
|
+
target_commitish: this.cfg.branch
|
|
1819
|
+
});
|
|
1820
|
+
const assetName = `${safeName}-${safeVer}.tar.gz`;
|
|
1821
|
+
const uploadRes = await octokit.rest.repos.uploadReleaseAsset({
|
|
1822
|
+
owner: this.cfg.owner,
|
|
1823
|
+
repo: this.cfg.repo,
|
|
1824
|
+
release_id: release.data.id,
|
|
1825
|
+
name: assetName,
|
|
1826
|
+
data: bundleBuf,
|
|
1827
|
+
headers: {
|
|
1828
|
+
"content-type": "application/gzip",
|
|
1829
|
+
"content-length": bundleSize
|
|
1830
|
+
}
|
|
1831
|
+
});
|
|
1832
|
+
return {
|
|
1833
|
+
bundleUrl: uploadRes.data.browser_download_url,
|
|
1834
|
+
bundleSize,
|
|
1835
|
+
sha256,
|
|
1836
|
+
tag
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
upsertSkillEntry(index, entry) {
|
|
1840
|
+
const createdAt = nowIso();
|
|
1841
|
+
const existingIdx = index.skills.findIndex((s) => s?.name === entry.name && s?.type === entry.type);
|
|
1842
|
+
if (existingIdx >= 0) {
|
|
1843
|
+
const prev = index.skills[existingIdx];
|
|
1844
|
+
index.skills[existingIdx] = {
|
|
1845
|
+
...prev,
|
|
1846
|
+
...entry,
|
|
1847
|
+
updatedAt: nowIso(),
|
|
1848
|
+
createdAt: prev.createdAt || createdAt
|
|
1849
|
+
};
|
|
1850
|
+
} else {
|
|
1851
|
+
index.skills.push({
|
|
1852
|
+
...entry,
|
|
1853
|
+
createdAt,
|
|
1854
|
+
updatedAt: nowIso()
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
return index;
|
|
1858
|
+
}
|
|
1859
|
+
static stableIdForEntry(entry) {
|
|
1860
|
+
const key = entry.type === "git" ? `git:${entry.name}:${entry.gitUrl}` : `bundle:${entry.name}:${entry.skillVersion || ""}:${entry.bundleUrl}`;
|
|
1861
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 24);
|
|
1862
|
+
}
|
|
1863
|
+
async deleteReleaseByTag(tag) {
|
|
1864
|
+
const octokit = await this.octokit();
|
|
1865
|
+
let releaseId;
|
|
1866
|
+
let page = 1;
|
|
1867
|
+
const perPage = 30;
|
|
1868
|
+
while (!releaseId) {
|
|
1869
|
+
const { data: releases } = await octokit.rest.repos.listReleases({
|
|
1870
|
+
owner: this.cfg.owner,
|
|
1871
|
+
repo: this.cfg.repo,
|
|
1872
|
+
page,
|
|
1873
|
+
per_page: perPage
|
|
1874
|
+
});
|
|
1875
|
+
if (releases.length === 0) break;
|
|
1876
|
+
const found = releases.find((r) => r.tag_name === tag);
|
|
1877
|
+
if (found) {
|
|
1878
|
+
releaseId = found.id;
|
|
1879
|
+
break;
|
|
1880
|
+
}
|
|
1881
|
+
page++;
|
|
1882
|
+
}
|
|
1883
|
+
if (!releaseId) {
|
|
1884
|
+
console.warn(`Release with tag "${tag}" not found, skipping deletion.`);
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
await octokit.rest.repos.deleteRelease({
|
|
1888
|
+
owner: this.cfg.owner,
|
|
1889
|
+
repo: this.cfg.repo,
|
|
1890
|
+
release_id: releaseId
|
|
1891
|
+
});
|
|
1892
|
+
try {
|
|
1893
|
+
await octokit.rest.git.deleteRef({
|
|
1894
|
+
owner: this.cfg.owner,
|
|
1895
|
+
repo: this.cfg.repo,
|
|
1896
|
+
ref: `tags/${tag}`
|
|
1897
|
+
});
|
|
1898
|
+
} catch (e) {
|
|
1899
|
+
console.warn(`Failed to delete git tag ref "tags/${tag}":`, e.message);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
};
|
|
1903
|
+
|
|
1904
|
+
// src/marketplaceConfig.ts
|
|
1905
|
+
var marketplaceConfig = {
|
|
1906
|
+
indexUrl: "https://fdksd.github.io/skillverse-marketplace/index.json",
|
|
1907
|
+
owner: "fdksd",
|
|
1908
|
+
repo: "skillverse-marketplace",
|
|
1909
|
+
branch: "main",
|
|
1910
|
+
token: "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"
|
|
1911
|
+
};
|
|
1912
|
+
|
|
1913
|
+
// src/routes/marketplace.ts
|
|
1682
1914
|
var router3 = Router3();
|
|
1915
|
+
function requireMarketplaceGitHubConfig() {
|
|
1916
|
+
const { indexUrl, owner, repo, branch, token } = marketplaceConfig;
|
|
1917
|
+
if (!indexUrl) {
|
|
1918
|
+
throw new AppError(
|
|
1919
|
+
ErrorCode.VALIDATION_ERROR,
|
|
1920
|
+
"Marketplace indexUrl is not configured.",
|
|
1921
|
+
400
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
return {
|
|
1925
|
+
indexUrl,
|
|
1926
|
+
owner,
|
|
1927
|
+
repo,
|
|
1928
|
+
branch,
|
|
1929
|
+
token,
|
|
1930
|
+
indexPath: "index.json"
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
function mapIndexToMarketplaceItems(index, search) {
|
|
1934
|
+
const query = (search || "").trim().toLowerCase();
|
|
1935
|
+
const skills = index.skills || [];
|
|
1936
|
+
const filtered = query ? skills.filter((s) => {
|
|
1937
|
+
const name = String(s?.name || "").toLowerCase();
|
|
1938
|
+
const desc = String(s?.description || "").toLowerCase();
|
|
1939
|
+
return name.includes(query) || desc.includes(query);
|
|
1940
|
+
}) : skills;
|
|
1941
|
+
return filtered.map((entry) => {
|
|
1942
|
+
const id = GitHubMarketplaceRegistry.stableIdForEntry(entry);
|
|
1943
|
+
const publishDate = entry.createdAt || entry.updatedAt || index.updatedAt || (/* @__PURE__ */ new Date()).toISOString();
|
|
1944
|
+
return {
|
|
1945
|
+
id,
|
|
1946
|
+
skillId: id,
|
|
1947
|
+
publisherId: void 0,
|
|
1948
|
+
publisherName: entry.publisherName || "Anonymous",
|
|
1949
|
+
publishDate: new Date(publishDate),
|
|
1950
|
+
downloads: entry.downloads || 0,
|
|
1951
|
+
skill: {
|
|
1952
|
+
id,
|
|
1953
|
+
name: entry.name,
|
|
1954
|
+
source: entry.type === "git" ? "git" : "local",
|
|
1955
|
+
sourceUrl: entry.type === "git" ? entry.gitUrl : void 0,
|
|
1956
|
+
description: entry.description || "",
|
|
1957
|
+
commitHash: void 0,
|
|
1958
|
+
repoUrl: entry.type === "git" ? entry.gitUrl : void 0,
|
|
1959
|
+
updateAvailable: false,
|
|
1960
|
+
lastUpdateCheck: void 0,
|
|
1961
|
+
installDate: new Date(publishDate),
|
|
1962
|
+
metadata: {
|
|
1963
|
+
marketplace: entry
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
async function fetchIndex() {
|
|
1970
|
+
const ghCfg = requireMarketplaceGitHubConfig();
|
|
1971
|
+
const registry = new GitHubMarketplaceRegistry(ghCfg);
|
|
1972
|
+
return await registry.fetchIndexFromPages();
|
|
1973
|
+
}
|
|
1683
1974
|
router3.get("/skills", async (req, res, next) => {
|
|
1684
1975
|
try {
|
|
1685
1976
|
const { search, page = "1", pageSize = "20" } = req.query;
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
]
|
|
1693
|
-
};
|
|
1694
|
-
}
|
|
1695
|
-
const [items, total] = await Promise.all([
|
|
1696
|
-
prisma.marketplaceSkill.findMany({
|
|
1697
|
-
where,
|
|
1698
|
-
include: {
|
|
1699
|
-
skill: true
|
|
1700
|
-
},
|
|
1701
|
-
orderBy: {
|
|
1702
|
-
downloads: "desc"
|
|
1703
|
-
},
|
|
1704
|
-
skip: (parseInt(page) - 1) * parseInt(pageSize),
|
|
1705
|
-
take: parseInt(pageSize)
|
|
1706
|
-
}),
|
|
1707
|
-
prisma.marketplaceSkill.count({ where })
|
|
1708
|
-
]);
|
|
1977
|
+
const index = await fetchIndex();
|
|
1978
|
+
const allItems = mapIndexToMarketplaceItems(index, search);
|
|
1979
|
+
const p = parseInt(page);
|
|
1980
|
+
const ps = parseInt(pageSize);
|
|
1981
|
+
const total = allItems.length;
|
|
1982
|
+
const items = allItems.slice((p - 1) * ps, (p - 1) * ps + ps);
|
|
1709
1983
|
res.json({
|
|
1710
1984
|
success: true,
|
|
1711
1985
|
data: {
|
|
1712
1986
|
items,
|
|
1713
1987
|
total,
|
|
1714
|
-
page:
|
|
1715
|
-
pageSize:
|
|
1988
|
+
page: p,
|
|
1989
|
+
pageSize: ps
|
|
1716
1990
|
}
|
|
1717
1991
|
});
|
|
1718
1992
|
} catch (error) {
|
|
@@ -1721,12 +1995,9 @@ router3.get("/skills", async (req, res, next) => {
|
|
|
1721
1995
|
});
|
|
1722
1996
|
router3.get("/skills/:id", async (req, res, next) => {
|
|
1723
1997
|
try {
|
|
1724
|
-
const
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
skill: true
|
|
1728
|
-
}
|
|
1729
|
-
});
|
|
1998
|
+
const index = await fetchIndex();
|
|
1999
|
+
const items = mapIndexToMarketplaceItems(index);
|
|
2000
|
+
const marketplaceSkill = items.find((i) => i.id === req.params.id);
|
|
1730
2001
|
if (!marketplaceSkill) {
|
|
1731
2002
|
throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
|
|
1732
2003
|
}
|
|
@@ -1740,33 +2011,48 @@ router3.get("/skills/:id", async (req, res, next) => {
|
|
|
1740
2011
|
});
|
|
1741
2012
|
router3.get("/download/:id", async (req, res, next) => {
|
|
1742
2013
|
try {
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
2014
|
+
const ghCfg = requireMarketplaceGitHubConfig();
|
|
2015
|
+
const registry = new GitHubMarketplaceRegistry(ghCfg);
|
|
2016
|
+
let index;
|
|
2017
|
+
let sha;
|
|
2018
|
+
try {
|
|
2019
|
+
const result = await registry.readIndexFromRepo();
|
|
2020
|
+
index = result.index;
|
|
2021
|
+
sha = result.sha;
|
|
2022
|
+
} catch (e) {
|
|
2023
|
+
console.warn("Failed to read index from repo, falling back to cached:", e);
|
|
2024
|
+
index = await fetchIndex();
|
|
2025
|
+
}
|
|
2026
|
+
const entryIndex = (index.skills || []).findIndex(
|
|
2027
|
+
(s) => GitHubMarketplaceRegistry.stableIdForEntry(s) === req.params.id
|
|
2028
|
+
);
|
|
2029
|
+
const entry = entryIndex >= 0 ? index.skills[entryIndex] : void 0;
|
|
2030
|
+
if (!entry) throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
|
|
2031
|
+
if (sha) {
|
|
2032
|
+
try {
|
|
2033
|
+
entry.downloads = (entry.downloads || 0) + 1;
|
|
2034
|
+
index.skills[entryIndex] = entry;
|
|
2035
|
+
await registry.upsertIndexToRepo(index, sha);
|
|
2036
|
+
} catch (e) {
|
|
2037
|
+
console.warn("Failed to update download count:", e);
|
|
1747
2038
|
}
|
|
1748
|
-
});
|
|
1749
|
-
if (!marketplaceSkill) {
|
|
1750
|
-
throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
|
|
1751
2039
|
}
|
|
1752
|
-
if (
|
|
2040
|
+
if (entry.type === "git") {
|
|
1753
2041
|
return res.json({
|
|
1754
2042
|
success: true,
|
|
1755
|
-
data: {
|
|
1756
|
-
type: "git",
|
|
1757
|
-
sourceUrl: marketplaceSkill.skill.sourceUrl
|
|
1758
|
-
}
|
|
2043
|
+
data: { type: "git", sourceUrl: entry.gitUrl }
|
|
1759
2044
|
});
|
|
1760
2045
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
"
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
2046
|
+
return res.json({
|
|
2047
|
+
success: true,
|
|
2048
|
+
data: {
|
|
2049
|
+
type: "bundle",
|
|
2050
|
+
bundleUrl: entry.bundleUrl,
|
|
2051
|
+
bundleSize: entry.bundleSize,
|
|
2052
|
+
sha256: entry.sha256,
|
|
2053
|
+
skillVersion: entry.skillVersion
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
1770
2056
|
} catch (error) {
|
|
1771
2057
|
next(error);
|
|
1772
2058
|
}
|
|
@@ -1780,38 +2066,74 @@ router3.post("/publish", async (req, res, next) => {
|
|
|
1780
2066
|
error: "skillId is required"
|
|
1781
2067
|
});
|
|
1782
2068
|
}
|
|
1783
|
-
const skill = await
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
});
|
|
1792
|
-
if (existingEntry) {
|
|
1793
|
-
throw new AppError(ErrorCode.ALREADY_EXISTS, "Skill is already published to marketplace", 409);
|
|
1794
|
-
}
|
|
1795
|
-
let bundlePath = null;
|
|
1796
|
-
let bundleSize = null;
|
|
1797
|
-
if (skill.source === "local" && existsSync6(skill.storagePath)) {
|
|
1798
|
-
bundlePath = await bundleService.createBundle(skill.storagePath, skill.name);
|
|
1799
|
-
bundleSize = await bundleService.getBundleSize(bundlePath);
|
|
2069
|
+
const skill = await skillService.getSkillById(skillId);
|
|
2070
|
+
const ghCfg = requireMarketplaceGitHubConfig();
|
|
2071
|
+
if (!ghCfg.owner || !ghCfg.repo || !ghCfg.branch || !ghCfg.token) {
|
|
2072
|
+
throw new AppError(
|
|
2073
|
+
ErrorCode.VALIDATION_ERROR,
|
|
2074
|
+
"GitHub registry config is incomplete. Please set owner/repo/branch/token in Settings -> Marketplace.",
|
|
2075
|
+
400
|
|
2076
|
+
);
|
|
1800
2077
|
}
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1803
|
-
|
|
2078
|
+
const registry = new GitHubMarketplaceRegistry(ghCfg);
|
|
2079
|
+
const { index, sha } = await registry.readIndexFromRepo();
|
|
2080
|
+
if (skill.source === "git") {
|
|
2081
|
+
if (!skill.sourceUrl) {
|
|
2082
|
+
throw new AppError(ErrorCode.VALIDATION_ERROR, "Git skill is missing sourceUrl", 400);
|
|
2083
|
+
}
|
|
2084
|
+
registry.upsertSkillEntry(index, {
|
|
2085
|
+
type: "git",
|
|
2086
|
+
name: skill.name,
|
|
2087
|
+
description: skill.description || "",
|
|
1804
2088
|
publisherName: publisherName || "Anonymous",
|
|
2089
|
+
gitUrl: skill.sourceUrl
|
|
2090
|
+
});
|
|
2091
|
+
} else {
|
|
2092
|
+
if (!existsSync6(skill.storagePath)) {
|
|
2093
|
+
throw new AppError(ErrorCode.FILE_SYSTEM_ERROR, "Skill files not found on disk", 400);
|
|
2094
|
+
}
|
|
2095
|
+
const bundlePath = await bundleService.createBundle(skill.storagePath, skill.name);
|
|
2096
|
+
const parsedMeta = (() => {
|
|
2097
|
+
try {
|
|
2098
|
+
return skill.metadata ? JSON.parse(skill.metadata) : {};
|
|
2099
|
+
} catch {
|
|
2100
|
+
return {};
|
|
2101
|
+
}
|
|
2102
|
+
})();
|
|
2103
|
+
const skillVersion = String(parsedMeta.version || parsedMeta.skillVersion || "1.0.0");
|
|
2104
|
+
const uploaded = await registry.createReleaseWithAsset({
|
|
2105
|
+
skillName: skill.name,
|
|
2106
|
+
skillVersion,
|
|
1805
2107
|
bundlePath,
|
|
1806
|
-
|
|
2108
|
+
description: skill.description || ""
|
|
2109
|
+
});
|
|
2110
|
+
registry.upsertSkillEntry(index, {
|
|
2111
|
+
type: "bundle",
|
|
2112
|
+
name: skill.name,
|
|
2113
|
+
description: skill.description || "",
|
|
2114
|
+
publisherName: publisherName || "Anonymous",
|
|
2115
|
+
bundleUrl: uploaded.bundleUrl,
|
|
2116
|
+
bundleSize: uploaded.bundleSize,
|
|
2117
|
+
sha256: uploaded.sha256,
|
|
2118
|
+
skillVersion
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
await registry.upsertIndexToRepo(index, sha);
|
|
2122
|
+
await prisma.marketplaceSkill.upsert({
|
|
2123
|
+
where: { skillId: skill.id },
|
|
2124
|
+
create: {
|
|
2125
|
+
skillId: skill.id,
|
|
2126
|
+
publisherName: publisherName || "Anonymous",
|
|
2127
|
+
downloads: 0
|
|
1807
2128
|
},
|
|
1808
|
-
|
|
1809
|
-
|
|
2129
|
+
update: {
|
|
2130
|
+
publisherName: publisherName || "Anonymous",
|
|
2131
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1810
2132
|
}
|
|
1811
2133
|
});
|
|
1812
2134
|
res.status(201).json({
|
|
1813
2135
|
success: true,
|
|
1814
|
-
data:
|
|
2136
|
+
data: { ok: true },
|
|
1815
2137
|
message: "Skill published to marketplace successfully"
|
|
1816
2138
|
});
|
|
1817
2139
|
} catch (error) {
|
|
@@ -1820,66 +2142,117 @@ router3.post("/publish", async (req, res, next) => {
|
|
|
1820
2142
|
});
|
|
1821
2143
|
router3.post("/install/:id", async (req, res, next) => {
|
|
1822
2144
|
try {
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2145
|
+
const ghCfg = requireMarketplaceGitHubConfig();
|
|
2146
|
+
const registry = new GitHubMarketplaceRegistry(ghCfg);
|
|
2147
|
+
let index;
|
|
2148
|
+
let sha;
|
|
2149
|
+
try {
|
|
2150
|
+
const result = await registry.readIndexFromRepo();
|
|
2151
|
+
index = result.index;
|
|
2152
|
+
sha = result.sha;
|
|
2153
|
+
} catch (e) {
|
|
2154
|
+
console.warn("Failed to read index from repo for install count:", e);
|
|
2155
|
+
index = await fetchIndex();
|
|
2156
|
+
}
|
|
2157
|
+
const entryIndex = (index.skills || []).findIndex(
|
|
2158
|
+
(s) => GitHubMarketplaceRegistry.stableIdForEntry(s) === req.params.id
|
|
2159
|
+
);
|
|
2160
|
+
const entry = entryIndex >= 0 ? index.skills[entryIndex] : void 0;
|
|
2161
|
+
if (!entry) throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
|
|
2162
|
+
if (sha) {
|
|
2163
|
+
try {
|
|
2164
|
+
entry.downloads = (entry.downloads || 0) + 1;
|
|
2165
|
+
index.skills[entryIndex] = entry;
|
|
2166
|
+
await registry.upsertIndexToRepo(index, sha);
|
|
2167
|
+
} catch (e) {
|
|
2168
|
+
console.warn("Failed to update download count:", e);
|
|
1827
2169
|
}
|
|
1828
|
-
});
|
|
1829
|
-
if (!marketplaceSkill) {
|
|
1830
|
-
throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
|
|
1831
2170
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
const newSkill = await skillService.createSkillFromGit(
|
|
1835
|
-
sourceSkill.sourceUrl,
|
|
1836
|
-
sourceSkill.description || void 0
|
|
1837
|
-
);
|
|
1838
|
-
await prisma.marketplaceSkill.update({
|
|
1839
|
-
where: { id: req.params.id },
|
|
1840
|
-
data: { downloads: { increment: 1 } }
|
|
1841
|
-
});
|
|
2171
|
+
if (entry.type === "git") {
|
|
2172
|
+
const newSkill = await skillService.createSkillFromGit(entry.gitUrl, entry.description || void 0);
|
|
1842
2173
|
return res.status(201).json({
|
|
1843
2174
|
success: true,
|
|
1844
2175
|
data: newSkill,
|
|
1845
2176
|
message: "Skill installed from marketplace successfully"
|
|
1846
2177
|
});
|
|
1847
2178
|
}
|
|
1848
|
-
|
|
2179
|
+
const tempDir = process.env.TEMP_DIR || join6(process.env.HOME || "", ".skillverse", "temp");
|
|
2180
|
+
await mkdir5(tempDir, { recursive: true });
|
|
2181
|
+
const resp = await fetch(entry.bundleUrl);
|
|
2182
|
+
if (!resp.ok) {
|
|
2183
|
+
throw new AppError(
|
|
2184
|
+
ErrorCode.INTERNAL_ERROR,
|
|
2185
|
+
`Failed to download bundle: ${resp.status} ${resp.statusText}`,
|
|
2186
|
+
502
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
2190
|
+
const tmpPath = join6(tempDir, `marketplace-${entry.name}-${Date.now()}.tar.gz`);
|
|
2191
|
+
await writeFile(tmpPath, buf);
|
|
2192
|
+
try {
|
|
1849
2193
|
const newSkill = await skillService.createSkillFromBundle(
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
2194
|
+
tmpPath,
|
|
2195
|
+
entry.name,
|
|
2196
|
+
entry.description || void 0
|
|
1853
2197
|
);
|
|
1854
|
-
await prisma.marketplaceSkill.update({
|
|
1855
|
-
where: { id: req.params.id },
|
|
1856
|
-
data: { downloads: { increment: 1 } }
|
|
1857
|
-
});
|
|
1858
2198
|
return res.status(201).json({
|
|
1859
2199
|
success: true,
|
|
1860
2200
|
data: newSkill,
|
|
1861
2201
|
message: "Skill installed from marketplace successfully"
|
|
1862
2202
|
});
|
|
2203
|
+
} finally {
|
|
2204
|
+
await rm5(tmpPath, { force: true }).catch(() => {
|
|
2205
|
+
});
|
|
1863
2206
|
}
|
|
1864
|
-
return res.status(400).json({
|
|
1865
|
-
success: false,
|
|
1866
|
-
error: "Cannot install this skill. Bundle not available."
|
|
1867
|
-
});
|
|
1868
2207
|
} catch (error) {
|
|
1869
2208
|
next(error);
|
|
1870
2209
|
}
|
|
1871
2210
|
});
|
|
1872
2211
|
router3.delete("/unpublish/:skillId", async (req, res, next) => {
|
|
1873
2212
|
try {
|
|
1874
|
-
const
|
|
1875
|
-
|
|
2213
|
+
const skill = await skillService.getSkillById(req.params.skillId);
|
|
2214
|
+
const ghCfg = requireMarketplaceGitHubConfig();
|
|
2215
|
+
if (!ghCfg.owner || !ghCfg.repo || !ghCfg.branch || !ghCfg.token) {
|
|
2216
|
+
throw new AppError(
|
|
2217
|
+
ErrorCode.VALIDATION_ERROR,
|
|
2218
|
+
"GitHub registry config is incomplete. Please set owner/repo/branch/token in Settings -> Marketplace.",
|
|
2219
|
+
400
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
const registry = new GitHubMarketplaceRegistry(ghCfg);
|
|
2223
|
+
const { index, sha } = await registry.readIndexFromRepo();
|
|
2224
|
+
const before = index.skills.length;
|
|
2225
|
+
const entryToRemove = index.skills.find((s) => {
|
|
2226
|
+
if (s?.name !== skill.name) return false;
|
|
2227
|
+
if (skill.source === "git") return s.type === "git";
|
|
2228
|
+
return s.type === "bundle";
|
|
1876
2229
|
});
|
|
1877
|
-
if (
|
|
2230
|
+
if (entryToRemove) {
|
|
2231
|
+
if (entryToRemove.type === "bundle" && entryToRemove.bundleUrl) {
|
|
2232
|
+
try {
|
|
2233
|
+
const match = entryToRemove.bundleUrl.match(/\/releases\/download\/(.+)\/[^/]+$/);
|
|
2234
|
+
if (match && match[1]) {
|
|
2235
|
+
const tag = decodeURIComponent(match[1]);
|
|
2236
|
+
console.log(`Deleting release tag: ${tag}`);
|
|
2237
|
+
await registry.deleteReleaseByTag(tag);
|
|
2238
|
+
}
|
|
2239
|
+
} catch (e) {
|
|
2240
|
+
console.warn("Failed to delete GitHub release:", e);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
index.skills = index.skills.filter((s) => s !== entryToRemove);
|
|
2244
|
+
}
|
|
2245
|
+
const after = index.skills.length;
|
|
2246
|
+
if (before === after) {
|
|
1878
2247
|
throw new AppError(ErrorCode.NOT_FOUND, "Skill is not published to marketplace", 404);
|
|
1879
2248
|
}
|
|
1880
|
-
await
|
|
1881
|
-
|
|
1882
|
-
|
|
2249
|
+
await registry.upsertIndexToRepo(index, sha);
|
|
2250
|
+
try {
|
|
2251
|
+
await prisma.marketplaceSkill.delete({
|
|
2252
|
+
where: { skillId: skill.id }
|
|
2253
|
+
});
|
|
2254
|
+
} catch (e) {
|
|
2255
|
+
}
|
|
1883
2256
|
res.json({
|
|
1884
2257
|
success: true,
|
|
1885
2258
|
message: "Skill unpublished from marketplace successfully"
|
|
@@ -1985,16 +2358,16 @@ var dashboard_default = router4;
|
|
|
1985
2358
|
|
|
1986
2359
|
// src/routes/config.ts
|
|
1987
2360
|
import { Router as Router5 } from "express";
|
|
1988
|
-
import { join as
|
|
1989
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2361
|
+
import { join as join7 } from "path";
|
|
2362
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1990
2363
|
var router5 = Router5();
|
|
1991
2364
|
var HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
1992
|
-
var BOOTSTRAP_HOME =
|
|
1993
|
-
var CONFIG_FILE =
|
|
2365
|
+
var BOOTSTRAP_HOME = join7(HOME, ".skillverse");
|
|
2366
|
+
var CONFIG_FILE = join7(BOOTSTRAP_HOME, "config.json");
|
|
1994
2367
|
function readConfig() {
|
|
1995
2368
|
if (existsSync7(CONFIG_FILE)) {
|
|
1996
2369
|
try {
|
|
1997
|
-
return JSON.parse(
|
|
2370
|
+
return JSON.parse(readFileSync3(CONFIG_FILE, "utf-8"));
|
|
1998
2371
|
} catch (e) {
|
|
1999
2372
|
console.error("Failed to read config.json:", e);
|
|
2000
2373
|
return {};
|
|
@@ -2009,8 +2382,17 @@ router5.get("/", (req, res, next) => {
|
|
|
2009
2382
|
// Show saved preference if available, otherwise current env/default
|
|
2010
2383
|
skillverseHome: fileConfig.skillverseHome || process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME,
|
|
2011
2384
|
// registryUrl is potentially in config.json already
|
|
2012
|
-
registryUrl: fileConfig.registryUrl || process.env.SKILLVERSE_REGISTRY || "http://localhost:4000"
|
|
2013
|
-
//
|
|
2385
|
+
registryUrl: fileConfig.registryUrl || process.env.SKILLVERSE_REGISTRY || "http://localhost:4000",
|
|
2386
|
+
// Marketplace is now hardcoded
|
|
2387
|
+
marketplace: {
|
|
2388
|
+
indexUrl: "",
|
|
2389
|
+
github: {
|
|
2390
|
+
owner: "",
|
|
2391
|
+
repo: "",
|
|
2392
|
+
branch: "",
|
|
2393
|
+
tokenConfigured: false
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2014
2396
|
};
|
|
2015
2397
|
res.json({
|
|
2016
2398
|
success: true,
|
|
@@ -2022,19 +2404,19 @@ router5.get("/", (req, res, next) => {
|
|
|
2022
2404
|
});
|
|
2023
2405
|
router5.post("/", async (req, res, next) => {
|
|
2024
2406
|
try {
|
|
2025
|
-
const { skillverseHome, registryUrl, migrate } = req.body;
|
|
2407
|
+
const { skillverseHome, registryUrl, migrate, marketplace } = req.body;
|
|
2026
2408
|
const currentConfig = readConfig();
|
|
2027
2409
|
const oldHome = process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME;
|
|
2028
2410
|
if (migrate && skillverseHome && skillverseHome !== oldHome) {
|
|
2029
2411
|
console.log(`Migrating data from ${oldHome} to ${skillverseHome}...`);
|
|
2030
2412
|
const { cp: cp2 } = await import("fs/promises");
|
|
2031
2413
|
if (!existsSync7(skillverseHome)) {
|
|
2032
|
-
const { mkdir:
|
|
2033
|
-
await
|
|
2414
|
+
const { mkdir: mkdir7 } = await import("fs/promises");
|
|
2415
|
+
await mkdir7(skillverseHome, { recursive: true });
|
|
2034
2416
|
}
|
|
2035
2417
|
const copyDir = async (srcName) => {
|
|
2036
|
-
const src =
|
|
2037
|
-
const dest =
|
|
2418
|
+
const src = join7(oldHome, srcName);
|
|
2419
|
+
const dest = join7(skillverseHome, srcName);
|
|
2038
2420
|
if (existsSync7(src)) {
|
|
2039
2421
|
console.log(`Copying ${srcName}...`);
|
|
2040
2422
|
await cp2(src, dest, { recursive: true, force: true });
|
|
@@ -2042,8 +2424,8 @@ router5.post("/", async (req, res, next) => {
|
|
|
2042
2424
|
};
|
|
2043
2425
|
await copyDir("skills");
|
|
2044
2426
|
await copyDir("marketplace");
|
|
2045
|
-
const dbSrc =
|
|
2046
|
-
const dbDest =
|
|
2427
|
+
const dbSrc = join7(oldHome, "skillverse.db");
|
|
2428
|
+
const dbDest = join7(skillverseHome, "skillverse.db");
|
|
2047
2429
|
if (existsSync7(dbSrc)) {
|
|
2048
2430
|
console.log("Copying database...");
|
|
2049
2431
|
await cp2(dbSrc, dbDest, { force: true });
|
|
@@ -2053,8 +2435,8 @@ router5.post("/", async (req, res, next) => {
|
|
|
2053
2435
|
if (existsSync7(shmSrc)) await cp2(shmSrc, dbDest + "-shm", { force: true });
|
|
2054
2436
|
console.log("Updating skill paths in new database...");
|
|
2055
2437
|
const { execSync } = await import("child_process");
|
|
2056
|
-
const oldSkillsPath =
|
|
2057
|
-
const newSkillsPath =
|
|
2438
|
+
const oldSkillsPath = join7(oldHome, "skills");
|
|
2439
|
+
const newSkillsPath = join7(skillverseHome, "skills");
|
|
2058
2440
|
const updateQuery = `UPDATE Skill SET storagePath = replace(storagePath, '${oldSkillsPath}', '${newSkillsPath}') WHERE storagePath LIKE '${oldSkillsPath}%';`;
|
|
2059
2441
|
try {
|
|
2060
2442
|
execSync(`sqlite3 "${dbDest}" "${updateQuery}"`, { encoding: "utf-8" });
|
|
@@ -2064,21 +2446,79 @@ router5.post("/", async (req, res, next) => {
|
|
|
2064
2446
|
}
|
|
2065
2447
|
}
|
|
2066
2448
|
}
|
|
2067
|
-
const
|
|
2449
|
+
const nextConfig = {
|
|
2068
2450
|
...currentConfig,
|
|
2069
2451
|
...skillverseHome && { skillverseHome },
|
|
2070
2452
|
...registryUrl && { registryUrl }
|
|
2071
2453
|
};
|
|
2072
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(
|
|
2454
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(nextConfig, null, 2));
|
|
2073
2455
|
res.json({
|
|
2074
2456
|
success: true,
|
|
2075
|
-
data:
|
|
2457
|
+
data: {
|
|
2458
|
+
...nextConfig,
|
|
2459
|
+
// Never return token
|
|
2460
|
+
marketplace: {
|
|
2461
|
+
indexUrl: nextConfig.marketplace?.indexUrl || "",
|
|
2462
|
+
github: {
|
|
2463
|
+
owner: nextConfig.marketplace?.github?.owner || "",
|
|
2464
|
+
repo: nextConfig.marketplace?.github?.repo || "",
|
|
2465
|
+
branch: nextConfig.marketplace?.github?.branch || "main",
|
|
2466
|
+
tokenConfigured: Boolean(nextConfig.marketplace?.github?.token)
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
},
|
|
2076
2470
|
message: "Configuration saved. Restart server for changes to take effect."
|
|
2077
2471
|
});
|
|
2078
2472
|
} catch (error) {
|
|
2079
2473
|
next(error);
|
|
2080
2474
|
}
|
|
2081
2475
|
});
|
|
2476
|
+
router5.get("/version", async (req, res, next) => {
|
|
2477
|
+
try {
|
|
2478
|
+
const { dirname: dirname4, join: pathJoin } = await import("path");
|
|
2479
|
+
const { fileURLToPath: fileURLToPath4 } = await import("url");
|
|
2480
|
+
const __filename4 = fileURLToPath4(import.meta.url);
|
|
2481
|
+
const __dirname4 = dirname4(__filename4);
|
|
2482
|
+
const packageJsonPath = pathJoin(__dirname4, "..", "..", "package.json");
|
|
2483
|
+
let currentVersion = "0.0.0";
|
|
2484
|
+
if (existsSync7(packageJsonPath)) {
|
|
2485
|
+
const pkg = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
2486
|
+
currentVersion = pkg.version || "0.0.0";
|
|
2487
|
+
}
|
|
2488
|
+
let latestVersion = currentVersion;
|
|
2489
|
+
let hasUpdate = false;
|
|
2490
|
+
try {
|
|
2491
|
+
const response = await fetch("https://registry.npmjs.org/skillverse/latest");
|
|
2492
|
+
if (response.ok) {
|
|
2493
|
+
const data = await response.json();
|
|
2494
|
+
latestVersion = data.version || currentVersion;
|
|
2495
|
+
const parseVersion = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
|
|
2496
|
+
const current = parseVersion(currentVersion);
|
|
2497
|
+
const latest = parseVersion(latestVersion);
|
|
2498
|
+
for (let i = 0; i < 3; i++) {
|
|
2499
|
+
if (latest[i] > current[i]) {
|
|
2500
|
+
hasUpdate = true;
|
|
2501
|
+
break;
|
|
2502
|
+
} else if (latest[i] < current[i]) {
|
|
2503
|
+
break;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
} catch (fetchError) {
|
|
2508
|
+
console.warn("Failed to check npm registry for updates:", fetchError);
|
|
2509
|
+
}
|
|
2510
|
+
res.json({
|
|
2511
|
+
success: true,
|
|
2512
|
+
data: {
|
|
2513
|
+
currentVersion,
|
|
2514
|
+
latestVersion,
|
|
2515
|
+
hasUpdate
|
|
2516
|
+
}
|
|
2517
|
+
});
|
|
2518
|
+
} catch (error) {
|
|
2519
|
+
next(error);
|
|
2520
|
+
}
|
|
2521
|
+
});
|
|
2082
2522
|
var config_default = router5;
|
|
2083
2523
|
|
|
2084
2524
|
// src/index.ts
|
|
@@ -2097,7 +2537,7 @@ function requestLogger(req, res, next) {
|
|
|
2097
2537
|
// src/lib/initDb.ts
|
|
2098
2538
|
init_db();
|
|
2099
2539
|
import { existsSync as existsSync8 } from "fs";
|
|
2100
|
-
import { join as
|
|
2540
|
+
import { join as join8, dirname as dirname2 } from "path";
|
|
2101
2541
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2102
2542
|
import { spawnSync } from "child_process";
|
|
2103
2543
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
@@ -2105,13 +2545,13 @@ var __dirname2 = dirname2(__filename2);
|
|
|
2105
2545
|
function getSchemaPath() {
|
|
2106
2546
|
const possiblePaths = [
|
|
2107
2547
|
// Prod: dist/bin.js -> dist/../prisma/schema.prisma (i.e. ROOT/prisma/schema.prisma)
|
|
2108
|
-
|
|
2548
|
+
join8(__dirname2, "../prisma/schema.prisma"),
|
|
2109
2549
|
// Dev: src/lib/initDb.ts -> src/prisma/schema.prisma ? No, src/../prisma -> server/prisma
|
|
2110
2550
|
// If __dirname is src/lib: ../../prisma/schema.prisma
|
|
2111
|
-
|
|
2551
|
+
join8(__dirname2, "../../prisma/schema.prisma"),
|
|
2112
2552
|
// Fallbacks
|
|
2113
|
-
|
|
2114
|
-
|
|
2553
|
+
join8(process.cwd(), "prisma/schema.prisma"),
|
|
2554
|
+
join8(process.cwd(), "server/prisma/schema.prisma")
|
|
2115
2555
|
];
|
|
2116
2556
|
return possiblePaths.find((p) => existsSync8(p)) || null;
|
|
2117
2557
|
}
|
|
@@ -2138,10 +2578,10 @@ async function initializeDatabase() {
|
|
|
2138
2578
|
throw new Error("Could not find schema.prisma");
|
|
2139
2579
|
}
|
|
2140
2580
|
const possiblePrismaBins = [
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2581
|
+
join8(__dirname2, "../node_modules/.bin/prisma"),
|
|
2582
|
+
join8(__dirname2, "../../node_modules/.bin/prisma"),
|
|
2583
|
+
join8(__dirname2, "../../../node_modules/.bin/prisma"),
|
|
2584
|
+
join8(process.cwd(), "node_modules/.bin/prisma")
|
|
2145
2585
|
];
|
|
2146
2586
|
const prismaBin = possiblePrismaBins.find((p) => existsSync8(p)) || "prisma";
|
|
2147
2587
|
console.log(` Using schema: ${schemaPath}`);
|
|
@@ -2170,13 +2610,13 @@ app.use(express.json());
|
|
|
2170
2610
|
app.use(express.urlencoded({ extended: true }));
|
|
2171
2611
|
app.use(requestLogger);
|
|
2172
2612
|
var possiblePublicDirs = [
|
|
2173
|
-
|
|
2613
|
+
join9(__dirname3, "../public"),
|
|
2174
2614
|
// Production: dist/index.js -> public
|
|
2175
|
-
|
|
2615
|
+
join9(__dirname3, "../../public"),
|
|
2176
2616
|
// Dev tsx might resolve here
|
|
2177
|
-
|
|
2617
|
+
join9(process.cwd(), "public"),
|
|
2178
2618
|
// Fallback: relative to cwd
|
|
2179
|
-
|
|
2619
|
+
join9(process.cwd(), "server/public")
|
|
2180
2620
|
// Fallback: from root
|
|
2181
2621
|
];
|
|
2182
2622
|
var publicDir = possiblePublicDirs.find((dir) => existsSync9(dir));
|
|
@@ -2196,18 +2636,18 @@ app.get("/health", (req, res) => {
|
|
|
2196
2636
|
});
|
|
2197
2637
|
if (publicDir && existsSync9(publicDir)) {
|
|
2198
2638
|
app.get("*", (req, res) => {
|
|
2199
|
-
res.sendFile(
|
|
2639
|
+
res.sendFile(join9(publicDir, "index.html"));
|
|
2200
2640
|
});
|
|
2201
2641
|
}
|
|
2202
2642
|
app.use(errorHandler);
|
|
2203
2643
|
async function initializeStorage() {
|
|
2204
|
-
const skillverseHome = process.env.SKILLVERSE_HOME ||
|
|
2205
|
-
const skillsDir = process.env.SKILLS_DIR ||
|
|
2206
|
-
const marketplaceDir = process.env.MARKETPLACE_DIR ||
|
|
2644
|
+
const skillverseHome = process.env.SKILLVERSE_HOME || join9(process.env.HOME || "", ".skillverse");
|
|
2645
|
+
const skillsDir = process.env.SKILLS_DIR || join9(skillverseHome, "skills");
|
|
2646
|
+
const marketplaceDir = process.env.MARKETPLACE_DIR || join9(skillverseHome, "marketplace");
|
|
2207
2647
|
const dirs = [skillverseHome, skillsDir, marketplaceDir];
|
|
2208
2648
|
for (const dir of dirs) {
|
|
2209
2649
|
if (!existsSync9(dir)) {
|
|
2210
|
-
await
|
|
2650
|
+
await mkdir6(dir, { recursive: true });
|
|
2211
2651
|
console.log(`Created directory: ${dir}`);
|
|
2212
2652
|
}
|
|
2213
2653
|
}
|
|
@@ -2216,13 +2656,13 @@ async function startServer(port = 3001) {
|
|
|
2216
2656
|
try {
|
|
2217
2657
|
await initializeStorage();
|
|
2218
2658
|
await ensureDatabaseInitialized();
|
|
2219
|
-
console.log("\u{1F50D}
|
|
2659
|
+
console.log("\u{1F50D} Syncing skills with filesystem...");
|
|
2220
2660
|
const { skillService: skillService2 } = await Promise.resolve().then(() => (init_skillService(), skillService_exports));
|
|
2221
|
-
await skillService2.
|
|
2661
|
+
await skillService2.syncSkillsWithFileSystem();
|
|
2222
2662
|
return new Promise((resolve) => {
|
|
2223
2663
|
app.listen(port, () => {
|
|
2224
2664
|
console.log(`\u{1F680} SkillVerse server running on http://localhost:${port}`);
|
|
2225
|
-
console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME ||
|
|
2665
|
+
console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join9(process.env.HOME || "", ".skillverse")}`);
|
|
2226
2666
|
resolve();
|
|
2227
2667
|
});
|
|
2228
2668
|
});
|