skillverse 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -11,12 +11,26 @@ var __export = (target, all) => {
11
11
 
12
12
  // src/config.ts
13
13
  import { join, dirname } from "path";
14
- import { existsSync, mkdirSync } from "fs";
14
+ import { existsSync, mkdirSync, readFileSync } from "fs";
15
15
  import { fileURLToPath } from "url";
16
16
  import dotenv from "dotenv";
17
17
  function setupEnvironment() {
18
18
  const home = process.env.HOME || process.env.USERPROFILE || "";
19
- const skillverseHome = process.env.SKILLVERSE_HOME || join(home, ".skillverse");
19
+ const defaultHome = join(home, ".skillverse");
20
+ const bootstrapConfigPath = join(defaultHome, "config.json");
21
+ let skillverseHome = process.env.SKILLVERSE_HOME || defaultHome;
22
+ if (existsSync(bootstrapConfigPath)) {
23
+ try {
24
+ const config3 = JSON.parse(readFileSync(bootstrapConfigPath, "utf-8"));
25
+ if (config3.skillverseHome) {
26
+ skillverseHome = config3.skillverseHome;
27
+ process.env.SKILLVERSE_HOME = skillverseHome;
28
+ console.log(`Common config found. Using custom home: ${skillverseHome}`);
29
+ }
30
+ } catch (e) {
31
+ console.warn("Failed to read bootstrap config:", e);
32
+ }
33
+ }
20
34
  const dbUrl = process.env.DATABASE_URL;
21
35
  if (!existsSync(skillverseHome)) {
22
36
  try {
@@ -25,10 +39,14 @@ function setupEnvironment() {
25
39
  console.error("Failed to create .skillverse directory:", error);
26
40
  }
27
41
  }
28
- if (!dbUrl) {
42
+ if (!dbUrl || skillverseHome !== defaultHome) {
29
43
  const dbPath = join(skillverseHome, "skillverse.db");
30
44
  process.env.DATABASE_URL = `file:${dbPath}`;
45
+ console.log(`Using database at: ${dbPath}`);
31
46
  }
47
+ process.env.SKILLS_DIR = join(skillverseHome, "skills");
48
+ process.env.MARKETPLACE_DIR = join(skillverseHome, "marketplace");
49
+ process.env.TEMP_DIR = join(skillverseHome, "temp");
32
50
  return {
33
51
  skillverseHome,
34
52
  databaseUrl: process.env.DATABASE_URL
@@ -47,16 +65,25 @@ var init_config = __esm({
47
65
 
48
66
  // src/lib/db.ts
49
67
  import { PrismaClient } from "@prisma/client";
50
- var globalForPrisma, prisma;
68
+ var config2, globalForPrisma, currentDbUrl, prisma;
51
69
  var init_db = __esm({
52
70
  "src/lib/db.ts"() {
53
71
  "use strict";
54
72
  init_config();
73
+ config2 = setupEnvironment();
55
74
  globalForPrisma = globalThis;
75
+ currentDbUrl = process.env.DATABASE_URL;
76
+ console.log(`\u{1F4CA} DB connection target: ${currentDbUrl}`);
77
+ if (globalForPrisma.prisma && globalForPrisma.lastDbUrl !== currentDbUrl) {
78
+ console.log(`\u{1F504} DATABASE_URL changed from ${globalForPrisma.lastDbUrl} to ${currentDbUrl}, reconnecting...`);
79
+ globalForPrisma.prisma.$disconnect().catch(console.error);
80
+ globalForPrisma.prisma = void 0;
81
+ }
56
82
  prisma = globalForPrisma.prisma ?? new PrismaClient({
57
83
  log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"]
58
84
  });
59
- if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
85
+ globalForPrisma.prisma = prisma;
86
+ globalForPrisma.lastDbUrl = currentDbUrl;
60
87
  }
61
88
  });
62
89
 
@@ -211,6 +238,11 @@ var init_errorHandler = __esm({
211
238
  });
212
239
 
213
240
  // src/services/skillService.ts
241
+ var skillService_exports = {};
242
+ __export(skillService_exports, {
243
+ SkillService: () => SkillService,
244
+ skillService: () => skillService
245
+ });
214
246
  import { join as join3, basename } from "path";
215
247
  import { existsSync as existsSync3 } from "fs";
216
248
  import { mkdir, rm, cp, readFile, unlink } from "fs/promises";
@@ -231,16 +263,21 @@ function parseGitUrl(url) {
231
263
  const skillName = pathParts[pathParts.length - 1];
232
264
  return { repoUrl, subdir, skillName };
233
265
  }
234
- var SKILLVERSE_HOME, SKILLS_DIR, TEMP_DIR, SkillService, skillService;
266
+ var getPaths, SkillService, skillService;
235
267
  var init_skillService = __esm({
236
268
  "src/services/skillService.ts"() {
237
269
  "use strict";
238
270
  init_db();
239
271
  init_errorHandler();
240
272
  init_dist();
241
- SKILLVERSE_HOME = process.env.SKILLVERSE_HOME || join3(process.env.HOME || "", ".skillverse");
242
- SKILLS_DIR = process.env.SKILLS_DIR || join3(SKILLVERSE_HOME, "skills");
243
- TEMP_DIR = process.env.TEMP_DIR || join3(SKILLVERSE_HOME, "temp");
273
+ getPaths = () => {
274
+ const home = process.env.SKILLVERSE_HOME || join3(process.env.HOME || "", ".skillverse");
275
+ return {
276
+ home,
277
+ skills: process.env.SKILLS_DIR || join3(home, "skills"),
278
+ temp: process.env.TEMP_DIR || join3(home, "temp")
279
+ };
280
+ };
244
281
  SkillService = class {
245
282
  async getAllSkills() {
246
283
  const skills = await prisma.skill.findMany({
@@ -334,13 +371,14 @@ var init_skillService = __esm({
334
371
  if (existingSkill) {
335
372
  throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${skillName}" already exists`, 409);
336
373
  }
337
- const skillPath = join3(SKILLS_DIR, skillName);
374
+ const { skills: skillsDir, temp: tempDir } = getPaths();
375
+ const skillPath = join3(skillsDir, skillName);
338
376
  if (existsSync3(skillPath)) {
339
377
  throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${skillName}" already exists`, 409);
340
378
  }
341
379
  let commitHash = "";
342
380
  if (subdir) {
343
- tempPath = join3(TEMP_DIR, `git-clone-${Date.now()}`);
381
+ tempPath = join3(tempDir, `git-clone-${Date.now()}`);
344
382
  await mkdir(tempPath, { recursive: true });
345
383
  console.log(`Cloning ${repoUrl} to temp path for extraction...`);
346
384
  const git = simpleGit();
@@ -411,8 +449,9 @@ var init_skillService = __esm({
411
449
  if (!existsSync3(path)) {
412
450
  throw new AppError(ErrorCode.FILE_SYSTEM_ERROR, `Source path not found: ${path}`, 400);
413
451
  }
452
+ const { skills: skillsDir } = getPaths();
414
453
  const skillName = name || basename(path);
415
- const skillPath = join3(SKILLS_DIR, skillName);
454
+ const skillPath = join3(skillsDir, skillName);
416
455
  const existingSkill = await prisma.skill.findUnique({
417
456
  where: { name: skillName }
418
457
  });
@@ -467,7 +506,8 @@ var init_skillService = __esm({
467
506
  }
468
507
  }
469
508
  async createSkillFromLocal(name, zipPath, description) {
470
- const skillPath = join3(SKILLS_DIR, name);
509
+ const { skills: skillsDir, temp: tempDir } = getPaths();
510
+ const skillPath = join3(skillsDir, name);
471
511
  const existingSkill = await prisma.skill.findUnique({
472
512
  where: { name }
473
513
  });
@@ -477,7 +517,7 @@ var init_skillService = __esm({
477
517
  if (existsSync3(skillPath)) {
478
518
  throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
479
519
  }
480
- const extractPath = join3(TEMP_DIR, `extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
520
+ const extractPath = join3(tempDir, `extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
481
521
  await mkdir(extractPath, { recursive: true });
482
522
  try {
483
523
  const zip = new AdmZip(zipPath);
@@ -523,8 +563,9 @@ var init_skillService = __esm({
523
563
  name = `${originalName}-${counter}`;
524
564
  counter++;
525
565
  }
526
- const skillPath = join3(SKILLS_DIR, name);
527
- const extractPath = join3(TEMP_DIR, `extract-bundle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
566
+ const { skills: skillsDir, temp: tempDir } = getPaths();
567
+ const skillPath = join3(skillsDir, name);
568
+ const extractPath = join3(tempDir, `extract-bundle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
528
569
  await mkdir(extractPath, { recursive: true });
529
570
  try {
530
571
  const { createReadStream: createReadStream2 } = await import("fs");
@@ -617,7 +658,8 @@ var init_skillService = __esm({
617
658
  message: "Not a git skill or missing repo URL"
618
659
  };
619
660
  }
620
- const tempPath = join3(TEMP_DIR, `check-update-${Date.now()}`);
661
+ const { temp: tempDir } = getPaths();
662
+ const tempPath = join3(tempDir, `check-update-${Date.now()}`);
621
663
  await mkdir(tempPath, { recursive: true });
622
664
  try {
623
665
  const git = simpleGit();
@@ -690,7 +732,8 @@ var init_skillService = __esm({
690
732
  let tempSkillPath = null;
691
733
  try {
692
734
  const { repoUrl, subdir } = parseGitUrl(skill.sourceUrl);
693
- tempPath = join3(TEMP_DIR, `upgrade-${Date.now()}`);
735
+ const { temp: tempDir } = getPaths();
736
+ tempPath = join3(tempDir, `upgrade-${Date.now()}`);
694
737
  await mkdir(tempPath, { recursive: true });
695
738
  const git = simpleGit();
696
739
  console.log(`Cloning ${repoUrl} to temp for upgrade...`);
@@ -742,6 +785,99 @@ var init_skillService = __esm({
742
785
  });
743
786
  }
744
787
  }
788
+ /**
789
+ * Sync skills between filesystem and database:
790
+ * 1. Remove orphaned database records (skills in DB but not on disk)
791
+ * 2. Import new skills from disk (skills on disk but not in DB)
792
+ */
793
+ async syncSkillsWithFileSystem() {
794
+ const { skills: skillsDir } = getPaths();
795
+ const dbSkills = await prisma.skill.findMany();
796
+ let removedCount = 0;
797
+ for (const skill of dbSkills) {
798
+ if (!existsSync3(skill.storagePath)) {
799
+ console.log(`\u{1F5D1}\uFE0F Removing orphaned skill from database: ${skill.name} (path: ${skill.storagePath})`);
800
+ try {
801
+ await prisma.skill.delete({ where: { id: skill.id } });
802
+ removedCount++;
803
+ } catch (error) {
804
+ console.error(`Failed to remove orphaned skill ${skill.name}:`, error);
805
+ }
806
+ }
807
+ }
808
+ if (removedCount > 0) {
809
+ console.log(`\u2705 Removed ${removedCount} orphaned skill(s) from database.`);
810
+ }
811
+ const importedCount = await this.scanForSkills();
812
+ return { removedCount, importedCount: importedCount || 0 };
813
+ }
814
+ async scanForSkills() {
815
+ const { skills: skillsDir } = getPaths();
816
+ console.log(`Scanning for skills in ${skillsDir}...`);
817
+ if (!existsSync3(skillsDir)) {
818
+ console.log("Skills directory does not exist, skipping scan.");
819
+ return;
820
+ }
821
+ const entries = await import("fs/promises").then((fs) => fs.readdir(skillsDir, { withFileTypes: true }));
822
+ const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
823
+ console.log(`Found ${directories.length} directories in skills folder.`);
824
+ let importedCount = 0;
825
+ for (const dirName of directories) {
826
+ const skillPath = join3(skillsDir, dirName);
827
+ const existingSkill = await prisma.skill.findUnique({
828
+ where: { name: dirName }
829
+ });
830
+ if (existingSkill) {
831
+ continue;
832
+ }
833
+ const hasSkillMd = existsSync3(join3(skillPath, "SKILL.md"));
834
+ const hasPackageJson = existsSync3(join3(skillPath, "package.json"));
835
+ const hasSkillJson = existsSync3(join3(skillPath, "skill.json"));
836
+ if (!hasSkillMd && !hasPackageJson && !hasSkillJson) {
837
+ console.log(`Skipping ${dirName}: No skill metadata found.`);
838
+ continue;
839
+ }
840
+ try {
841
+ console.log(`Importing skill: ${dirName}`);
842
+ const parsed = await this.parseSkillMetadata(skillPath);
843
+ let source = "local";
844
+ let sourceUrl = void 0;
845
+ let repoUrl = void 0;
846
+ let commitHash = void 0;
847
+ if (existsSync3(join3(skillPath, ".git"))) {
848
+ source = "git";
849
+ try {
850
+ const git = simpleGit(skillPath);
851
+ const remotes = await git.getRemotes(true);
852
+ if (remotes.length > 0) {
853
+ sourceUrl = remotes[0].refs.fetch;
854
+ repoUrl = sourceUrl;
855
+ }
856
+ commitHash = await git.revparse(["HEAD"]);
857
+ } catch (e) {
858
+ console.warn(`Failed to read git info for ${dirName}:`, e);
859
+ }
860
+ }
861
+ await prisma.skill.create({
862
+ data: {
863
+ name: dirName,
864
+ source,
865
+ sourceUrl,
866
+ repoUrl,
867
+ commitHash,
868
+ description: parsed.description,
869
+ storagePath: skillPath,
870
+ metadata: JSON.stringify(parsed.metadata)
871
+ }
872
+ });
873
+ importedCount++;
874
+ } catch (error) {
875
+ console.error(`Failed to import skill ${dirName}:`, error);
876
+ }
877
+ }
878
+ console.log(`Scan complete. Imported ${importedCount} new skills.`);
879
+ return importedCount;
880
+ }
745
881
  };
746
882
  skillService = new SkillService();
747
883
  }
@@ -1077,17 +1213,17 @@ ${relativeSkillsPath}/
1077
1213
  */
1078
1214
  async migrateExistingSkills(workspaceId, skillNames) {
1079
1215
  const workspace = await this.getWorkspaceById(workspaceId);
1080
- const SKILLVERSE_HOME3 = process.env.SKILLVERSE_HOME || join4(homedir(), ".skillverse");
1081
- const SKILLS_DIR2 = process.env.SKILLS_DIR || join4(SKILLVERSE_HOME3, "skills");
1082
- if (!existsSync4(SKILLS_DIR2)) {
1083
- await mkdir2(SKILLS_DIR2, { recursive: true });
1216
+ const SKILLVERSE_HOME2 = process.env.SKILLVERSE_HOME || join4(homedir(), ".skillverse");
1217
+ const SKILLS_DIR = process.env.SKILLS_DIR || join4(SKILLVERSE_HOME2, "skills");
1218
+ if (!existsSync4(SKILLS_DIR)) {
1219
+ await mkdir2(SKILLS_DIR, { recursive: true });
1084
1220
  }
1085
1221
  const { rename, readFile: readFile3, cp: cp2 } = await import("fs/promises");
1086
1222
  const migrated = [];
1087
1223
  const errors = [];
1088
1224
  for (const skillName of skillNames) {
1089
1225
  const sourcePath = join4(workspace.path, skillName);
1090
- const targetPath = join4(SKILLS_DIR2, skillName);
1226
+ const targetPath = join4(SKILLS_DIR, skillName);
1091
1227
  try {
1092
1228
  if (!existsSync4(sourcePath)) {
1093
1229
  errors.push(`${skillName}: Source path not found`);
@@ -1165,20 +1301,20 @@ import multer from "multer";
1165
1301
  import { join as join5 } from "path";
1166
1302
  import { existsSync as existsSync5 } from "fs";
1167
1303
  import { mkdir as mkdir3, rm as rm3 } from "fs/promises";
1168
- var router, TEMP_DIR2, storage, upload, skills_default;
1304
+ var router, TEMP_DIR, storage, upload, skills_default;
1169
1305
  var init_skills = __esm({
1170
1306
  "src/routes/skills.ts"() {
1171
1307
  "use strict";
1172
1308
  init_skillService();
1173
1309
  init_workspaceService();
1174
1310
  router = Router();
1175
- TEMP_DIR2 = process.env.TEMP_DIR || join5(process.env.HOME || "", ".skillverse", "temp");
1176
- if (!existsSync5(TEMP_DIR2)) {
1177
- mkdir3(TEMP_DIR2, { recursive: true });
1311
+ TEMP_DIR = process.env.TEMP_DIR || join5(process.env.HOME || "", ".skillverse", "temp");
1312
+ if (!existsSync5(TEMP_DIR)) {
1313
+ mkdir3(TEMP_DIR, { recursive: true });
1178
1314
  }
1179
1315
  storage = multer.diskStorage({
1180
1316
  destination: (req, file, cb) => {
1181
- cb(null, TEMP_DIR2);
1317
+ cb(null, TEMP_DIR);
1182
1318
  },
1183
1319
  filename: (req, file, cb) => {
1184
1320
  const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
@@ -1210,6 +1346,18 @@ var init_skills = __esm({
1210
1346
  next(error);
1211
1347
  }
1212
1348
  });
1349
+ router.post("/sync", async (req, res, next) => {
1350
+ try {
1351
+ const result = await skillService.syncSkillsWithFileSystem();
1352
+ res.json({
1353
+ success: true,
1354
+ data: result,
1355
+ message: `Synced: removed ${result.removedCount} orphan(s), imported ${result.importedCount} new skill(s)`
1356
+ });
1357
+ } catch (error) {
1358
+ next(error);
1359
+ }
1360
+ });
1213
1361
  router.get("/:id", async (req, res, next) => {
1214
1362
  try {
1215
1363
  const skill = await skillService.getSkillById(req.params.id);
@@ -1980,6 +2128,156 @@ var init_dashboard = __esm({
1980
2128
  }
1981
2129
  });
1982
2130
 
2131
+ // src/routes/config.ts
2132
+ import { Router as Router5 } from "express";
2133
+ import { join as join7 } from "path";
2134
+ import { existsSync as existsSync8, readFileSync as readFileSync2, writeFileSync } from "fs";
2135
+ function readConfig() {
2136
+ if (existsSync8(CONFIG_FILE)) {
2137
+ try {
2138
+ return JSON.parse(readFileSync2(CONFIG_FILE, "utf-8"));
2139
+ } catch (e) {
2140
+ console.error("Failed to read config.json:", e);
2141
+ return {};
2142
+ }
2143
+ }
2144
+ return {};
2145
+ }
2146
+ var router5, HOME, BOOTSTRAP_HOME, CONFIG_FILE, config_default;
2147
+ var init_config2 = __esm({
2148
+ "src/routes/config.ts"() {
2149
+ "use strict";
2150
+ router5 = Router5();
2151
+ HOME = process.env.HOME || process.env.USERPROFILE || "";
2152
+ BOOTSTRAP_HOME = join7(HOME, ".skillverse");
2153
+ CONFIG_FILE = join7(BOOTSTRAP_HOME, "config.json");
2154
+ router5.get("/", (req, res, next) => {
2155
+ try {
2156
+ const fileConfig = readConfig();
2157
+ const config3 = {
2158
+ // Show saved preference if available, otherwise current env/default
2159
+ skillverseHome: fileConfig.skillverseHome || process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME,
2160
+ // registryUrl is potentially in config.json already
2161
+ registryUrl: fileConfig.registryUrl || process.env.SKILLVERSE_REGISTRY || "http://localhost:4000"
2162
+ // ... add other configurable items
2163
+ };
2164
+ res.json({
2165
+ success: true,
2166
+ data: config3
2167
+ });
2168
+ } catch (error) {
2169
+ next(error);
2170
+ }
2171
+ });
2172
+ router5.post("/", async (req, res, next) => {
2173
+ try {
2174
+ const { skillverseHome, registryUrl, migrate } = req.body;
2175
+ const currentConfig = readConfig();
2176
+ const oldHome = process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME;
2177
+ if (migrate && skillverseHome && skillverseHome !== oldHome) {
2178
+ console.log(`Migrating data from ${oldHome} to ${skillverseHome}...`);
2179
+ const { cp: cp2 } = await import("fs/promises");
2180
+ if (!existsSync8(skillverseHome)) {
2181
+ const { mkdir: mkdir6 } = await import("fs/promises");
2182
+ await mkdir6(skillverseHome, { recursive: true });
2183
+ }
2184
+ const copyDir = async (srcName) => {
2185
+ const src = join7(oldHome, srcName);
2186
+ const dest = join7(skillverseHome, srcName);
2187
+ if (existsSync8(src)) {
2188
+ console.log(`Copying ${srcName}...`);
2189
+ await cp2(src, dest, { recursive: true, force: true });
2190
+ }
2191
+ };
2192
+ await copyDir("skills");
2193
+ await copyDir("marketplace");
2194
+ const dbSrc = join7(oldHome, "skillverse.db");
2195
+ const dbDest = join7(skillverseHome, "skillverse.db");
2196
+ if (existsSync8(dbSrc)) {
2197
+ console.log("Copying database...");
2198
+ await cp2(dbSrc, dbDest, { force: true });
2199
+ const walSrc = dbSrc + "-wal";
2200
+ if (existsSync8(walSrc)) await cp2(walSrc, dbDest + "-wal", { force: true });
2201
+ const shmSrc = dbSrc + "-shm";
2202
+ if (existsSync8(shmSrc)) await cp2(shmSrc, dbDest + "-shm", { force: true });
2203
+ console.log("Updating skill paths in new database...");
2204
+ const { execSync } = await import("child_process");
2205
+ const oldSkillsPath = join7(oldHome, "skills");
2206
+ const newSkillsPath = join7(skillverseHome, "skills");
2207
+ const updateQuery = `UPDATE Skill SET storagePath = replace(storagePath, '${oldSkillsPath}', '${newSkillsPath}') WHERE storagePath LIKE '${oldSkillsPath}%';`;
2208
+ try {
2209
+ execSync(`sqlite3 "${dbDest}" "${updateQuery}"`, { encoding: "utf-8" });
2210
+ console.log("\u2705 Updated skill paths in new database");
2211
+ } catch (sqlErr) {
2212
+ console.error("Failed to update skill paths:", sqlErr);
2213
+ }
2214
+ }
2215
+ }
2216
+ const newConfig = {
2217
+ ...currentConfig,
2218
+ ...skillverseHome && { skillverseHome },
2219
+ ...registryUrl && { registryUrl }
2220
+ };
2221
+ writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
2222
+ res.json({
2223
+ success: true,
2224
+ data: newConfig,
2225
+ message: "Configuration saved. Restart server for changes to take effect."
2226
+ });
2227
+ } catch (error) {
2228
+ next(error);
2229
+ }
2230
+ });
2231
+ router5.get("/version", async (req, res, next) => {
2232
+ try {
2233
+ const { dirname: dirname4, join: pathJoin } = await import("path");
2234
+ const { fileURLToPath: fileURLToPath4 } = await import("url");
2235
+ const __filename4 = fileURLToPath4(import.meta.url);
2236
+ const __dirname4 = dirname4(__filename4);
2237
+ const packageJsonPath = pathJoin(__dirname4, "..", "..", "package.json");
2238
+ let currentVersion = "0.0.0";
2239
+ if (existsSync8(packageJsonPath)) {
2240
+ const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
2241
+ currentVersion = pkg.version || "0.0.0";
2242
+ }
2243
+ let latestVersion = currentVersion;
2244
+ let hasUpdate = false;
2245
+ try {
2246
+ const response = await fetch("https://registry.npmjs.org/skillverse/latest");
2247
+ if (response.ok) {
2248
+ const data = await response.json();
2249
+ latestVersion = data.version || currentVersion;
2250
+ const parseVersion = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
2251
+ const current = parseVersion(currentVersion);
2252
+ const latest = parseVersion(latestVersion);
2253
+ for (let i = 0; i < 3; i++) {
2254
+ if (latest[i] > current[i]) {
2255
+ hasUpdate = true;
2256
+ break;
2257
+ } else if (latest[i] < current[i]) {
2258
+ break;
2259
+ }
2260
+ }
2261
+ }
2262
+ } catch (fetchError) {
2263
+ console.warn("Failed to check npm registry for updates:", fetchError);
2264
+ }
2265
+ res.json({
2266
+ success: true,
2267
+ data: {
2268
+ currentVersion,
2269
+ latestVersion,
2270
+ hasUpdate
2271
+ }
2272
+ });
2273
+ } catch (error) {
2274
+ next(error);
2275
+ }
2276
+ });
2277
+ config_default = router5;
2278
+ }
2279
+ });
2280
+
1983
2281
  // src/middleware/logger.ts
1984
2282
  function requestLogger(req, res, next) {
1985
2283
  const start = Date.now();
@@ -2004,16 +2302,16 @@ import express from "express";
2004
2302
  import cors from "cors";
2005
2303
  import dotenv2 from "dotenv";
2006
2304
  import { fileURLToPath as fileURLToPath3 } from "url";
2007
- import { dirname as dirname3, join as join7 } from "path";
2305
+ import { dirname as dirname3, join as join8 } from "path";
2008
2306
  import { mkdir as mkdir5 } from "fs/promises";
2009
- import { existsSync as existsSync8 } from "fs";
2307
+ import { existsSync as existsSync9 } from "fs";
2010
2308
  async function initializeStorage() {
2011
- const skillverseHome = process.env.SKILLVERSE_HOME || join7(process.env.HOME || "", ".skillverse");
2012
- const skillsDir = process.env.SKILLS_DIR || join7(skillverseHome, "skills");
2013
- const marketplaceDir = process.env.MARKETPLACE_DIR || join7(skillverseHome, "marketplace");
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");
2014
2312
  const dirs = [skillverseHome, skillsDir, marketplaceDir];
2015
2313
  for (const dir of dirs) {
2016
- if (!existsSync8(dir)) {
2314
+ if (!existsSync9(dir)) {
2017
2315
  await mkdir5(dir, { recursive: true });
2018
2316
  console.log(`Created directory: ${dir}`);
2019
2317
  }
@@ -2023,10 +2321,13 @@ async function startServer(port = 3001) {
2023
2321
  try {
2024
2322
  await initializeStorage();
2025
2323
  await ensureDatabaseInitialized();
2324
+ console.log("\u{1F50D} Syncing skills with filesystem...");
2325
+ const { skillService: skillService2 } = await Promise.resolve().then(() => (init_skillService(), skillService_exports));
2326
+ await skillService2.syncSkillsWithFileSystem();
2026
2327
  return new Promise((resolve) => {
2027
2328
  app.listen(port, () => {
2028
2329
  console.log(`\u{1F680} SkillVerse server running on http://localhost:${port}`);
2029
- console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join7(process.env.HOME || "", ".skillverse")}`);
2330
+ console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join8(process.env.HOME || "", ".skillverse")}`);
2030
2331
  resolve();
2031
2332
  });
2032
2333
  });
@@ -2043,6 +2344,7 @@ var init_index = __esm({
2043
2344
  init_workspaces();
2044
2345
  init_marketplace();
2045
2346
  init_dashboard();
2347
+ init_config2();
2046
2348
  init_errorHandler();
2047
2349
  init_logger();
2048
2350
  init_initDb();
@@ -2056,16 +2358,16 @@ var init_index = __esm({
2056
2358
  app.use(express.urlencoded({ extended: true }));
2057
2359
  app.use(requestLogger);
2058
2360
  possiblePublicDirs = [
2059
- join7(__dirname3, "../public"),
2361
+ join8(__dirname3, "../public"),
2060
2362
  // Production: dist/index.js -> public
2061
- join7(__dirname3, "../../public"),
2363
+ join8(__dirname3, "../../public"),
2062
2364
  // Dev tsx might resolve here
2063
- join7(process.cwd(), "public"),
2365
+ join8(process.cwd(), "public"),
2064
2366
  // Fallback: relative to cwd
2065
- join7(process.cwd(), "server/public")
2367
+ join8(process.cwd(), "server/public")
2066
2368
  // Fallback: from root
2067
2369
  ];
2068
- publicDir = possiblePublicDirs.find((dir) => existsSync8(dir));
2370
+ publicDir = possiblePublicDirs.find((dir) => existsSync9(dir));
2069
2371
  if (publicDir) {
2070
2372
  app.use(express.static(publicDir));
2071
2373
  console.log(`\u{1F4C2} Serving static files from: ${publicDir}`);
@@ -2076,12 +2378,13 @@ var init_index = __esm({
2076
2378
  app.use("/api/workspaces", workspaces_default);
2077
2379
  app.use("/api/marketplace", marketplace_default);
2078
2380
  app.use("/api/dashboard", dashboard_default);
2381
+ app.use("/api/config", config_default);
2079
2382
  app.get("/health", (req, res) => {
2080
2383
  res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
2081
2384
  });
2082
- if (publicDir && existsSync8(publicDir)) {
2385
+ if (publicDir && existsSync9(publicDir)) {
2083
2386
  app.get("*", (req, res) => {
2084
- res.sendFile(join7(publicDir, "index.html"));
2387
+ res.sendFile(join8(publicDir, "index.html"));
2085
2388
  });
2086
2389
  }
2087
2390
  app.use(errorHandler);
@@ -2098,33 +2401,33 @@ init_skillService();
2098
2401
  init_workspaceService();
2099
2402
  import { program } from "commander";
2100
2403
  import open from "open";
2101
- import { existsSync as existsSync9, readFileSync, writeFileSync } from "fs";
2102
- import { join as join8 } from "path";
2404
+ import { existsSync as existsSync10, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2405
+ import { join as join9 } from "path";
2103
2406
  import readline from "readline";
2104
- var { skillverseHome: SKILLVERSE_HOME2 } = config;
2105
- var AUTH_FILE = join8(SKILLVERSE_HOME2, "auth.json");
2106
- var CONFIG_FILE = join8(SKILLVERSE_HOME2, "config.json");
2407
+ var { skillverseHome: SKILLVERSE_HOME } = config;
2408
+ var AUTH_FILE = join9(SKILLVERSE_HOME, "auth.json");
2409
+ var CONFIG_FILE2 = join9(SKILLVERSE_HOME, "config.json");
2107
2410
  program.hook("preAction", async (thisCommand, actionCommand) => {
2108
2411
  if (["list", "start", "install", "add", "remove", "import", "search"].includes(actionCommand.name())) {
2109
2412
  await ensureDatabaseInitialized();
2110
2413
  }
2111
2414
  });
2112
2415
  function getRegistryUrl() {
2113
- if (existsSync9(CONFIG_FILE)) {
2114
- const config2 = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
2115
- return config2.registryUrl || "http://localhost:4000";
2416
+ if (existsSync10(CONFIG_FILE2)) {
2417
+ const config3 = JSON.parse(readFileSync3(CONFIG_FILE2, "utf-8"));
2418
+ return config3.registryUrl || "http://localhost:4000";
2116
2419
  }
2117
2420
  return process.env.SKILLVERSE_REGISTRY || "http://localhost:4000";
2118
2421
  }
2119
2422
  function getAuthToken() {
2120
- if (existsSync9(AUTH_FILE)) {
2121
- const auth = JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
2423
+ if (existsSync10(AUTH_FILE)) {
2424
+ const auth = JSON.parse(readFileSync3(AUTH_FILE, "utf-8"));
2122
2425
  return auth.token || null;
2123
2426
  }
2124
2427
  return null;
2125
2428
  }
2126
2429
  function saveAuth(token, user) {
2127
- writeFileSync(AUTH_FILE, JSON.stringify({ token, user }, null, 2));
2430
+ writeFileSync2(AUTH_FILE, JSON.stringify({ token, user }, null, 2));
2128
2431
  }
2129
2432
  async function prompt(question, hidden = false) {
2130
2433
  const rl = readline.createInterface({
@@ -2215,7 +2518,7 @@ program.command("publish [path]").description("Publish a skill to the Registry")
2215
2518
  }
2216
2519
  const targetPath = skillPath || process.cwd();
2217
2520
  const registryUrl = options.registry || getRegistryUrl();
2218
- if (!existsSync9(targetPath)) {
2521
+ if (!existsSync10(targetPath)) {
2219
2522
  console.error(`
2220
2523
  \u274C Path not found: ${targetPath}`);
2221
2524
  process.exit(1);
@@ -2230,7 +2533,7 @@ program.command("publish [path]").description("Publish a skill to the Registry")
2230
2533
  const form = new FormData();
2231
2534
  form.append("name", skillName);
2232
2535
  if (options.description) form.append("description", options.description);
2233
- form.append("bundle", readFileSync(bundlePath), {
2536
+ form.append("bundle", readFileSync3(bundlePath), {
2234
2537
  filename: `${skillName}.tar.gz`,
2235
2538
  contentType: "application/gzip"
2236
2539
  });
@@ -2289,14 +2592,14 @@ program.command("search <query>").description("Search for skills in the Registry
2289
2592
  }
2290
2593
  });
2291
2594
  program.command("config").description("Configure SkillVerse settings").option("-r, --registry <url>", "Set default registry URL").action((options) => {
2292
- const config2 = existsSync9(CONFIG_FILE) ? JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) : {};
2595
+ const config3 = existsSync10(CONFIG_FILE2) ? JSON.parse(readFileSync3(CONFIG_FILE2, "utf-8")) : {};
2293
2596
  if (options.registry) {
2294
- config2.registryUrl = options.registry;
2295
- writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2));
2597
+ config3.registryUrl = options.registry;
2598
+ writeFileSync2(CONFIG_FILE2, JSON.stringify(config3, null, 2));
2296
2599
  console.log(`\u2705 Registry URL set to: ${options.registry}`);
2297
2600
  } else {
2298
2601
  console.log("\nCurrent Configuration:");
2299
- console.log(` Registry: ${config2.registryUrl || getRegistryUrl()}`);
2602
+ console.log(` Registry: ${config3.registryUrl || getRegistryUrl()}`);
2300
2603
  }
2301
2604
  });
2302
2605
  program.command("install [gitUrl]").description("Install a skill from Git URL").option("-a, --agent <agent>", "Link to agent workspace (vscode, cursor, etc.)").action(async (gitUrl, options) => {
@@ -2342,8 +2645,8 @@ program.command("install [gitUrl]").description("Install a skill from Git URL").
2342
2645
  }
2343
2646
  });
2344
2647
  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) => {
2345
- const sourcePath = options.path.startsWith("/") ? options.path : join8(process.cwd(), options.path);
2346
- if (!existsSync9(sourcePath)) {
2648
+ const sourcePath = options.path.startsWith("/") ? options.path : join9(process.cwd(), options.path);
2649
+ if (!existsSync10(sourcePath)) {
2347
2650
  console.error(`\u274C Source path not found: ${sourcePath}`);
2348
2651
  process.exit(1);
2349
2652
  }