skillverse 0.1.6 → 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/index.js CHANGED
@@ -816,8 +816,8 @@ import express from "express";
816
816
  import cors from "cors";
817
817
  import dotenv2 from "dotenv";
818
818
  import { fileURLToPath as fileURLToPath3 } from "url";
819
- import { dirname as dirname3, join as join8 } from "path";
820
- import { mkdir as mkdir5 } from "fs/promises";
819
+ import { dirname as dirname3, join as join9 } from "path";
820
+ import { mkdir as mkdir6 } from "fs/promises";
821
821
  import { existsSync as existsSync9 } from "fs";
822
822
 
823
823
  // src/routes/skills.ts
@@ -1118,7 +1118,7 @@ ${relativeSkillsPath}/
1118
1118
  if (!existsSync3(skillsPath)) {
1119
1119
  return [];
1120
1120
  }
1121
- const { readdir: readdir2, stat: stat2, readFile: readFile3 } = await import("fs/promises");
1121
+ const { readdir: readdir2, stat: stat2, readFile: readFile4 } = await import("fs/promises");
1122
1122
  const items = await readdir2(skillsPath);
1123
1123
  const existingSkills = [];
1124
1124
  for (const item of items) {
@@ -1159,7 +1159,7 @@ ${relativeSkillsPath}/
1159
1159
  if (!existsSync3(SKILLS_DIR)) {
1160
1160
  await mkdir2(SKILLS_DIR, { recursive: true });
1161
1161
  }
1162
- const { rename, readFile: readFile3, cp: cp2 } = await import("fs/promises");
1162
+ const { rename, readFile: readFile4, cp: cp2 } = await import("fs/promises");
1163
1163
  const migrated = [];
1164
1164
  const errors = [];
1165
1165
  for (const skillName of skillNames) {
@@ -1192,7 +1192,7 @@ ${relativeSkillsPath}/
1192
1192
  if (existsSync3(skillMdPath)) {
1193
1193
  try {
1194
1194
  const matter2 = await import("gray-matter");
1195
- const fileContent = await readFile3(skillMdPath, "utf-8");
1195
+ const fileContent = await readFile4(skillMdPath, "utf-8");
1196
1196
  const parsed = matter2.default(fileContent);
1197
1197
  description = parsed.data.description || "";
1198
1198
  metadata = parsed.data;
@@ -1462,7 +1462,7 @@ router.post("/:id/refresh-metadata", async (req, res, next) => {
1462
1462
  });
1463
1463
  router.get("/:id/skill-md", async (req, res, next) => {
1464
1464
  try {
1465
- const { readFile: readFile3 } = await import("fs/promises");
1465
+ const { readFile: readFile4 } = await import("fs/promises");
1466
1466
  const skill = await skillService.getSkillById(req.params.id);
1467
1467
  const skillMdPath = join4(skill.storagePath, "SKILL.md");
1468
1468
  if (!existsSync4(skillMdPath)) {
@@ -1474,7 +1474,7 @@ router.get("/:id/skill-md", async (req, res, next) => {
1474
1474
  }
1475
1475
  });
1476
1476
  }
1477
- const content = await readFile3(skillMdPath, "utf-8");
1477
+ const content = await readFile4(skillMdPath, "utf-8");
1478
1478
  res.json({
1479
1479
  success: true,
1480
1480
  data: {
@@ -1629,7 +1629,6 @@ router2.post("/:id/migrate-skills", async (req, res, next) => {
1629
1629
  var workspaces_default = router2;
1630
1630
 
1631
1631
  // src/routes/marketplace.ts
1632
- init_db();
1633
1632
  init_skillService();
1634
1633
  import { Router as Router3 } from "express";
1635
1634
 
@@ -1716,41 +1715,278 @@ var bundleService = {
1716
1715
  // src/routes/marketplace.ts
1717
1716
  init_errorHandler();
1718
1717
  init_dist();
1718
+ init_db();
1719
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
1720
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
+ }
1721
1974
  router3.get("/skills", async (req, res, next) => {
1722
1975
  try {
1723
1976
  const { search, page = "1", pageSize = "20" } = req.query;
1724
- const where = {};
1725
- if (search) {
1726
- where.skill = {
1727
- OR: [
1728
- { name: { contains: search } },
1729
- { description: { contains: search } }
1730
- ]
1731
- };
1732
- }
1733
- const [items, total] = await Promise.all([
1734
- prisma.marketplaceSkill.findMany({
1735
- where,
1736
- include: {
1737
- skill: true
1738
- },
1739
- orderBy: {
1740
- downloads: "desc"
1741
- },
1742
- skip: (parseInt(page) - 1) * parseInt(pageSize),
1743
- take: parseInt(pageSize)
1744
- }),
1745
- prisma.marketplaceSkill.count({ where })
1746
- ]);
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);
1747
1983
  res.json({
1748
1984
  success: true,
1749
1985
  data: {
1750
1986
  items,
1751
1987
  total,
1752
- page: parseInt(page),
1753
- pageSize: parseInt(pageSize)
1988
+ page: p,
1989
+ pageSize: ps
1754
1990
  }
1755
1991
  });
1756
1992
  } catch (error) {
@@ -1759,12 +1995,9 @@ router3.get("/skills", async (req, res, next) => {
1759
1995
  });
1760
1996
  router3.get("/skills/:id", async (req, res, next) => {
1761
1997
  try {
1762
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1763
- where: { id: req.params.id },
1764
- include: {
1765
- skill: true
1766
- }
1767
- });
1998
+ const index = await fetchIndex();
1999
+ const items = mapIndexToMarketplaceItems(index);
2000
+ const marketplaceSkill = items.find((i) => i.id === req.params.id);
1768
2001
  if (!marketplaceSkill) {
1769
2002
  throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1770
2003
  }
@@ -1778,33 +2011,48 @@ router3.get("/skills/:id", async (req, res, next) => {
1778
2011
  });
1779
2012
  router3.get("/download/:id", async (req, res, next) => {
1780
2013
  try {
1781
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1782
- where: { id: req.params.id },
1783
- include: {
1784
- skill: true
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);
1785
2038
  }
1786
- });
1787
- if (!marketplaceSkill) {
1788
- throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1789
2039
  }
1790
- if (marketplaceSkill.skill.source === "git" && marketplaceSkill.skill.sourceUrl) {
2040
+ if (entry.type === "git") {
1791
2041
  return res.json({
1792
2042
  success: true,
1793
- data: {
1794
- type: "git",
1795
- sourceUrl: marketplaceSkill.skill.sourceUrl
1796
- }
2043
+ data: { type: "git", sourceUrl: entry.gitUrl }
1797
2044
  });
1798
2045
  }
1799
- if (marketplaceSkill.bundlePath && existsSync6(marketplaceSkill.bundlePath)) {
1800
- res.setHeader("Content-Type", "application/gzip");
1801
- res.setHeader(
1802
- "Content-Disposition",
1803
- `attachment; filename="${marketplaceSkill.skill.name}.tar.gz"`
1804
- );
1805
- return res.sendFile(marketplaceSkill.bundlePath);
1806
- }
1807
- throw new AppError(ErrorCode.NOT_FOUND, "Bundle not available for this skill", 404);
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
+ });
1808
2056
  } catch (error) {
1809
2057
  next(error);
1810
2058
  }
@@ -1818,38 +2066,74 @@ router3.post("/publish", async (req, res, next) => {
1818
2066
  error: "skillId is required"
1819
2067
  });
1820
2068
  }
1821
- const skill = await prisma.skill.findUnique({
1822
- where: { id: skillId }
1823
- });
1824
- if (!skill) {
1825
- throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
1826
- }
1827
- const existingEntry = await prisma.marketplaceSkill.findUnique({
1828
- where: { skillId }
1829
- });
1830
- if (existingEntry) {
1831
- throw new AppError(ErrorCode.ALREADY_EXISTS, "Skill is already published to marketplace", 409);
1832
- }
1833
- let bundlePath = null;
1834
- let bundleSize = null;
1835
- if (skill.source === "local" && existsSync6(skill.storagePath)) {
1836
- bundlePath = await bundleService.createBundle(skill.storagePath, skill.name);
1837
- 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
+ );
1838
2077
  }
1839
- const marketplaceSkill = await prisma.marketplaceSkill.create({
1840
- data: {
1841
- skillId,
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 || "",
1842
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,
1843
2107
  bundlePath,
1844
- bundleSize
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
1845
2128
  },
1846
- include: {
1847
- skill: true
2129
+ update: {
2130
+ publisherName: publisherName || "Anonymous",
2131
+ updatedAt: /* @__PURE__ */ new Date()
1848
2132
  }
1849
2133
  });
1850
2134
  res.status(201).json({
1851
2135
  success: true,
1852
- data: marketplaceSkill,
2136
+ data: { ok: true },
1853
2137
  message: "Skill published to marketplace successfully"
1854
2138
  });
1855
2139
  } catch (error) {
@@ -1858,66 +2142,117 @@ router3.post("/publish", async (req, res, next) => {
1858
2142
  });
1859
2143
  router3.post("/install/:id", async (req, res, next) => {
1860
2144
  try {
1861
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1862
- where: { id: req.params.id },
1863
- include: {
1864
- skill: true
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);
1865
2169
  }
1866
- });
1867
- if (!marketplaceSkill) {
1868
- throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1869
2170
  }
1870
- const sourceSkill = marketplaceSkill.skill;
1871
- if (sourceSkill.source === "git" && sourceSkill.sourceUrl) {
1872
- const newSkill = await skillService.createSkillFromGit(
1873
- sourceSkill.sourceUrl,
1874
- sourceSkill.description || void 0
1875
- );
1876
- await prisma.marketplaceSkill.update({
1877
- where: { id: req.params.id },
1878
- data: { downloads: { increment: 1 } }
1879
- });
2171
+ if (entry.type === "git") {
2172
+ const newSkill = await skillService.createSkillFromGit(entry.gitUrl, entry.description || void 0);
1880
2173
  return res.status(201).json({
1881
2174
  success: true,
1882
2175
  data: newSkill,
1883
2176
  message: "Skill installed from marketplace successfully"
1884
2177
  });
1885
2178
  }
1886
- if (sourceSkill.source === "local" && marketplaceSkill.bundlePath && existsSync6(marketplaceSkill.bundlePath)) {
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 {
1887
2193
  const newSkill = await skillService.createSkillFromBundle(
1888
- marketplaceSkill.bundlePath,
1889
- sourceSkill.name,
1890
- sourceSkill.description || void 0
2194
+ tmpPath,
2195
+ entry.name,
2196
+ entry.description || void 0
1891
2197
  );
1892
- await prisma.marketplaceSkill.update({
1893
- where: { id: req.params.id },
1894
- data: { downloads: { increment: 1 } }
1895
- });
1896
2198
  return res.status(201).json({
1897
2199
  success: true,
1898
2200
  data: newSkill,
1899
2201
  message: "Skill installed from marketplace successfully"
1900
2202
  });
2203
+ } finally {
2204
+ await rm5(tmpPath, { force: true }).catch(() => {
2205
+ });
1901
2206
  }
1902
- return res.status(400).json({
1903
- success: false,
1904
- error: "Cannot install this skill. Bundle not available."
1905
- });
1906
2207
  } catch (error) {
1907
2208
  next(error);
1908
2209
  }
1909
2210
  });
1910
2211
  router3.delete("/unpublish/:skillId", async (req, res, next) => {
1911
2212
  try {
1912
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1913
- where: { skillId: req.params.skillId }
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";
1914
2229
  });
1915
- if (!marketplaceSkill) {
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) {
1916
2247
  throw new AppError(ErrorCode.NOT_FOUND, "Skill is not published to marketplace", 404);
1917
2248
  }
1918
- await prisma.marketplaceSkill.delete({
1919
- where: { id: marketplaceSkill.id }
1920
- });
2249
+ await registry.upsertIndexToRepo(index, sha);
2250
+ try {
2251
+ await prisma.marketplaceSkill.delete({
2252
+ where: { skillId: skill.id }
2253
+ });
2254
+ } catch (e) {
2255
+ }
1921
2256
  res.json({
1922
2257
  success: true,
1923
2258
  message: "Skill unpublished from marketplace successfully"
@@ -2023,16 +2358,16 @@ var dashboard_default = router4;
2023
2358
 
2024
2359
  // src/routes/config.ts
2025
2360
  import { Router as Router5 } from "express";
2026
- import { join as join6 } from "path";
2027
- import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync } from "fs";
2361
+ import { join as join7 } from "path";
2362
+ import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync } from "fs";
2028
2363
  var router5 = Router5();
2029
2364
  var HOME = process.env.HOME || process.env.USERPROFILE || "";
2030
- var BOOTSTRAP_HOME = join6(HOME, ".skillverse");
2031
- var CONFIG_FILE = join6(BOOTSTRAP_HOME, "config.json");
2365
+ var BOOTSTRAP_HOME = join7(HOME, ".skillverse");
2366
+ var CONFIG_FILE = join7(BOOTSTRAP_HOME, "config.json");
2032
2367
  function readConfig() {
2033
2368
  if (existsSync7(CONFIG_FILE)) {
2034
2369
  try {
2035
- return JSON.parse(readFileSync2(CONFIG_FILE, "utf-8"));
2370
+ return JSON.parse(readFileSync3(CONFIG_FILE, "utf-8"));
2036
2371
  } catch (e) {
2037
2372
  console.error("Failed to read config.json:", e);
2038
2373
  return {};
@@ -2047,8 +2382,17 @@ router5.get("/", (req, res, next) => {
2047
2382
  // Show saved preference if available, otherwise current env/default
2048
2383
  skillverseHome: fileConfig.skillverseHome || process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME,
2049
2384
  // registryUrl is potentially in config.json already
2050
- registryUrl: fileConfig.registryUrl || process.env.SKILLVERSE_REGISTRY || "http://localhost:4000"
2051
- // ... add other configurable items
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
+ }
2052
2396
  };
2053
2397
  res.json({
2054
2398
  success: true,
@@ -2060,19 +2404,19 @@ router5.get("/", (req, res, next) => {
2060
2404
  });
2061
2405
  router5.post("/", async (req, res, next) => {
2062
2406
  try {
2063
- const { skillverseHome, registryUrl, migrate } = req.body;
2407
+ const { skillverseHome, registryUrl, migrate, marketplace } = req.body;
2064
2408
  const currentConfig = readConfig();
2065
2409
  const oldHome = process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME;
2066
2410
  if (migrate && skillverseHome && skillverseHome !== oldHome) {
2067
2411
  console.log(`Migrating data from ${oldHome} to ${skillverseHome}...`);
2068
2412
  const { cp: cp2 } = await import("fs/promises");
2069
2413
  if (!existsSync7(skillverseHome)) {
2070
- const { mkdir: mkdir6 } = await import("fs/promises");
2071
- await mkdir6(skillverseHome, { recursive: true });
2414
+ const { mkdir: mkdir7 } = await import("fs/promises");
2415
+ await mkdir7(skillverseHome, { recursive: true });
2072
2416
  }
2073
2417
  const copyDir = async (srcName) => {
2074
- const src = join6(oldHome, srcName);
2075
- const dest = join6(skillverseHome, srcName);
2418
+ const src = join7(oldHome, srcName);
2419
+ const dest = join7(skillverseHome, srcName);
2076
2420
  if (existsSync7(src)) {
2077
2421
  console.log(`Copying ${srcName}...`);
2078
2422
  await cp2(src, dest, { recursive: true, force: true });
@@ -2080,8 +2424,8 @@ router5.post("/", async (req, res, next) => {
2080
2424
  };
2081
2425
  await copyDir("skills");
2082
2426
  await copyDir("marketplace");
2083
- const dbSrc = join6(oldHome, "skillverse.db");
2084
- const dbDest = join6(skillverseHome, "skillverse.db");
2427
+ const dbSrc = join7(oldHome, "skillverse.db");
2428
+ const dbDest = join7(skillverseHome, "skillverse.db");
2085
2429
  if (existsSync7(dbSrc)) {
2086
2430
  console.log("Copying database...");
2087
2431
  await cp2(dbSrc, dbDest, { force: true });
@@ -2091,8 +2435,8 @@ router5.post("/", async (req, res, next) => {
2091
2435
  if (existsSync7(shmSrc)) await cp2(shmSrc, dbDest + "-shm", { force: true });
2092
2436
  console.log("Updating skill paths in new database...");
2093
2437
  const { execSync } = await import("child_process");
2094
- const oldSkillsPath = join6(oldHome, "skills");
2095
- const newSkillsPath = join6(skillverseHome, "skills");
2438
+ const oldSkillsPath = join7(oldHome, "skills");
2439
+ const newSkillsPath = join7(skillverseHome, "skills");
2096
2440
  const updateQuery = `UPDATE Skill SET storagePath = replace(storagePath, '${oldSkillsPath}', '${newSkillsPath}') WHERE storagePath LIKE '${oldSkillsPath}%';`;
2097
2441
  try {
2098
2442
  execSync(`sqlite3 "${dbDest}" "${updateQuery}"`, { encoding: "utf-8" });
@@ -2102,15 +2446,27 @@ router5.post("/", async (req, res, next) => {
2102
2446
  }
2103
2447
  }
2104
2448
  }
2105
- const newConfig = {
2449
+ const nextConfig = {
2106
2450
  ...currentConfig,
2107
2451
  ...skillverseHome && { skillverseHome },
2108
2452
  ...registryUrl && { registryUrl }
2109
2453
  };
2110
- writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
2454
+ writeFileSync(CONFIG_FILE, JSON.stringify(nextConfig, null, 2));
2111
2455
  res.json({
2112
2456
  success: true,
2113
- data: newConfig,
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
+ },
2114
2470
  message: "Configuration saved. Restart server for changes to take effect."
2115
2471
  });
2116
2472
  } catch (error) {
@@ -2126,7 +2482,7 @@ router5.get("/version", async (req, res, next) => {
2126
2482
  const packageJsonPath = pathJoin(__dirname4, "..", "..", "package.json");
2127
2483
  let currentVersion = "0.0.0";
2128
2484
  if (existsSync7(packageJsonPath)) {
2129
- const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
2485
+ const pkg = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
2130
2486
  currentVersion = pkg.version || "0.0.0";
2131
2487
  }
2132
2488
  let latestVersion = currentVersion;
@@ -2181,7 +2537,7 @@ function requestLogger(req, res, next) {
2181
2537
  // src/lib/initDb.ts
2182
2538
  init_db();
2183
2539
  import { existsSync as existsSync8 } from "fs";
2184
- import { join as join7, dirname as dirname2 } from "path";
2540
+ import { join as join8, dirname as dirname2 } from "path";
2185
2541
  import { fileURLToPath as fileURLToPath2 } from "url";
2186
2542
  import { spawnSync } from "child_process";
2187
2543
  var __filename2 = fileURLToPath2(import.meta.url);
@@ -2189,13 +2545,13 @@ var __dirname2 = dirname2(__filename2);
2189
2545
  function getSchemaPath() {
2190
2546
  const possiblePaths = [
2191
2547
  // Prod: dist/bin.js -> dist/../prisma/schema.prisma (i.e. ROOT/prisma/schema.prisma)
2192
- join7(__dirname2, "../prisma/schema.prisma"),
2548
+ join8(__dirname2, "../prisma/schema.prisma"),
2193
2549
  // Dev: src/lib/initDb.ts -> src/prisma/schema.prisma ? No, src/../prisma -> server/prisma
2194
2550
  // If __dirname is src/lib: ../../prisma/schema.prisma
2195
- join7(__dirname2, "../../prisma/schema.prisma"),
2551
+ join8(__dirname2, "../../prisma/schema.prisma"),
2196
2552
  // Fallbacks
2197
- join7(process.cwd(), "prisma/schema.prisma"),
2198
- join7(process.cwd(), "server/prisma/schema.prisma")
2553
+ join8(process.cwd(), "prisma/schema.prisma"),
2554
+ join8(process.cwd(), "server/prisma/schema.prisma")
2199
2555
  ];
2200
2556
  return possiblePaths.find((p) => existsSync8(p)) || null;
2201
2557
  }
@@ -2222,10 +2578,10 @@ async function initializeDatabase() {
2222
2578
  throw new Error("Could not find schema.prisma");
2223
2579
  }
2224
2580
  const possiblePrismaBins = [
2225
- join7(__dirname2, "../node_modules/.bin/prisma"),
2226
- join7(__dirname2, "../../node_modules/.bin/prisma"),
2227
- join7(__dirname2, "../../../node_modules/.bin/prisma"),
2228
- join7(process.cwd(), "node_modules/.bin/prisma")
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")
2229
2585
  ];
2230
2586
  const prismaBin = possiblePrismaBins.find((p) => existsSync8(p)) || "prisma";
2231
2587
  console.log(` Using schema: ${schemaPath}`);
@@ -2254,13 +2610,13 @@ app.use(express.json());
2254
2610
  app.use(express.urlencoded({ extended: true }));
2255
2611
  app.use(requestLogger);
2256
2612
  var possiblePublicDirs = [
2257
- join8(__dirname3, "../public"),
2613
+ join9(__dirname3, "../public"),
2258
2614
  // Production: dist/index.js -> public
2259
- join8(__dirname3, "../../public"),
2615
+ join9(__dirname3, "../../public"),
2260
2616
  // Dev tsx might resolve here
2261
- join8(process.cwd(), "public"),
2617
+ join9(process.cwd(), "public"),
2262
2618
  // Fallback: relative to cwd
2263
- join8(process.cwd(), "server/public")
2619
+ join9(process.cwd(), "server/public")
2264
2620
  // Fallback: from root
2265
2621
  ];
2266
2622
  var publicDir = possiblePublicDirs.find((dir) => existsSync9(dir));
@@ -2280,18 +2636,18 @@ app.get("/health", (req, res) => {
2280
2636
  });
2281
2637
  if (publicDir && existsSync9(publicDir)) {
2282
2638
  app.get("*", (req, res) => {
2283
- res.sendFile(join8(publicDir, "index.html"));
2639
+ res.sendFile(join9(publicDir, "index.html"));
2284
2640
  });
2285
2641
  }
2286
2642
  app.use(errorHandler);
2287
2643
  async function initializeStorage() {
2288
- const skillverseHome = process.env.SKILLVERSE_HOME || join8(process.env.HOME || "", ".skillverse");
2289
- const skillsDir = process.env.SKILLS_DIR || join8(skillverseHome, "skills");
2290
- const marketplaceDir = process.env.MARKETPLACE_DIR || join8(skillverseHome, "marketplace");
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");
2291
2647
  const dirs = [skillverseHome, skillsDir, marketplaceDir];
2292
2648
  for (const dir of dirs) {
2293
2649
  if (!existsSync9(dir)) {
2294
- await mkdir5(dir, { recursive: true });
2650
+ await mkdir6(dir, { recursive: true });
2295
2651
  console.log(`Created directory: ${dir}`);
2296
2652
  }
2297
2653
  }
@@ -2306,7 +2662,7 @@ async function startServer(port = 3001) {
2306
2662
  return new Promise((resolve) => {
2307
2663
  app.listen(port, () => {
2308
2664
  console.log(`\u{1F680} SkillVerse server running on http://localhost:${port}`);
2309
- console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join8(process.env.HOME || "", ".skillverse")}`);
2665
+ console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join9(process.env.HOME || "", ".skillverse")}`);
2310
2666
  resolve();
2311
2667
  });
2312
2668
  });