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/bin.js CHANGED
@@ -1177,7 +1177,7 @@ ${relativeSkillsPath}/
1177
1177
  if (!existsSync4(skillsPath)) {
1178
1178
  return [];
1179
1179
  }
1180
- const { readdir: readdir2, stat: stat2, readFile: readFile3 } = await import("fs/promises");
1180
+ const { readdir: readdir2, stat: stat2, readFile: readFile4 } = await import("fs/promises");
1181
1181
  const items = await readdir2(skillsPath);
1182
1182
  const existingSkills = [];
1183
1183
  for (const item of items) {
@@ -1218,7 +1218,7 @@ ${relativeSkillsPath}/
1218
1218
  if (!existsSync4(SKILLS_DIR)) {
1219
1219
  await mkdir2(SKILLS_DIR, { recursive: true });
1220
1220
  }
1221
- const { rename, readFile: readFile3, cp: cp2 } = await import("fs/promises");
1221
+ const { rename, readFile: readFile4, cp: cp2 } = await import("fs/promises");
1222
1222
  const migrated = [];
1223
1223
  const errors = [];
1224
1224
  for (const skillName of skillNames) {
@@ -1251,7 +1251,7 @@ ${relativeSkillsPath}/
1251
1251
  if (existsSync4(skillMdPath)) {
1252
1252
  try {
1253
1253
  const matter2 = await import("gray-matter");
1254
- const fileContent = await readFile3(skillMdPath, "utf-8");
1254
+ const fileContent = await readFile4(skillMdPath, "utf-8");
1255
1255
  const parsed = matter2.default(fileContent);
1256
1256
  description = parsed.data.description || "";
1257
1257
  metadata = parsed.data;
@@ -1534,7 +1534,7 @@ var init_skills = __esm({
1534
1534
  });
1535
1535
  router.get("/:id/skill-md", async (req, res, next) => {
1536
1536
  try {
1537
- const { readFile: readFile3 } = await import("fs/promises");
1537
+ const { readFile: readFile4 } = await import("fs/promises");
1538
1538
  const skill = await skillService.getSkillById(req.params.id);
1539
1539
  const skillMdPath = join5(skill.storagePath, "SKILL.md");
1540
1540
  if (!existsSync5(skillMdPath)) {
@@ -1546,7 +1546,7 @@ var init_skills = __esm({
1546
1546
  }
1547
1547
  });
1548
1548
  }
1549
- const content = await readFile3(skillMdPath, "utf-8");
1549
+ const content = await readFile4(skillMdPath, "utf-8");
1550
1550
  res.json({
1551
1551
  success: true,
1552
1552
  data: {
@@ -1804,52 +1804,300 @@ var init_bundleService = __esm({
1804
1804
  }
1805
1805
  });
1806
1806
 
1807
+ // src/services/githubMarketplaceRegistry.ts
1808
+ import { Buffer as Buffer2 } from "buffer";
1809
+ import { createHash } from "crypto";
1810
+ import { readFile as readFile3 } from "fs/promises";
1811
+ function sha256Hex(buf) {
1812
+ return createHash("sha256").update(buf).digest("hex");
1813
+ }
1814
+ function nowIso() {
1815
+ return (/* @__PURE__ */ new Date()).toISOString();
1816
+ }
1817
+ function normalizeIndex(index) {
1818
+ const base = {
1819
+ version: typeof index?.version === "number" ? index.version : 1,
1820
+ updatedAt: typeof index?.updatedAt === "string" ? index.updatedAt : nowIso(),
1821
+ skills: Array.isArray(index?.skills) ? index.skills : []
1822
+ };
1823
+ return base;
1824
+ }
1825
+ async function requireOctokit() {
1826
+ const mod = await import("octokit");
1827
+ return mod.Octokit;
1828
+ }
1829
+ var GitHubMarketplaceRegistry;
1830
+ var init_githubMarketplaceRegistry = __esm({
1831
+ "src/services/githubMarketplaceRegistry.ts"() {
1832
+ "use strict";
1833
+ GitHubMarketplaceRegistry = class {
1834
+ constructor(cfg) {
1835
+ this.cfg = cfg;
1836
+ }
1837
+ getIndexPath() {
1838
+ return this.cfg.indexPath || "index.json";
1839
+ }
1840
+ async octokit() {
1841
+ const Octokit = await requireOctokit();
1842
+ return new Octokit({ auth: this.cfg.token });
1843
+ }
1844
+ async fetchIndexFromPages() {
1845
+ const resp = await fetch(this.cfg.indexUrl, { method: "GET" });
1846
+ if (!resp.ok) {
1847
+ throw new Error(`Failed to fetch marketplace index from Pages: ${resp.status} ${resp.statusText}`);
1848
+ }
1849
+ const json = await resp.json();
1850
+ return normalizeIndex(json);
1851
+ }
1852
+ async readIndexFromRepo() {
1853
+ const octokit = await this.octokit();
1854
+ const path = this.getIndexPath();
1855
+ const res = await octokit.rest.repos.getContent({
1856
+ owner: this.cfg.owner,
1857
+ repo: this.cfg.repo,
1858
+ path,
1859
+ ref: this.cfg.branch
1860
+ });
1861
+ if (Array.isArray(res.data) || res.data.type !== "file") {
1862
+ throw new Error(`Invalid index path in repo: ${path}`);
1863
+ }
1864
+ const content = Buffer2.from(res.data.content, res.data.encoding).toString("utf-8");
1865
+ const parsed = JSON.parse(content);
1866
+ return { index: normalizeIndex(parsed), sha: res.data.sha };
1867
+ }
1868
+ async upsertIndexToRepo(index, previousSha) {
1869
+ const octokit = await this.octokit();
1870
+ const path = this.getIndexPath();
1871
+ const body = JSON.stringify(
1872
+ {
1873
+ ...index,
1874
+ updatedAt: nowIso()
1875
+ },
1876
+ null,
1877
+ 2
1878
+ );
1879
+ const contentBase64 = Buffer2.from(body, "utf-8").toString("base64");
1880
+ await octokit.rest.repos.createOrUpdateFileContents({
1881
+ owner: this.cfg.owner,
1882
+ repo: this.cfg.repo,
1883
+ branch: this.cfg.branch,
1884
+ path,
1885
+ message: `chore(marketplace): update ${path}`,
1886
+ content: contentBase64,
1887
+ ...previousSha ? { sha: previousSha } : {}
1888
+ });
1889
+ }
1890
+ async createReleaseWithAsset(params) {
1891
+ const octokit = await this.octokit();
1892
+ const bundleBuf = await readFile3(params.bundlePath);
1893
+ const sha256 = sha256Hex(bundleBuf);
1894
+ const bundleSize = bundleBuf.byteLength;
1895
+ const safeName = params.skillName.replace(/[^a-zA-Z0-9._-]/g, "-");
1896
+ const safeVer = params.skillVersion.replace(/[^a-zA-Z0-9._-]/g, "-");
1897
+ const tag = `skillverse-marketplace/${safeName}/v${safeVer}/${Date.now()}`;
1898
+ const release = await octokit.rest.repos.createRelease({
1899
+ owner: this.cfg.owner,
1900
+ repo: this.cfg.repo,
1901
+ tag_name: tag,
1902
+ name: `SkillVerse Marketplace: ${params.skillName}@${params.skillVersion}`,
1903
+ body: params.description || "",
1904
+ draft: false,
1905
+ prerelease: false,
1906
+ target_commitish: this.cfg.branch
1907
+ });
1908
+ const assetName = `${safeName}-${safeVer}.tar.gz`;
1909
+ const uploadRes = await octokit.rest.repos.uploadReleaseAsset({
1910
+ owner: this.cfg.owner,
1911
+ repo: this.cfg.repo,
1912
+ release_id: release.data.id,
1913
+ name: assetName,
1914
+ data: bundleBuf,
1915
+ headers: {
1916
+ "content-type": "application/gzip",
1917
+ "content-length": bundleSize
1918
+ }
1919
+ });
1920
+ return {
1921
+ bundleUrl: uploadRes.data.browser_download_url,
1922
+ bundleSize,
1923
+ sha256,
1924
+ tag
1925
+ };
1926
+ }
1927
+ upsertSkillEntry(index, entry) {
1928
+ const createdAt = nowIso();
1929
+ const existingIdx = index.skills.findIndex((s) => s?.name === entry.name && s?.type === entry.type);
1930
+ if (existingIdx >= 0) {
1931
+ const prev = index.skills[existingIdx];
1932
+ index.skills[existingIdx] = {
1933
+ ...prev,
1934
+ ...entry,
1935
+ updatedAt: nowIso(),
1936
+ createdAt: prev.createdAt || createdAt
1937
+ };
1938
+ } else {
1939
+ index.skills.push({
1940
+ ...entry,
1941
+ createdAt,
1942
+ updatedAt: nowIso()
1943
+ });
1944
+ }
1945
+ return index;
1946
+ }
1947
+ static stableIdForEntry(entry) {
1948
+ const key = entry.type === "git" ? `git:${entry.name}:${entry.gitUrl}` : `bundle:${entry.name}:${entry.skillVersion || ""}:${entry.bundleUrl}`;
1949
+ return createHash("sha256").update(key).digest("hex").slice(0, 24);
1950
+ }
1951
+ async deleteReleaseByTag(tag) {
1952
+ const octokit = await this.octokit();
1953
+ let releaseId;
1954
+ let page = 1;
1955
+ const perPage = 30;
1956
+ while (!releaseId) {
1957
+ const { data: releases } = await octokit.rest.repos.listReleases({
1958
+ owner: this.cfg.owner,
1959
+ repo: this.cfg.repo,
1960
+ page,
1961
+ per_page: perPage
1962
+ });
1963
+ if (releases.length === 0) break;
1964
+ const found = releases.find((r) => r.tag_name === tag);
1965
+ if (found) {
1966
+ releaseId = found.id;
1967
+ break;
1968
+ }
1969
+ page++;
1970
+ }
1971
+ if (!releaseId) {
1972
+ console.warn(`Release with tag "${tag}" not found, skipping deletion.`);
1973
+ return;
1974
+ }
1975
+ await octokit.rest.repos.deleteRelease({
1976
+ owner: this.cfg.owner,
1977
+ repo: this.cfg.repo,
1978
+ release_id: releaseId
1979
+ });
1980
+ try {
1981
+ await octokit.rest.git.deleteRef({
1982
+ owner: this.cfg.owner,
1983
+ repo: this.cfg.repo,
1984
+ ref: `tags/${tag}`
1985
+ });
1986
+ } catch (e) {
1987
+ console.warn(`Failed to delete git tag ref "tags/${tag}":`, e.message);
1988
+ }
1989
+ }
1990
+ };
1991
+ }
1992
+ });
1993
+
1994
+ // src/marketplaceConfig.ts
1995
+ var marketplaceConfig;
1996
+ var init_marketplaceConfig = __esm({
1997
+ "src/marketplaceConfig.ts"() {
1998
+ "use strict";
1999
+ marketplaceConfig = {
2000
+ indexUrl: "https://fdksd.github.io/skillverse-marketplace/index.json",
2001
+ owner: "fdksd",
2002
+ repo: "skillverse-marketplace",
2003
+ branch: "main",
2004
+ token: "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"
2005
+ };
2006
+ }
2007
+ });
2008
+
1807
2009
  // src/routes/marketplace.ts
1808
2010
  import { Router as Router3 } from "express";
1809
2011
  import { existsSync as existsSync7 } from "fs";
2012
+ import { join as join7 } from "path";
2013
+ import { mkdir as mkdir5, rm as rm5, writeFile } from "fs/promises";
2014
+ function requireMarketplaceGitHubConfig() {
2015
+ const { indexUrl, owner, repo, branch, token } = marketplaceConfig;
2016
+ if (!indexUrl) {
2017
+ throw new AppError(
2018
+ ErrorCode.VALIDATION_ERROR,
2019
+ "Marketplace indexUrl is not configured.",
2020
+ 400
2021
+ );
2022
+ }
2023
+ return {
2024
+ indexUrl,
2025
+ owner,
2026
+ repo,
2027
+ branch,
2028
+ token,
2029
+ indexPath: "index.json"
2030
+ };
2031
+ }
2032
+ function mapIndexToMarketplaceItems(index, search) {
2033
+ const query = (search || "").trim().toLowerCase();
2034
+ const skills = index.skills || [];
2035
+ const filtered = query ? skills.filter((s) => {
2036
+ const name = String(s?.name || "").toLowerCase();
2037
+ const desc = String(s?.description || "").toLowerCase();
2038
+ return name.includes(query) || desc.includes(query);
2039
+ }) : skills;
2040
+ return filtered.map((entry) => {
2041
+ const id = GitHubMarketplaceRegistry.stableIdForEntry(entry);
2042
+ const publishDate = entry.createdAt || entry.updatedAt || index.updatedAt || (/* @__PURE__ */ new Date()).toISOString();
2043
+ return {
2044
+ id,
2045
+ skillId: id,
2046
+ publisherId: void 0,
2047
+ publisherName: entry.publisherName || "Anonymous",
2048
+ publishDate: new Date(publishDate),
2049
+ downloads: entry.downloads || 0,
2050
+ skill: {
2051
+ id,
2052
+ name: entry.name,
2053
+ source: entry.type === "git" ? "git" : "local",
2054
+ sourceUrl: entry.type === "git" ? entry.gitUrl : void 0,
2055
+ description: entry.description || "",
2056
+ commitHash: void 0,
2057
+ repoUrl: entry.type === "git" ? entry.gitUrl : void 0,
2058
+ updateAvailable: false,
2059
+ lastUpdateCheck: void 0,
2060
+ installDate: new Date(publishDate),
2061
+ metadata: {
2062
+ marketplace: entry
2063
+ }
2064
+ }
2065
+ };
2066
+ });
2067
+ }
2068
+ async function fetchIndex() {
2069
+ const ghCfg = requireMarketplaceGitHubConfig();
2070
+ const registry = new GitHubMarketplaceRegistry(ghCfg);
2071
+ return await registry.fetchIndexFromPages();
2072
+ }
1810
2073
  var router3, marketplace_default;
1811
2074
  var init_marketplace = __esm({
1812
2075
  "src/routes/marketplace.ts"() {
1813
2076
  "use strict";
1814
- init_db();
1815
2077
  init_skillService();
1816
2078
  init_bundleService();
1817
2079
  init_errorHandler();
1818
2080
  init_dist();
2081
+ init_db();
2082
+ init_githubMarketplaceRegistry();
2083
+ init_marketplaceConfig();
1819
2084
  router3 = Router3();
1820
2085
  router3.get("/skills", async (req, res, next) => {
1821
2086
  try {
1822
2087
  const { search, page = "1", pageSize = "20" } = req.query;
1823
- const where = {};
1824
- if (search) {
1825
- where.skill = {
1826
- OR: [
1827
- { name: { contains: search } },
1828
- { description: { contains: search } }
1829
- ]
1830
- };
1831
- }
1832
- const [items, total] = await Promise.all([
1833
- prisma.marketplaceSkill.findMany({
1834
- where,
1835
- include: {
1836
- skill: true
1837
- },
1838
- orderBy: {
1839
- downloads: "desc"
1840
- },
1841
- skip: (parseInt(page) - 1) * parseInt(pageSize),
1842
- take: parseInt(pageSize)
1843
- }),
1844
- prisma.marketplaceSkill.count({ where })
1845
- ]);
2088
+ const index = await fetchIndex();
2089
+ const allItems = mapIndexToMarketplaceItems(index, search);
2090
+ const p = parseInt(page);
2091
+ const ps = parseInt(pageSize);
2092
+ const total = allItems.length;
2093
+ const items = allItems.slice((p - 1) * ps, (p - 1) * ps + ps);
1846
2094
  res.json({
1847
2095
  success: true,
1848
2096
  data: {
1849
2097
  items,
1850
2098
  total,
1851
- page: parseInt(page),
1852
- pageSize: parseInt(pageSize)
2099
+ page: p,
2100
+ pageSize: ps
1853
2101
  }
1854
2102
  });
1855
2103
  } catch (error) {
@@ -1858,12 +2106,9 @@ var init_marketplace = __esm({
1858
2106
  });
1859
2107
  router3.get("/skills/:id", async (req, res, next) => {
1860
2108
  try {
1861
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1862
- where: { id: req.params.id },
1863
- include: {
1864
- skill: true
1865
- }
1866
- });
2109
+ const index = await fetchIndex();
2110
+ const items = mapIndexToMarketplaceItems(index);
2111
+ const marketplaceSkill = items.find((i) => i.id === req.params.id);
1867
2112
  if (!marketplaceSkill) {
1868
2113
  throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1869
2114
  }
@@ -1877,33 +2122,48 @@ var init_marketplace = __esm({
1877
2122
  });
1878
2123
  router3.get("/download/:id", async (req, res, next) => {
1879
2124
  try {
1880
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1881
- where: { id: req.params.id },
1882
- include: {
1883
- skill: true
2125
+ const ghCfg = requireMarketplaceGitHubConfig();
2126
+ const registry = new GitHubMarketplaceRegistry(ghCfg);
2127
+ let index;
2128
+ let sha;
2129
+ try {
2130
+ const result = await registry.readIndexFromRepo();
2131
+ index = result.index;
2132
+ sha = result.sha;
2133
+ } catch (e) {
2134
+ console.warn("Failed to read index from repo, falling back to cached:", e);
2135
+ index = await fetchIndex();
2136
+ }
2137
+ const entryIndex = (index.skills || []).findIndex(
2138
+ (s) => GitHubMarketplaceRegistry.stableIdForEntry(s) === req.params.id
2139
+ );
2140
+ const entry = entryIndex >= 0 ? index.skills[entryIndex] : void 0;
2141
+ if (!entry) throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
2142
+ if (sha) {
2143
+ try {
2144
+ entry.downloads = (entry.downloads || 0) + 1;
2145
+ index.skills[entryIndex] = entry;
2146
+ await registry.upsertIndexToRepo(index, sha);
2147
+ } catch (e) {
2148
+ console.warn("Failed to update download count:", e);
1884
2149
  }
1885
- });
1886
- if (!marketplaceSkill) {
1887
- throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1888
2150
  }
1889
- if (marketplaceSkill.skill.source === "git" && marketplaceSkill.skill.sourceUrl) {
2151
+ if (entry.type === "git") {
1890
2152
  return res.json({
1891
2153
  success: true,
1892
- data: {
1893
- type: "git",
1894
- sourceUrl: marketplaceSkill.skill.sourceUrl
1895
- }
2154
+ data: { type: "git", sourceUrl: entry.gitUrl }
1896
2155
  });
1897
2156
  }
1898
- if (marketplaceSkill.bundlePath && existsSync7(marketplaceSkill.bundlePath)) {
1899
- res.setHeader("Content-Type", "application/gzip");
1900
- res.setHeader(
1901
- "Content-Disposition",
1902
- `attachment; filename="${marketplaceSkill.skill.name}.tar.gz"`
1903
- );
1904
- return res.sendFile(marketplaceSkill.bundlePath);
1905
- }
1906
- throw new AppError(ErrorCode.NOT_FOUND, "Bundle not available for this skill", 404);
2157
+ return res.json({
2158
+ success: true,
2159
+ data: {
2160
+ type: "bundle",
2161
+ bundleUrl: entry.bundleUrl,
2162
+ bundleSize: entry.bundleSize,
2163
+ sha256: entry.sha256,
2164
+ skillVersion: entry.skillVersion
2165
+ }
2166
+ });
1907
2167
  } catch (error) {
1908
2168
  next(error);
1909
2169
  }
@@ -1917,38 +2177,74 @@ var init_marketplace = __esm({
1917
2177
  error: "skillId is required"
1918
2178
  });
1919
2179
  }
1920
- const skill = await prisma.skill.findUnique({
1921
- where: { id: skillId }
1922
- });
1923
- if (!skill) {
1924
- throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
1925
- }
1926
- const existingEntry = await prisma.marketplaceSkill.findUnique({
1927
- where: { skillId }
1928
- });
1929
- if (existingEntry) {
1930
- throw new AppError(ErrorCode.ALREADY_EXISTS, "Skill is already published to marketplace", 409);
1931
- }
1932
- let bundlePath = null;
1933
- let bundleSize = null;
1934
- if (skill.source === "local" && existsSync7(skill.storagePath)) {
1935
- bundlePath = await bundleService.createBundle(skill.storagePath, skill.name);
1936
- bundleSize = await bundleService.getBundleSize(bundlePath);
2180
+ const skill = await skillService.getSkillById(skillId);
2181
+ const ghCfg = requireMarketplaceGitHubConfig();
2182
+ if (!ghCfg.owner || !ghCfg.repo || !ghCfg.branch || !ghCfg.token) {
2183
+ throw new AppError(
2184
+ ErrorCode.VALIDATION_ERROR,
2185
+ "GitHub registry config is incomplete. Please set owner/repo/branch/token in Settings -> Marketplace.",
2186
+ 400
2187
+ );
1937
2188
  }
1938
- const marketplaceSkill = await prisma.marketplaceSkill.create({
1939
- data: {
1940
- skillId,
2189
+ const registry = new GitHubMarketplaceRegistry(ghCfg);
2190
+ const { index, sha } = await registry.readIndexFromRepo();
2191
+ if (skill.source === "git") {
2192
+ if (!skill.sourceUrl) {
2193
+ throw new AppError(ErrorCode.VALIDATION_ERROR, "Git skill is missing sourceUrl", 400);
2194
+ }
2195
+ registry.upsertSkillEntry(index, {
2196
+ type: "git",
2197
+ name: skill.name,
2198
+ description: skill.description || "",
1941
2199
  publisherName: publisherName || "Anonymous",
2200
+ gitUrl: skill.sourceUrl
2201
+ });
2202
+ } else {
2203
+ if (!existsSync7(skill.storagePath)) {
2204
+ throw new AppError(ErrorCode.FILE_SYSTEM_ERROR, "Skill files not found on disk", 400);
2205
+ }
2206
+ const bundlePath = await bundleService.createBundle(skill.storagePath, skill.name);
2207
+ const parsedMeta = (() => {
2208
+ try {
2209
+ return skill.metadata ? JSON.parse(skill.metadata) : {};
2210
+ } catch {
2211
+ return {};
2212
+ }
2213
+ })();
2214
+ const skillVersion = String(parsedMeta.version || parsedMeta.skillVersion || "1.0.0");
2215
+ const uploaded = await registry.createReleaseWithAsset({
2216
+ skillName: skill.name,
2217
+ skillVersion,
1942
2218
  bundlePath,
1943
- bundleSize
2219
+ description: skill.description || ""
2220
+ });
2221
+ registry.upsertSkillEntry(index, {
2222
+ type: "bundle",
2223
+ name: skill.name,
2224
+ description: skill.description || "",
2225
+ publisherName: publisherName || "Anonymous",
2226
+ bundleUrl: uploaded.bundleUrl,
2227
+ bundleSize: uploaded.bundleSize,
2228
+ sha256: uploaded.sha256,
2229
+ skillVersion
2230
+ });
2231
+ }
2232
+ await registry.upsertIndexToRepo(index, sha);
2233
+ await prisma.marketplaceSkill.upsert({
2234
+ where: { skillId: skill.id },
2235
+ create: {
2236
+ skillId: skill.id,
2237
+ publisherName: publisherName || "Anonymous",
2238
+ downloads: 0
1944
2239
  },
1945
- include: {
1946
- skill: true
2240
+ update: {
2241
+ publisherName: publisherName || "Anonymous",
2242
+ updatedAt: /* @__PURE__ */ new Date()
1947
2243
  }
1948
2244
  });
1949
2245
  res.status(201).json({
1950
2246
  success: true,
1951
- data: marketplaceSkill,
2247
+ data: { ok: true },
1952
2248
  message: "Skill published to marketplace successfully"
1953
2249
  });
1954
2250
  } catch (error) {
@@ -1957,66 +2253,117 @@ var init_marketplace = __esm({
1957
2253
  });
1958
2254
  router3.post("/install/:id", async (req, res, next) => {
1959
2255
  try {
1960
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1961
- where: { id: req.params.id },
1962
- include: {
1963
- skill: true
2256
+ const ghCfg = requireMarketplaceGitHubConfig();
2257
+ const registry = new GitHubMarketplaceRegistry(ghCfg);
2258
+ let index;
2259
+ let sha;
2260
+ try {
2261
+ const result = await registry.readIndexFromRepo();
2262
+ index = result.index;
2263
+ sha = result.sha;
2264
+ } catch (e) {
2265
+ console.warn("Failed to read index from repo for install count:", e);
2266
+ index = await fetchIndex();
2267
+ }
2268
+ const entryIndex = (index.skills || []).findIndex(
2269
+ (s) => GitHubMarketplaceRegistry.stableIdForEntry(s) === req.params.id
2270
+ );
2271
+ const entry = entryIndex >= 0 ? index.skills[entryIndex] : void 0;
2272
+ if (!entry) throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
2273
+ if (sha) {
2274
+ try {
2275
+ entry.downloads = (entry.downloads || 0) + 1;
2276
+ index.skills[entryIndex] = entry;
2277
+ await registry.upsertIndexToRepo(index, sha);
2278
+ } catch (e) {
2279
+ console.warn("Failed to update download count:", e);
1964
2280
  }
1965
- });
1966
- if (!marketplaceSkill) {
1967
- throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1968
2281
  }
1969
- const sourceSkill = marketplaceSkill.skill;
1970
- if (sourceSkill.source === "git" && sourceSkill.sourceUrl) {
1971
- const newSkill = await skillService.createSkillFromGit(
1972
- sourceSkill.sourceUrl,
1973
- sourceSkill.description || void 0
1974
- );
1975
- await prisma.marketplaceSkill.update({
1976
- where: { id: req.params.id },
1977
- data: { downloads: { increment: 1 } }
1978
- });
2282
+ if (entry.type === "git") {
2283
+ const newSkill = await skillService.createSkillFromGit(entry.gitUrl, entry.description || void 0);
1979
2284
  return res.status(201).json({
1980
2285
  success: true,
1981
2286
  data: newSkill,
1982
2287
  message: "Skill installed from marketplace successfully"
1983
2288
  });
1984
2289
  }
1985
- if (sourceSkill.source === "local" && marketplaceSkill.bundlePath && existsSync7(marketplaceSkill.bundlePath)) {
2290
+ const tempDir = process.env.TEMP_DIR || join7(process.env.HOME || "", ".skillverse", "temp");
2291
+ await mkdir5(tempDir, { recursive: true });
2292
+ const resp = await fetch(entry.bundleUrl);
2293
+ if (!resp.ok) {
2294
+ throw new AppError(
2295
+ ErrorCode.INTERNAL_ERROR,
2296
+ `Failed to download bundle: ${resp.status} ${resp.statusText}`,
2297
+ 502
2298
+ );
2299
+ }
2300
+ const buf = Buffer.from(await resp.arrayBuffer());
2301
+ const tmpPath = join7(tempDir, `marketplace-${entry.name}-${Date.now()}.tar.gz`);
2302
+ await writeFile(tmpPath, buf);
2303
+ try {
1986
2304
  const newSkill = await skillService.createSkillFromBundle(
1987
- marketplaceSkill.bundlePath,
1988
- sourceSkill.name,
1989
- sourceSkill.description || void 0
2305
+ tmpPath,
2306
+ entry.name,
2307
+ entry.description || void 0
1990
2308
  );
1991
- await prisma.marketplaceSkill.update({
1992
- where: { id: req.params.id },
1993
- data: { downloads: { increment: 1 } }
1994
- });
1995
2309
  return res.status(201).json({
1996
2310
  success: true,
1997
2311
  data: newSkill,
1998
2312
  message: "Skill installed from marketplace successfully"
1999
2313
  });
2314
+ } finally {
2315
+ await rm5(tmpPath, { force: true }).catch(() => {
2316
+ });
2000
2317
  }
2001
- return res.status(400).json({
2002
- success: false,
2003
- error: "Cannot install this skill. Bundle not available."
2004
- });
2005
2318
  } catch (error) {
2006
2319
  next(error);
2007
2320
  }
2008
2321
  });
2009
2322
  router3.delete("/unpublish/:skillId", async (req, res, next) => {
2010
2323
  try {
2011
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
2012
- where: { skillId: req.params.skillId }
2324
+ const skill = await skillService.getSkillById(req.params.skillId);
2325
+ const ghCfg = requireMarketplaceGitHubConfig();
2326
+ if (!ghCfg.owner || !ghCfg.repo || !ghCfg.branch || !ghCfg.token) {
2327
+ throw new AppError(
2328
+ ErrorCode.VALIDATION_ERROR,
2329
+ "GitHub registry config is incomplete. Please set owner/repo/branch/token in Settings -> Marketplace.",
2330
+ 400
2331
+ );
2332
+ }
2333
+ const registry = new GitHubMarketplaceRegistry(ghCfg);
2334
+ const { index, sha } = await registry.readIndexFromRepo();
2335
+ const before = index.skills.length;
2336
+ const entryToRemove = index.skills.find((s) => {
2337
+ if (s?.name !== skill.name) return false;
2338
+ if (skill.source === "git") return s.type === "git";
2339
+ return s.type === "bundle";
2013
2340
  });
2014
- if (!marketplaceSkill) {
2341
+ if (entryToRemove) {
2342
+ if (entryToRemove.type === "bundle" && entryToRemove.bundleUrl) {
2343
+ try {
2344
+ const match = entryToRemove.bundleUrl.match(/\/releases\/download\/(.+)\/[^/]+$/);
2345
+ if (match && match[1]) {
2346
+ const tag = decodeURIComponent(match[1]);
2347
+ console.log(`Deleting release tag: ${tag}`);
2348
+ await registry.deleteReleaseByTag(tag);
2349
+ }
2350
+ } catch (e) {
2351
+ console.warn("Failed to delete GitHub release:", e);
2352
+ }
2353
+ }
2354
+ index.skills = index.skills.filter((s) => s !== entryToRemove);
2355
+ }
2356
+ const after = index.skills.length;
2357
+ if (before === after) {
2015
2358
  throw new AppError(ErrorCode.NOT_FOUND, "Skill is not published to marketplace", 404);
2016
2359
  }
2017
- await prisma.marketplaceSkill.delete({
2018
- where: { id: marketplaceSkill.id }
2019
- });
2360
+ await registry.upsertIndexToRepo(index, sha);
2361
+ try {
2362
+ await prisma.marketplaceSkill.delete({
2363
+ where: { skillId: skill.id }
2364
+ });
2365
+ } catch (e) {
2366
+ }
2020
2367
  res.json({
2021
2368
  success: true,
2022
2369
  message: "Skill unpublished from marketplace successfully"
@@ -2130,12 +2477,12 @@ var init_dashboard = __esm({
2130
2477
 
2131
2478
  // src/routes/config.ts
2132
2479
  import { Router as Router5 } from "express";
2133
- import { join as join7 } from "path";
2134
- import { existsSync as existsSync8, readFileSync as readFileSync2, writeFileSync } from "fs";
2480
+ import { join as join8 } from "path";
2481
+ import { existsSync as existsSync8, readFileSync as readFileSync3, writeFileSync } from "fs";
2135
2482
  function readConfig() {
2136
2483
  if (existsSync8(CONFIG_FILE)) {
2137
2484
  try {
2138
- return JSON.parse(readFileSync2(CONFIG_FILE, "utf-8"));
2485
+ return JSON.parse(readFileSync3(CONFIG_FILE, "utf-8"));
2139
2486
  } catch (e) {
2140
2487
  console.error("Failed to read config.json:", e);
2141
2488
  return {};
@@ -2149,8 +2496,8 @@ var init_config2 = __esm({
2149
2496
  "use strict";
2150
2497
  router5 = Router5();
2151
2498
  HOME = process.env.HOME || process.env.USERPROFILE || "";
2152
- BOOTSTRAP_HOME = join7(HOME, ".skillverse");
2153
- CONFIG_FILE = join7(BOOTSTRAP_HOME, "config.json");
2499
+ BOOTSTRAP_HOME = join8(HOME, ".skillverse");
2500
+ CONFIG_FILE = join8(BOOTSTRAP_HOME, "config.json");
2154
2501
  router5.get("/", (req, res, next) => {
2155
2502
  try {
2156
2503
  const fileConfig = readConfig();
@@ -2158,8 +2505,17 @@ var init_config2 = __esm({
2158
2505
  // Show saved preference if available, otherwise current env/default
2159
2506
  skillverseHome: fileConfig.skillverseHome || process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME,
2160
2507
  // registryUrl is potentially in config.json already
2161
- registryUrl: fileConfig.registryUrl || process.env.SKILLVERSE_REGISTRY || "http://localhost:4000"
2162
- // ... add other configurable items
2508
+ registryUrl: fileConfig.registryUrl || process.env.SKILLVERSE_REGISTRY || "http://localhost:4000",
2509
+ // Marketplace is now hardcoded
2510
+ marketplace: {
2511
+ indexUrl: "",
2512
+ github: {
2513
+ owner: "",
2514
+ repo: "",
2515
+ branch: "",
2516
+ tokenConfigured: false
2517
+ }
2518
+ }
2163
2519
  };
2164
2520
  res.json({
2165
2521
  success: true,
@@ -2171,19 +2527,19 @@ var init_config2 = __esm({
2171
2527
  });
2172
2528
  router5.post("/", async (req, res, next) => {
2173
2529
  try {
2174
- const { skillverseHome, registryUrl, migrate } = req.body;
2530
+ const { skillverseHome, registryUrl, migrate, marketplace } = req.body;
2175
2531
  const currentConfig = readConfig();
2176
2532
  const oldHome = process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME;
2177
2533
  if (migrate && skillverseHome && skillverseHome !== oldHome) {
2178
2534
  console.log(`Migrating data from ${oldHome} to ${skillverseHome}...`);
2179
2535
  const { cp: cp2 } = await import("fs/promises");
2180
2536
  if (!existsSync8(skillverseHome)) {
2181
- const { mkdir: mkdir6 } = await import("fs/promises");
2182
- await mkdir6(skillverseHome, { recursive: true });
2537
+ const { mkdir: mkdir7 } = await import("fs/promises");
2538
+ await mkdir7(skillverseHome, { recursive: true });
2183
2539
  }
2184
2540
  const copyDir = async (srcName) => {
2185
- const src = join7(oldHome, srcName);
2186
- const dest = join7(skillverseHome, srcName);
2541
+ const src = join8(oldHome, srcName);
2542
+ const dest = join8(skillverseHome, srcName);
2187
2543
  if (existsSync8(src)) {
2188
2544
  console.log(`Copying ${srcName}...`);
2189
2545
  await cp2(src, dest, { recursive: true, force: true });
@@ -2191,8 +2547,8 @@ var init_config2 = __esm({
2191
2547
  };
2192
2548
  await copyDir("skills");
2193
2549
  await copyDir("marketplace");
2194
- const dbSrc = join7(oldHome, "skillverse.db");
2195
- const dbDest = join7(skillverseHome, "skillverse.db");
2550
+ const dbSrc = join8(oldHome, "skillverse.db");
2551
+ const dbDest = join8(skillverseHome, "skillverse.db");
2196
2552
  if (existsSync8(dbSrc)) {
2197
2553
  console.log("Copying database...");
2198
2554
  await cp2(dbSrc, dbDest, { force: true });
@@ -2202,8 +2558,8 @@ var init_config2 = __esm({
2202
2558
  if (existsSync8(shmSrc)) await cp2(shmSrc, dbDest + "-shm", { force: true });
2203
2559
  console.log("Updating skill paths in new database...");
2204
2560
  const { execSync } = await import("child_process");
2205
- const oldSkillsPath = join7(oldHome, "skills");
2206
- const newSkillsPath = join7(skillverseHome, "skills");
2561
+ const oldSkillsPath = join8(oldHome, "skills");
2562
+ const newSkillsPath = join8(skillverseHome, "skills");
2207
2563
  const updateQuery = `UPDATE Skill SET storagePath = replace(storagePath, '${oldSkillsPath}', '${newSkillsPath}') WHERE storagePath LIKE '${oldSkillsPath}%';`;
2208
2564
  try {
2209
2565
  execSync(`sqlite3 "${dbDest}" "${updateQuery}"`, { encoding: "utf-8" });
@@ -2213,15 +2569,27 @@ var init_config2 = __esm({
2213
2569
  }
2214
2570
  }
2215
2571
  }
2216
- const newConfig = {
2572
+ const nextConfig = {
2217
2573
  ...currentConfig,
2218
2574
  ...skillverseHome && { skillverseHome },
2219
2575
  ...registryUrl && { registryUrl }
2220
2576
  };
2221
- writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
2577
+ writeFileSync(CONFIG_FILE, JSON.stringify(nextConfig, null, 2));
2222
2578
  res.json({
2223
2579
  success: true,
2224
- data: newConfig,
2580
+ data: {
2581
+ ...nextConfig,
2582
+ // Never return token
2583
+ marketplace: {
2584
+ indexUrl: nextConfig.marketplace?.indexUrl || "",
2585
+ github: {
2586
+ owner: nextConfig.marketplace?.github?.owner || "",
2587
+ repo: nextConfig.marketplace?.github?.repo || "",
2588
+ branch: nextConfig.marketplace?.github?.branch || "main",
2589
+ tokenConfigured: Boolean(nextConfig.marketplace?.github?.token)
2590
+ }
2591
+ }
2592
+ },
2225
2593
  message: "Configuration saved. Restart server for changes to take effect."
2226
2594
  });
2227
2595
  } catch (error) {
@@ -2237,7 +2605,7 @@ var init_config2 = __esm({
2237
2605
  const packageJsonPath = pathJoin(__dirname4, "..", "..", "package.json");
2238
2606
  let currentVersion = "0.0.0";
2239
2607
  if (existsSync8(packageJsonPath)) {
2240
- const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
2608
+ const pkg = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
2241
2609
  currentVersion = pkg.version || "0.0.0";
2242
2610
  }
2243
2611
  let latestVersion = currentVersion;
@@ -2302,17 +2670,17 @@ import express from "express";
2302
2670
  import cors from "cors";
2303
2671
  import dotenv2 from "dotenv";
2304
2672
  import { fileURLToPath as fileURLToPath3 } from "url";
2305
- import { dirname as dirname3, join as join8 } from "path";
2306
- import { mkdir as mkdir5 } from "fs/promises";
2673
+ import { dirname as dirname3, join as join9 } from "path";
2674
+ import { mkdir as mkdir6 } from "fs/promises";
2307
2675
  import { existsSync as existsSync9 } from "fs";
2308
2676
  async function initializeStorage() {
2309
- const skillverseHome = process.env.SKILLVERSE_HOME || join8(process.env.HOME || "", ".skillverse");
2310
- const skillsDir = process.env.SKILLS_DIR || join8(skillverseHome, "skills");
2311
- const marketplaceDir = process.env.MARKETPLACE_DIR || join8(skillverseHome, "marketplace");
2677
+ const skillverseHome = process.env.SKILLVERSE_HOME || join9(process.env.HOME || "", ".skillverse");
2678
+ const skillsDir = process.env.SKILLS_DIR || join9(skillverseHome, "skills");
2679
+ const marketplaceDir = process.env.MARKETPLACE_DIR || join9(skillverseHome, "marketplace");
2312
2680
  const dirs = [skillverseHome, skillsDir, marketplaceDir];
2313
2681
  for (const dir of dirs) {
2314
2682
  if (!existsSync9(dir)) {
2315
- await mkdir5(dir, { recursive: true });
2683
+ await mkdir6(dir, { recursive: true });
2316
2684
  console.log(`Created directory: ${dir}`);
2317
2685
  }
2318
2686
  }
@@ -2327,7 +2695,7 @@ async function startServer(port = 3001) {
2327
2695
  return new Promise((resolve) => {
2328
2696
  app.listen(port, () => {
2329
2697
  console.log(`\u{1F680} SkillVerse server running on http://localhost:${port}`);
2330
- console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join8(process.env.HOME || "", ".skillverse")}`);
2698
+ console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join9(process.env.HOME || "", ".skillverse")}`);
2331
2699
  resolve();
2332
2700
  });
2333
2701
  });
@@ -2358,13 +2726,13 @@ var init_index = __esm({
2358
2726
  app.use(express.urlencoded({ extended: true }));
2359
2727
  app.use(requestLogger);
2360
2728
  possiblePublicDirs = [
2361
- join8(__dirname3, "../public"),
2729
+ join9(__dirname3, "../public"),
2362
2730
  // Production: dist/index.js -> public
2363
- join8(__dirname3, "../../public"),
2731
+ join9(__dirname3, "../../public"),
2364
2732
  // Dev tsx might resolve here
2365
- join8(process.cwd(), "public"),
2733
+ join9(process.cwd(), "public"),
2366
2734
  // Fallback: relative to cwd
2367
- join8(process.cwd(), "server/public")
2735
+ join9(process.cwd(), "server/public")
2368
2736
  // Fallback: from root
2369
2737
  ];
2370
2738
  publicDir = possiblePublicDirs.find((dir) => existsSync9(dir));
@@ -2384,7 +2752,7 @@ var init_index = __esm({
2384
2752
  });
2385
2753
  if (publicDir && existsSync9(publicDir)) {
2386
2754
  app.get("*", (req, res) => {
2387
- res.sendFile(join8(publicDir, "index.html"));
2755
+ res.sendFile(join9(publicDir, "index.html"));
2388
2756
  });
2389
2757
  }
2390
2758
  app.use(errorHandler);
@@ -2401,12 +2769,12 @@ init_skillService();
2401
2769
  init_workspaceService();
2402
2770
  import { program } from "commander";
2403
2771
  import open from "open";
2404
- import { existsSync as existsSync10, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2405
- import { join as join9 } from "path";
2772
+ import { existsSync as existsSync10, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2773
+ import { join as join10 } from "path";
2406
2774
  import readline from "readline";
2407
2775
  var { skillverseHome: SKILLVERSE_HOME } = config;
2408
- var AUTH_FILE = join9(SKILLVERSE_HOME, "auth.json");
2409
- var CONFIG_FILE2 = join9(SKILLVERSE_HOME, "config.json");
2776
+ var AUTH_FILE = join10(SKILLVERSE_HOME, "auth.json");
2777
+ var CONFIG_FILE2 = join10(SKILLVERSE_HOME, "config.json");
2410
2778
  program.hook("preAction", async (thisCommand, actionCommand) => {
2411
2779
  if (["list", "start", "install", "add", "remove", "import", "search"].includes(actionCommand.name())) {
2412
2780
  await ensureDatabaseInitialized();
@@ -2414,14 +2782,14 @@ program.hook("preAction", async (thisCommand, actionCommand) => {
2414
2782
  });
2415
2783
  function getRegistryUrl() {
2416
2784
  if (existsSync10(CONFIG_FILE2)) {
2417
- const config3 = JSON.parse(readFileSync3(CONFIG_FILE2, "utf-8"));
2785
+ const config3 = JSON.parse(readFileSync4(CONFIG_FILE2, "utf-8"));
2418
2786
  return config3.registryUrl || "http://localhost:4000";
2419
2787
  }
2420
2788
  return process.env.SKILLVERSE_REGISTRY || "http://localhost:4000";
2421
2789
  }
2422
2790
  function getAuthToken() {
2423
2791
  if (existsSync10(AUTH_FILE)) {
2424
- const auth = JSON.parse(readFileSync3(AUTH_FILE, "utf-8"));
2792
+ const auth = JSON.parse(readFileSync4(AUTH_FILE, "utf-8"));
2425
2793
  return auth.token || null;
2426
2794
  }
2427
2795
  return null;
@@ -2533,7 +2901,7 @@ program.command("publish [path]").description("Publish a skill to the Registry")
2533
2901
  const form = new FormData();
2534
2902
  form.append("name", skillName);
2535
2903
  if (options.description) form.append("description", options.description);
2536
- form.append("bundle", readFileSync3(bundlePath), {
2904
+ form.append("bundle", readFileSync4(bundlePath), {
2537
2905
  filename: `${skillName}.tar.gz`,
2538
2906
  contentType: "application/gzip"
2539
2907
  });
@@ -2592,7 +2960,7 @@ program.command("search <query>").description("Search for skills in the Registry
2592
2960
  }
2593
2961
  });
2594
2962
  program.command("config").description("Configure SkillVerse settings").option("-r, --registry <url>", "Set default registry URL").action((options) => {
2595
- const config3 = existsSync10(CONFIG_FILE2) ? JSON.parse(readFileSync3(CONFIG_FILE2, "utf-8")) : {};
2963
+ const config3 = existsSync10(CONFIG_FILE2) ? JSON.parse(readFileSync4(CONFIG_FILE2, "utf-8")) : {};
2596
2964
  if (options.registry) {
2597
2965
  config3.registryUrl = options.registry;
2598
2966
  writeFileSync2(CONFIG_FILE2, JSON.stringify(config3, null, 2));
@@ -2645,7 +3013,7 @@ program.command("install [gitUrl]").description("Install a skill from Git URL").
2645
3013
  }
2646
3014
  });
2647
3015
  program.command("add").description("Add a local skill").requiredOption("-p, --path <path>", "Path to skill directory").option("-a, --agent <agent>", "Link to agent workspace").action(async (options) => {
2648
- const sourcePath = options.path.startsWith("/") ? options.path : join9(process.cwd(), options.path);
3016
+ const sourcePath = options.path.startsWith("/") ? options.path : join10(process.cwd(), options.path);
2649
3017
  if (!existsSync10(sourcePath)) {
2650
3018
  console.error(`\u274C Source path not found: ${sourcePath}`);
2651
3019
  process.exit(1);