skillverse 0.1.1 → 0.1.2

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
@@ -9,133 +9,11 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/services/bundleService.ts
13
- var bundleService_exports = {};
14
- __export(bundleService_exports, {
15
- bundleService: () => bundleService,
16
- createBundle: () => createBundle,
17
- deleteBundle: () => deleteBundle,
18
- extractBundle: () => extractBundle,
19
- getBundleSize: () => getBundleSize,
20
- listBundles: () => listBundles
21
- });
22
- import { createWriteStream, existsSync as existsSync5, createReadStream } from "fs";
23
- import { mkdir as mkdir4, rm as rm4, readdir, stat } from "fs/promises";
24
- import { join as join5 } from "path";
25
- import { pipeline } from "stream/promises";
26
- import archiver from "archiver";
27
- import { createGunzip } from "zlib";
28
- import { extract } from "tar";
29
- async function ensureBundlesDir() {
30
- if (!existsSync5(BUNDLES_DIR)) {
31
- await mkdir4(BUNDLES_DIR, { recursive: true });
32
- }
33
- }
34
- async function createBundle(skillPath, skillName) {
35
- await ensureBundlesDir();
36
- const bundleName = `${skillName}-${Date.now()}.tar.gz`;
37
- const bundlePath = join5(BUNDLES_DIR, bundleName);
38
- return new Promise((resolve, reject) => {
39
- const output = createWriteStream(bundlePath);
40
- const archive = archiver("tar", {
41
- gzip: true,
42
- gzipOptions: { level: 9 }
43
- });
44
- output.on("close", () => {
45
- console.log(`\u{1F4E6} Bundle created: ${bundlePath} (${archive.pointer()} bytes)`);
46
- resolve(bundlePath);
47
- });
48
- archive.on("error", (err) => {
49
- reject(err);
50
- });
51
- archive.pipe(output);
52
- archive.directory(skillPath, false);
53
- archive.finalize();
54
- });
55
- }
56
- async function extractBundle(bundlePath, targetDir) {
57
- if (!existsSync5(bundlePath)) {
58
- throw new Error(`Bundle not found: ${bundlePath}`);
59
- }
60
- if (!existsSync5(targetDir)) {
61
- await mkdir4(targetDir, { recursive: true });
62
- }
63
- await pipeline(
64
- createReadStream(bundlePath),
65
- createGunzip(),
66
- extract({ cwd: targetDir })
67
- );
68
- console.log(`\u{1F4C2} Bundle extracted to: ${targetDir}`);
69
- }
70
- async function deleteBundle(bundlePath) {
71
- if (existsSync5(bundlePath)) {
72
- await rm4(bundlePath);
73
- console.log(`\u{1F5D1}\uFE0F Bundle deleted: ${bundlePath}`);
74
- }
75
- }
76
- async function getBundleSize(bundlePath) {
77
- if (!existsSync5(bundlePath)) {
78
- return 0;
79
- }
80
- const stats = await stat(bundlePath);
81
- return stats.size;
82
- }
83
- async function listBundles() {
84
- await ensureBundlesDir();
85
- const files = await readdir(BUNDLES_DIR);
86
- return files.filter((f) => f.endsWith(".tar.gz")).map((f) => join5(BUNDLES_DIR, f));
87
- }
88
- var BUNDLES_DIR, bundleService;
89
- var init_bundleService = __esm({
90
- "src/services/bundleService.ts"() {
91
- "use strict";
92
- BUNDLES_DIR = join5(
93
- process.env.SKILLVERSE_HOME || join5(process.env.HOME || "", ".skillverse"),
94
- "bundles"
95
- );
96
- bundleService = {
97
- createBundle,
98
- extractBundle,
99
- deleteBundle,
100
- getBundleSize,
101
- listBundles,
102
- BUNDLES_DIR
103
- };
104
- }
105
- });
106
-
107
- // src/index.ts
108
- import express from "express";
109
- import cors from "cors";
110
- import dotenv2 from "dotenv";
111
- import { fileURLToPath as fileURLToPath2 } from "url";
112
- import { dirname as dirname2, join as join6 } from "path";
113
- import { mkdir as mkdir5 } from "fs/promises";
114
- import { existsSync as existsSync7 } from "fs";
115
-
116
- // src/routes/skills.ts
117
- import { Router } from "express";
118
- import multer from "multer";
119
- import { join as join4 } from "path";
120
- import { existsSync as existsSync4 } from "fs";
121
- import { mkdir as mkdir3, rm as rm3 } from "fs/promises";
122
-
123
- // src/services/skillService.ts
124
- import { join as join2, basename } from "path";
125
- import { existsSync as existsSync2 } from "fs";
126
- import { mkdir, rm, cp, readFile, unlink } from "fs/promises";
127
- import matter from "gray-matter";
128
- import simpleGit from "simple-git";
129
- import AdmZip from "adm-zip";
130
-
131
12
  // src/config.ts
132
13
  import { join, dirname } from "path";
133
14
  import { existsSync, mkdirSync } from "fs";
134
15
  import { fileURLToPath } from "url";
135
16
  import dotenv from "dotenv";
136
- dotenv.config();
137
- var __filename = fileURLToPath(import.meta.url);
138
- var __dirname = dirname(__filename);
139
17
  function setupEnvironment() {
140
18
  const home = process.env.HOME || process.env.USERPROFILE || "";
141
19
  const skillverseHome = process.env.SKILLVERSE_HOME || join(home, ".skillverse");
@@ -156,64 +34,77 @@ function setupEnvironment() {
156
34
  databaseUrl: process.env.DATABASE_URL
157
35
  };
158
36
  }
159
- var config = setupEnvironment();
37
+ var __filename, __dirname, config;
38
+ var init_config = __esm({
39
+ "src/config.ts"() {
40
+ "use strict";
41
+ dotenv.config();
42
+ __filename = fileURLToPath(import.meta.url);
43
+ __dirname = dirname(__filename);
44
+ config = setupEnvironment();
45
+ }
46
+ });
160
47
 
161
48
  // src/lib/db.ts
162
49
  import { PrismaClient } from "@prisma/client";
163
- var globalForPrisma = globalThis;
164
- var prisma = globalForPrisma.prisma ?? new PrismaClient({
165
- log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"]
50
+ var globalForPrisma, prisma;
51
+ var init_db = __esm({
52
+ "src/lib/db.ts"() {
53
+ "use strict";
54
+ init_config();
55
+ globalForPrisma = globalThis;
56
+ prisma = globalForPrisma.prisma ?? new PrismaClient({
57
+ log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"]
58
+ });
59
+ if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
60
+ }
166
61
  });
167
- if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
168
62
 
169
63
  // ../shared/dist/index.js
170
- var WORKSPACE_SKILLS_PATHS = {
171
- vscode: {
172
- project: ".github/skills",
173
- global: "~/.copilot/skills"
174
- },
175
- cursor: {
176
- project: ".cursor/skills",
177
- global: "~/.cursor/skills"
178
- },
179
- "claude-desktop": {
180
- project: ".claude/skills",
181
- global: "~/.claude/skills"
182
- },
183
- codex: {
184
- project: ".codex/skills",
185
- global: "~/.codex/skills"
186
- },
187
- antigravity: {
188
- project: ".agent/skills",
189
- global: "~/.agent/skills"
190
- },
191
- custom: {
192
- project: "skills",
193
- global: ""
64
+ var WORKSPACE_SKILLS_PATHS, ErrorCode;
65
+ var init_dist = __esm({
66
+ "../shared/dist/index.js"() {
67
+ "use strict";
68
+ WORKSPACE_SKILLS_PATHS = {
69
+ vscode: {
70
+ project: ".github/skills",
71
+ global: "~/.copilot/skills"
72
+ },
73
+ cursor: {
74
+ project: ".cursor/skills",
75
+ global: "~/.cursor/skills"
76
+ },
77
+ "claude-desktop": {
78
+ project: ".claude/skills",
79
+ global: "~/.claude/skills"
80
+ },
81
+ codex: {
82
+ project: ".codex/skills",
83
+ global: "~/.codex/skills"
84
+ },
85
+ antigravity: {
86
+ project: ".agent/skills",
87
+ global: "~/.agent/skills"
88
+ },
89
+ custom: {
90
+ project: "skills",
91
+ global: ""
92
+ }
93
+ };
94
+ ErrorCode = {
95
+ VALIDATION_ERROR: "VALIDATION_ERROR",
96
+ NOT_FOUND: "NOT_FOUND",
97
+ ALREADY_EXISTS: "ALREADY_EXISTS",
98
+ INTERNAL_ERROR: "INTERNAL_ERROR",
99
+ GIT_ERROR: "GIT_ERROR",
100
+ FILE_SYSTEM_ERROR: "FILE_SYSTEM_ERROR",
101
+ PERMISSION_ERROR: "PERMISSION_ERROR",
102
+ SYMLINK_ERROR: "SYMLINK_ERROR"
103
+ };
194
104
  }
195
- };
196
- var ErrorCode = {
197
- VALIDATION_ERROR: "VALIDATION_ERROR",
198
- NOT_FOUND: "NOT_FOUND",
199
- ALREADY_EXISTS: "ALREADY_EXISTS",
200
- INTERNAL_ERROR: "INTERNAL_ERROR",
201
- GIT_ERROR: "GIT_ERROR",
202
- FILE_SYSTEM_ERROR: "FILE_SYSTEM_ERROR",
203
- PERMISSION_ERROR: "PERMISSION_ERROR",
204
- SYMLINK_ERROR: "SYMLINK_ERROR"
205
- };
105
+ });
206
106
 
207
107
  // src/middleware/errorHandler.ts
208
- var AppError = class extends Error {
209
- constructor(code, message, statusCode = 500, details) {
210
- super(message);
211
- this.code = code;
212
- this.statusCode = statusCode;
213
- this.details = details;
214
- this.name = "AppError";
215
- }
216
- };
217
108
  function errorHandler(err, req, res, next) {
218
109
  console.error("Error:", err);
219
110
  if (err instanceof AppError) {
@@ -231,11 +122,30 @@ function errorHandler(err, req, res, next) {
231
122
  message: process.env.NODE_ENV === "development" ? err.message : void 0
232
123
  });
233
124
  }
125
+ var AppError;
126
+ var init_errorHandler = __esm({
127
+ "src/middleware/errorHandler.ts"() {
128
+ "use strict";
129
+ init_dist();
130
+ AppError = class extends Error {
131
+ constructor(code, message, statusCode = 500, details) {
132
+ super(message);
133
+ this.code = code;
134
+ this.statusCode = statusCode;
135
+ this.details = details;
136
+ this.name = "AppError";
137
+ }
138
+ };
139
+ }
140
+ });
234
141
 
235
142
  // src/services/skillService.ts
236
- var SKILLVERSE_HOME = process.env.SKILLVERSE_HOME || join2(process.env.HOME || "", ".skillverse");
237
- var SKILLS_DIR = process.env.SKILLS_DIR || join2(SKILLVERSE_HOME, "skills");
238
- var TEMP_DIR = process.env.TEMP_DIR || join2(SKILLVERSE_HOME, "temp");
143
+ import { join as join3, basename } from "path";
144
+ import { existsSync as existsSync3 } from "fs";
145
+ import { mkdir, rm, cp, readFile, unlink } from "fs/promises";
146
+ import matter from "gray-matter";
147
+ import simpleGit from "simple-git";
148
+ import AdmZip from "adm-zip";
239
149
  function parseGitUrl(url) {
240
150
  if (!url.includes("/tree/")) {
241
151
  const urlParts = url.split("/");
@@ -250,1601 +160,1754 @@ function parseGitUrl(url) {
250
160
  const skillName = pathParts[pathParts.length - 1];
251
161
  return { repoUrl, subdir, skillName };
252
162
  }
253
- var SkillService = class {
254
- async getAllSkills() {
255
- const skills = await prisma.skill.findMany({
256
- include: {
257
- linkedWorkspaces: {
163
+ var SKILLVERSE_HOME, SKILLS_DIR, TEMP_DIR, SkillService, skillService;
164
+ var init_skillService = __esm({
165
+ "src/services/skillService.ts"() {
166
+ "use strict";
167
+ init_db();
168
+ init_errorHandler();
169
+ init_dist();
170
+ SKILLVERSE_HOME = process.env.SKILLVERSE_HOME || join3(process.env.HOME || "", ".skillverse");
171
+ SKILLS_DIR = process.env.SKILLS_DIR || join3(SKILLVERSE_HOME, "skills");
172
+ TEMP_DIR = process.env.TEMP_DIR || join3(SKILLVERSE_HOME, "temp");
173
+ SkillService = class {
174
+ async getAllSkills() {
175
+ const skills = await prisma.skill.findMany({
258
176
  include: {
259
- workspace: true
177
+ linkedWorkspaces: {
178
+ include: {
179
+ workspace: true
180
+ }
181
+ },
182
+ marketplaceEntry: true
183
+ },
184
+ orderBy: {
185
+ installDate: "desc"
260
186
  }
261
- },
262
- marketplaceEntry: true
263
- },
264
- orderBy: {
265
- installDate: "desc"
187
+ });
188
+ return skills;
266
189
  }
267
- });
268
- return skills;
269
- }
270
- async getSkillById(id) {
271
- const skill = await prisma.skill.findUnique({
272
- where: { id },
273
- include: {
274
- linkedWorkspaces: {
190
+ async getSkillById(id) {
191
+ const skill = await prisma.skill.findUnique({
192
+ where: { id },
275
193
  include: {
276
- workspace: true
194
+ linkedWorkspaces: {
195
+ include: {
196
+ workspace: true
197
+ }
198
+ },
199
+ marketplaceEntry: true
277
200
  }
278
- },
279
- marketplaceEntry: true
201
+ });
202
+ if (!skill) {
203
+ throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
204
+ }
205
+ return skill;
280
206
  }
281
- });
282
- if (!skill) {
283
- throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
284
- }
285
- return skill;
286
- }
287
- async getSkillByName(name) {
288
- const skill = await prisma.skill.findUnique({
289
- where: { name },
290
- include: {
291
- linkedWorkspaces: {
207
+ async getSkillByName(name) {
208
+ const skill = await prisma.skill.findUnique({
209
+ where: { name },
292
210
  include: {
293
- workspace: true
211
+ linkedWorkspaces: {
212
+ include: {
213
+ workspace: true
214
+ }
215
+ },
216
+ marketplaceEntry: true
294
217
  }
295
- },
296
- marketplaceEntry: true
297
- }
298
- });
299
- if (!skill) {
300
- throw new AppError(ErrorCode.NOT_FOUND, `Skill "${name}" not found`, 404);
301
- }
302
- return skill;
303
- }
304
- async parseSkillMetadata(skillPath) {
305
- try {
306
- const skillMdPath = join2(skillPath, "SKILL.md");
307
- let description = "";
308
- let metadata = {};
309
- if (existsSync2(skillMdPath)) {
310
- const fileContent = await readFile(skillMdPath, "utf-8");
311
- const parsed = matter(fileContent);
312
- description = parsed.data.description || "";
313
- metadata = parsed.data;
314
- }
315
- if (!description) {
316
- const packageJsonPath = join2(skillPath, "package.json");
317
- const skillJsonPath = join2(skillPath, "skill.json");
318
- if (existsSync2(skillJsonPath)) {
319
- const content = await readFile(skillJsonPath, "utf-8");
320
- const json = JSON.parse(content);
321
- description = json.description || "";
322
- metadata = { ...metadata, ...json };
323
- } else if (existsSync2(packageJsonPath)) {
324
- const content = await readFile(packageJsonPath, "utf-8");
325
- const pkg = JSON.parse(content);
326
- description = pkg.description || "";
327
- metadata = { ...metadata, name: pkg.name, version: pkg.version };
328
- }
329
- }
330
- return { description, metadata };
331
- } catch (error) {
332
- console.warn("Failed to parse skill metadata:", error);
333
- return { description: "", metadata: {} };
334
- }
335
- }
336
- async createSkillFromGit(gitUrl, description) {
337
- let tempPath = null;
338
- try {
339
- const { repoUrl, subdir, skillName } = parseGitUrl(gitUrl);
340
- const existingSkill = await prisma.skill.findUnique({
341
- where: { name: skillName }
342
- });
343
- if (existingSkill) {
344
- throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${skillName}" already exists`, 409);
218
+ });
219
+ if (!skill) {
220
+ throw new AppError(ErrorCode.NOT_FOUND, `Skill "${name}" not found`, 404);
221
+ }
222
+ return skill;
345
223
  }
346
- const skillPath = join2(SKILLS_DIR, skillName);
347
- if (existsSync2(skillPath)) {
348
- throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${skillName}" already exists`, 409);
224
+ async parseSkillMetadata(skillPath) {
225
+ try {
226
+ const skillMdPath = join3(skillPath, "SKILL.md");
227
+ let description = "";
228
+ let metadata = {};
229
+ if (existsSync3(skillMdPath)) {
230
+ const fileContent = await readFile(skillMdPath, "utf-8");
231
+ const parsed = matter(fileContent);
232
+ description = parsed.data.description || "";
233
+ metadata = parsed.data;
234
+ }
235
+ if (!description) {
236
+ const packageJsonPath = join3(skillPath, "package.json");
237
+ const skillJsonPath = join3(skillPath, "skill.json");
238
+ if (existsSync3(skillJsonPath)) {
239
+ const content = await readFile(skillJsonPath, "utf-8");
240
+ const json = JSON.parse(content);
241
+ description = json.description || "";
242
+ metadata = { ...metadata, ...json };
243
+ } else if (existsSync3(packageJsonPath)) {
244
+ const content = await readFile(packageJsonPath, "utf-8");
245
+ const pkg = JSON.parse(content);
246
+ description = pkg.description || "";
247
+ metadata = { ...metadata, name: pkg.name, version: pkg.version };
248
+ }
249
+ }
250
+ return { description, metadata };
251
+ } catch (error) {
252
+ console.warn("Failed to parse skill metadata:", error);
253
+ return { description: "", metadata: {} };
254
+ }
349
255
  }
350
- let commitHash = "";
351
- if (subdir) {
352
- tempPath = join2(TEMP_DIR, `git-clone-${Date.now()}`);
353
- await mkdir(tempPath, { recursive: true });
354
- console.log(`Cloning ${repoUrl} to temp path for extraction...`);
355
- const git = simpleGit();
356
- await git.clone(repoUrl, tempPath);
256
+ async createSkillFromGit(gitUrl, description) {
257
+ let tempPath = null;
357
258
  try {
358
- commitHash = await simpleGit(tempPath).revparse(["HEAD"]);
359
- console.log(`Captured commit hash: ${commitHash}`);
360
- } catch (e) {
361
- console.warn("Failed to capture commit hash:", e);
259
+ const { repoUrl, subdir, skillName } = parseGitUrl(gitUrl);
260
+ const existingSkill = await prisma.skill.findUnique({
261
+ where: { name: skillName }
262
+ });
263
+ if (existingSkill) {
264
+ throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${skillName}" already exists`, 409);
265
+ }
266
+ const skillPath = join3(SKILLS_DIR, skillName);
267
+ if (existsSync3(skillPath)) {
268
+ throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${skillName}" already exists`, 409);
269
+ }
270
+ let commitHash = "";
271
+ if (subdir) {
272
+ tempPath = join3(TEMP_DIR, `git-clone-${Date.now()}`);
273
+ await mkdir(tempPath, { recursive: true });
274
+ console.log(`Cloning ${repoUrl} to temp path for extraction...`);
275
+ const git = simpleGit();
276
+ await git.clone(repoUrl, tempPath);
277
+ try {
278
+ commitHash = await simpleGit(tempPath).revparse(["HEAD"]);
279
+ console.log(`Captured commit hash: ${commitHash}`);
280
+ } catch (e) {
281
+ console.warn("Failed to capture commit hash:", e);
282
+ }
283
+ const sourcePath = join3(tempPath, subdir);
284
+ if (!existsSync3(sourcePath)) {
285
+ throw new AppError(
286
+ ErrorCode.GIT_ERROR,
287
+ `Subdirectory "${subdir}" not found in repository`,
288
+ 400
289
+ );
290
+ }
291
+ const hasSkillMd = existsSync3(join3(sourcePath, "SKILL.md"));
292
+ const hasSkillJson = existsSync3(join3(sourcePath, "skill.json"));
293
+ const hasPackageJson = existsSync3(join3(sourcePath, "package.json"));
294
+ if (!hasSkillMd && !hasSkillJson && !hasPackageJson) {
295
+ throw new AppError(
296
+ ErrorCode.VALIDATION_ERROR,
297
+ `Invalid skill structure: "${subdir}" does not contain SKILL.md, skill.json, or package.json`,
298
+ 400
299
+ );
300
+ }
301
+ await mkdir(skillPath, { recursive: true });
302
+ await cp(sourcePath, skillPath, { recursive: true });
303
+ } else {
304
+ if (!existsSync3(skillPath)) {
305
+ await mkdir(skillPath, { recursive: true });
306
+ }
307
+ const git = simpleGit();
308
+ console.log(`Cloning ${gitUrl} to ${skillPath}...`);
309
+ await git.clone(gitUrl, skillPath);
310
+ try {
311
+ commitHash = await git.cwd(skillPath).revparse(["HEAD"]);
312
+ console.log(`Captured commit hash: ${commitHash}`);
313
+ } catch (e) {
314
+ console.warn("Failed to capture commit hash:", e);
315
+ }
316
+ }
317
+ const parsed = await this.parseSkillMetadata(skillPath);
318
+ const skill = await prisma.skill.create({
319
+ data: {
320
+ name: skillName,
321
+ source: "git",
322
+ sourceUrl: gitUrl,
323
+ repoUrl,
324
+ commitHash,
325
+ description: description || parsed.description || "",
326
+ storagePath: skillPath,
327
+ metadata: JSON.stringify(parsed.metadata)
328
+ }
329
+ });
330
+ return skill;
331
+ } catch (error) {
332
+ throw error;
333
+ } finally {
334
+ if (tempPath && existsSync3(tempPath)) {
335
+ await rm(tempPath, { recursive: true, force: true }).catch(console.error);
336
+ }
362
337
  }
363
- const sourcePath = join2(tempPath, subdir);
364
- if (!existsSync2(sourcePath)) {
365
- throw new AppError(
366
- ErrorCode.GIT_ERROR,
367
- `Subdirectory "${subdir}" not found in repository`,
368
- 400
369
- );
338
+ }
339
+ async createSkillFromDirectory(path, name, description) {
340
+ if (!existsSync3(path)) {
341
+ throw new AppError(ErrorCode.FILE_SYSTEM_ERROR, `Source path not found: ${path}`, 400);
370
342
  }
371
- const hasSkillMd = existsSync2(join2(sourcePath, "SKILL.md"));
372
- const hasSkillJson = existsSync2(join2(sourcePath, "skill.json"));
373
- const hasPackageJson = existsSync2(join2(sourcePath, "package.json"));
374
- if (!hasSkillMd && !hasSkillJson && !hasPackageJson) {
375
- throw new AppError(
376
- ErrorCode.VALIDATION_ERROR,
377
- `Invalid skill structure: "${subdir}" does not contain SKILL.md, skill.json, or package.json`,
378
- 400
379
- );
343
+ const skillName = name || basename(path);
344
+ const skillPath = join3(SKILLS_DIR, skillName);
345
+ const existingSkill = await prisma.skill.findUnique({
346
+ where: { name: skillName }
347
+ });
348
+ if (existingSkill) {
349
+ throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${skillName}" already exists`, 409);
380
350
  }
381
- await mkdir(skillPath, { recursive: true });
382
- await cp(sourcePath, skillPath, { recursive: true });
383
- } else {
384
- if (!existsSync2(skillPath)) {
385
- await mkdir(skillPath, { recursive: true });
351
+ if (existsSync3(skillPath)) {
352
+ throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${skillName}" already exists`, 409);
386
353
  }
387
- const git = simpleGit();
388
- console.log(`Cloning ${gitUrl} to ${skillPath}...`);
389
- await git.clone(gitUrl, skillPath);
390
354
  try {
391
- commitHash = await git.cwd(skillPath).revparse(["HEAD"]);
392
- console.log(`Captured commit hash: ${commitHash}`);
393
- } catch (e) {
394
- console.warn("Failed to capture commit hash:", e);
355
+ await mkdir(skillPath, { recursive: true });
356
+ await cp(path, skillPath, { recursive: true });
357
+ const parsed = await this.parseSkillMetadata(skillPath);
358
+ const skill = await prisma.skill.create({
359
+ data: {
360
+ name: skillName,
361
+ source: "local",
362
+ description: description || parsed.description || "",
363
+ storagePath: skillPath,
364
+ metadata: JSON.stringify(parsed.metadata)
365
+ }
366
+ });
367
+ return skill;
368
+ } catch (error) {
369
+ if (existsSync3(skillPath)) {
370
+ await rm(skillPath, { recursive: true, force: true }).catch(() => {
371
+ });
372
+ }
373
+ throw error;
395
374
  }
396
375
  }
397
- const parsed = await this.parseSkillMetadata(skillPath);
398
- const skill = await prisma.skill.create({
399
- data: {
400
- name: skillName,
401
- source: "git",
402
- sourceUrl: gitUrl,
403
- repoUrl,
404
- commitHash,
405
- description: description || parsed.description || "",
406
- storagePath: skillPath,
407
- metadata: JSON.stringify(parsed.metadata)
376
+ /**
377
+ * Helper to handle cases where the archive contains a single top-level directory.
378
+ * Moves contents up one level if that's the case.
379
+ */
380
+ async stripTopLevelDirectory(targetPath) {
381
+ const { readdir: readdir2, rename, rmdir, stat: stat2 } = await import("fs/promises");
382
+ const items = await readdir2(targetPath);
383
+ const validItems = items.filter((i) => i !== ".DS_Store" && i !== "__MACOSX");
384
+ if (validItems.length === 1) {
385
+ const topLevelItem = validItems[0];
386
+ const topLevelPath = join3(targetPath, topLevelItem);
387
+ const stats = await stat2(topLevelPath);
388
+ if (stats.isDirectory()) {
389
+ console.log(`Striping top-level directory: ${topLevelItem}`);
390
+ const subItems = await readdir2(topLevelPath);
391
+ for (const item of subItems) {
392
+ await rename(join3(topLevelPath, item), join3(targetPath, item));
393
+ }
394
+ await rmdir(topLevelPath);
395
+ }
408
396
  }
409
- });
410
- return skill;
411
- } catch (error) {
412
- throw error;
413
- } finally {
414
- if (tempPath && existsSync2(tempPath)) {
415
- await rm(tempPath, { recursive: true, force: true }).catch(console.error);
416
397
  }
417
- }
418
- }
419
- async createSkillFromDirectory(path, name, description) {
420
- if (!existsSync2(path)) {
421
- throw new AppError(ErrorCode.FILE_SYSTEM_ERROR, `Source path not found: ${path}`, 400);
422
- }
423
- const skillName = name || basename(path);
424
- const skillPath = join2(SKILLS_DIR, skillName);
425
- const existingSkill = await prisma.skill.findUnique({
426
- where: { name: skillName }
427
- });
428
- if (existingSkill) {
429
- throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${skillName}" already exists`, 409);
430
- }
431
- if (existsSync2(skillPath)) {
432
- throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${skillName}" already exists`, 409);
433
- }
434
- try {
435
- await mkdir(skillPath, { recursive: true });
436
- await cp(path, skillPath, { recursive: true });
437
- const parsed = await this.parseSkillMetadata(skillPath);
438
- const skill = await prisma.skill.create({
439
- data: {
440
- name: skillName,
441
- source: "local",
442
- description: description || parsed.description || "",
443
- storagePath: skillPath,
444
- metadata: JSON.stringify(parsed.metadata)
445
- }
446
- });
447
- return skill;
448
- } catch (error) {
449
- if (existsSync2(skillPath)) {
450
- await rm(skillPath, { recursive: true, force: true }).catch(() => {
398
+ async createSkillFromLocal(name, zipPath, description) {
399
+ const skillPath = join3(SKILLS_DIR, name);
400
+ const existingSkill = await prisma.skill.findUnique({
401
+ where: { name }
451
402
  });
452
- }
453
- throw error;
454
- }
455
- }
456
- /**
457
- * Helper to handle cases where the archive contains a single top-level directory.
458
- * Moves contents up one level if that's the case.
459
- */
460
- async stripTopLevelDirectory(targetPath) {
461
- const { readdir: readdir2, rename, rmdir, stat: stat2 } = await import("fs/promises");
462
- const items = await readdir2(targetPath);
463
- const validItems = items.filter((i) => i !== ".DS_Store" && i !== "__MACOSX");
464
- if (validItems.length === 1) {
465
- const topLevelItem = validItems[0];
466
- const topLevelPath = join2(targetPath, topLevelItem);
467
- const stats = await stat2(topLevelPath);
468
- if (stats.isDirectory()) {
469
- console.log(`Striping top-level directory: ${topLevelItem}`);
470
- const subItems = await readdir2(topLevelPath);
471
- for (const item of subItems) {
472
- await rename(join2(topLevelPath, item), join2(targetPath, item));
473
- }
474
- await rmdir(topLevelPath);
475
- }
476
- }
477
- }
478
- async createSkillFromLocal(name, zipPath, description) {
479
- const skillPath = join2(SKILLS_DIR, name);
480
- const existingSkill = await prisma.skill.findUnique({
481
- where: { name }
482
- });
483
- if (existingSkill) {
484
- throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${name}" already exists`, 409);
485
- }
486
- if (existsSync2(skillPath)) {
487
- throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
488
- }
489
- const extractPath = join2(TEMP_DIR, `extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
490
- await mkdir(extractPath, { recursive: true });
491
- try {
492
- const zip = new AdmZip(zipPath);
493
- zip.extractAllTo(extractPath, true);
494
- await this.stripTopLevelDirectory(extractPath);
495
- const parsed = await this.parseSkillMetadata(extractPath);
496
- if (existsSync2(skillPath)) {
497
- throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
498
- }
499
- await import("fs/promises").then((fs) => fs.rename(extractPath, skillPath));
500
- const skill = await prisma.skill.create({
501
- data: {
502
- name,
503
- source: "local",
504
- description: description || parsed.description || "",
505
- storagePath: skillPath,
506
- metadata: JSON.stringify(parsed.metadata)
403
+ if (existingSkill) {
404
+ throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${name}" already exists`, 409);
507
405
  }
508
- });
509
- return skill;
510
- } catch (error) {
511
- if (existsSync2(skillPath)) {
512
- await rm(skillPath, { recursive: true, force: true }).catch(() => {
513
- });
514
- }
515
- if (existsSync2(extractPath)) {
516
- await rm(extractPath, { recursive: true, force: true }).catch(() => {
517
- });
518
- }
519
- throw error;
520
- }
521
- }
522
- /**
523
- * Create a skill from a tar.gz bundle (marketplace install)
524
- * @param bundlePath - Path to the .tar.gz bundle
525
- * @param originalName - Original skill name
526
- * @param description - Optional skill description
527
- */
528
- async createSkillFromBundle(bundlePath, originalName, description) {
529
- let name = originalName;
530
- let counter = 1;
531
- while (await prisma.skill.findUnique({ where: { name } })) {
532
- name = `${originalName}-${counter}`;
533
- counter++;
534
- }
535
- const skillPath = join2(SKILLS_DIR, name);
536
- const extractPath = join2(TEMP_DIR, `extract-bundle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
537
- await mkdir(extractPath, { recursive: true });
538
- try {
539
- const { createReadStream: createReadStream2 } = await import("fs");
540
- const { createGunzip: createGunzip2 } = await import("zlib");
541
- const tar = await import("tar");
542
- const { pipeline: pipeline2 } = await import("stream/promises");
543
- await pipeline2(
544
- createReadStream2(bundlePath),
545
- createGunzip2(),
546
- tar.extract({ cwd: extractPath })
547
- );
548
- await this.stripTopLevelDirectory(extractPath);
549
- const parsed = await this.parseSkillMetadata(extractPath);
550
- if (existsSync2(skillPath)) {
551
- throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
552
- }
553
- await import("fs/promises").then((fs) => fs.rename(extractPath, skillPath));
554
- const skill = await prisma.skill.create({
555
- data: {
556
- name,
557
- source: "local",
558
- description: description || parsed.description || "",
559
- storagePath: skillPath,
560
- metadata: JSON.stringify(parsed.metadata)
406
+ if (existsSync3(skillPath)) {
407
+ throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
561
408
  }
562
- });
563
- console.log(`\u{1F4E6} Installed skill "${name}" from bundle`);
564
- return skill;
565
- } catch (error) {
566
- if (existsSync2(skillPath)) {
567
- await rm(skillPath, { recursive: true, force: true }).catch(() => {
568
- });
569
- }
570
- if (existsSync2(extractPath)) {
571
- await rm(extractPath, { recursive: true, force: true }).catch(() => {
572
- });
573
- }
574
- throw error;
575
- }
576
- }
577
- async updateSkill(id, data) {
578
- const skill = await this.getSkillById(id);
579
- const updatedSkill = await prisma.skill.update({
580
- where: { id },
581
- data: {
582
- ...data.name && { name: data.name },
583
- ...data.description !== void 0 && { description: data.description },
584
- ...data.metadata && { metadata: JSON.stringify(data.metadata) }
585
- }
586
- });
587
- return updatedSkill;
588
- }
589
- async deleteSkill(id, removeFiles = true) {
590
- const skill = await this.getSkillById(id);
591
- if (skill.linkedWorkspaces && skill.linkedWorkspaces.length > 0) {
592
- for (const link of skill.linkedWorkspaces) {
409
+ const extractPath = join3(TEMP_DIR, `extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
410
+ await mkdir(extractPath, { recursive: true });
593
411
  try {
594
- const workspacePath = link.workspace.path;
595
- const symlinkPath = join2(workspacePath, skill.name);
596
- if (existsSync2(symlinkPath)) {
597
- const stats = await import("fs/promises").then((fs) => fs.lstat(symlinkPath));
598
- if (stats.isSymbolicLink()) {
599
- await unlink(symlinkPath);
600
- console.log(`Removed symlink for ${skill.name} in workspace ${workspacePath}`);
601
- }
412
+ const zip = new AdmZip(zipPath);
413
+ zip.extractAllTo(extractPath, true);
414
+ await this.stripTopLevelDirectory(extractPath);
415
+ const parsed = await this.parseSkillMetadata(extractPath);
416
+ if (existsSync3(skillPath)) {
417
+ throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
602
418
  }
419
+ await import("fs/promises").then((fs) => fs.rename(extractPath, skillPath));
420
+ const skill = await prisma.skill.create({
421
+ data: {
422
+ name,
423
+ source: "local",
424
+ description: description || parsed.description || "",
425
+ storagePath: skillPath,
426
+ metadata: JSON.stringify(parsed.metadata)
427
+ }
428
+ });
429
+ return skill;
603
430
  } catch (error) {
604
- console.warn(`Failed to remove symlink for ${skill.name} in workspace ${link.workspace.path}:`, error);
431
+ if (existsSync3(skillPath)) {
432
+ await rm(skillPath, { recursive: true, force: true }).catch(() => {
433
+ });
434
+ }
435
+ if (existsSync3(extractPath)) {
436
+ await rm(extractPath, { recursive: true, force: true }).catch(() => {
437
+ });
438
+ }
439
+ throw error;
605
440
  }
606
441
  }
607
- }
608
- await prisma.skill.delete({
609
- where: { id }
610
- });
611
- if (removeFiles && skill.storagePath) {
612
- await rm(skill.storagePath, { recursive: true, force: true }).catch((err) => {
613
- console.warn(`Failed to delete skill directory ${skill.storagePath}:`, err);
614
- });
615
- }
616
- return { success: true, message: "Skill deleted successfully" };
617
- }
618
- // Check for updates
619
- async checkForUpdate(id) {
620
- const skill = await this.getSkillById(id);
621
- if (skill.source !== "git" || !skill.repoUrl) {
622
- return {
623
- hasUpdate: false,
624
- currentHash: skill.commitHash,
625
- remoteHash: null,
626
- message: "Not a git skill or missing repo URL"
627
- };
628
- }
629
- const tempPath = join2(TEMP_DIR, `check-update-${Date.now()}`);
630
- await mkdir(tempPath, { recursive: true });
631
- try {
632
- const git = simpleGit();
633
- console.log(`Checking updates for ${skill.name} from ${skill.repoUrl}...`);
634
- const remote = await git.listRemote([skill.repoUrl, "HEAD"]);
635
- if (!remote) {
636
- throw new Error("Failed to get remote HEAD");
637
- }
638
- const remoteHash = remote.split(" ")[0];
639
- const hasUpdate = remoteHash !== skill.commitHash;
640
- await prisma.skill.update({
641
- where: { id },
642
- data: {
643
- lastUpdateCheck: /* @__PURE__ */ new Date(),
644
- updateAvailable: hasUpdate
442
+ /**
443
+ * Create a skill from a tar.gz bundle (marketplace install)
444
+ * @param bundlePath - Path to the .tar.gz bundle
445
+ * @param originalName - Original skill name
446
+ * @param description - Optional skill description
447
+ */
448
+ async createSkillFromBundle(bundlePath, originalName, description) {
449
+ let name = originalName;
450
+ let counter = 1;
451
+ while (await prisma.skill.findUnique({ where: { name } })) {
452
+ name = `${originalName}-${counter}`;
453
+ counter++;
454
+ }
455
+ const skillPath = join3(SKILLS_DIR, name);
456
+ const extractPath = join3(TEMP_DIR, `extract-bundle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
457
+ await mkdir(extractPath, { recursive: true });
458
+ try {
459
+ const { createReadStream: createReadStream2 } = await import("fs");
460
+ const { createGunzip: createGunzip2 } = await import("zlib");
461
+ const tar = await import("tar");
462
+ const { pipeline: pipeline2 } = await import("stream/promises");
463
+ await pipeline2(
464
+ createReadStream2(bundlePath),
465
+ createGunzip2(),
466
+ tar.extract({ cwd: extractPath })
467
+ );
468
+ await this.stripTopLevelDirectory(extractPath);
469
+ const parsed = await this.parseSkillMetadata(extractPath);
470
+ if (existsSync3(skillPath)) {
471
+ throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
472
+ }
473
+ await import("fs/promises").then((fs) => fs.rename(extractPath, skillPath));
474
+ const skill = await prisma.skill.create({
475
+ data: {
476
+ name,
477
+ source: "local",
478
+ description: description || parsed.description || "",
479
+ storagePath: skillPath,
480
+ metadata: JSON.stringify(parsed.metadata)
481
+ }
482
+ });
483
+ console.log(`\u{1F4E6} Installed skill "${name}" from bundle`);
484
+ return skill;
485
+ } catch (error) {
486
+ if (existsSync3(skillPath)) {
487
+ await rm(skillPath, { recursive: true, force: true }).catch(() => {
488
+ });
489
+ }
490
+ if (existsSync3(extractPath)) {
491
+ await rm(extractPath, { recursive: true, force: true }).catch(() => {
492
+ });
493
+ }
494
+ throw error;
645
495
  }
646
- });
647
- return {
648
- hasUpdate,
649
- currentHash: skill.commitHash,
650
- remoteHash
651
- };
652
- } catch (error) {
653
- console.error("Update check error:", error);
654
- throw new AppError(ErrorCode.GIT_ERROR, `Failed to check for updates: ${error.message}`, 500);
655
- } finally {
656
- await rm(tempPath, { recursive: true, force: true }).catch(() => {
657
- });
658
- }
659
- }
660
- async checkUpdates(ids) {
661
- const whereClause = {
662
- source: "git",
663
- repoUrl: { not: null }
664
- };
665
- if (ids && ids.length > 0) {
666
- whereClause.id = { in: ids };
667
- }
668
- const skills = await prisma.skill.findMany({ where: whereClause });
669
- const results = {};
670
- console.log(`Checking updates for ${skills.length} skills...`);
671
- await Promise.all(skills.map(async (skill) => {
672
- try {
673
- const result = await this.checkForUpdate(skill.id);
674
- results[skill.id] = result;
675
- } catch (e) {
676
- results[skill.id] = { error: e.message };
677
496
  }
678
- }));
679
- return results;
680
- }
681
- async refreshMetadata(id) {
682
- const skill = await this.getSkillById(id);
683
- const parsed = await this.parseSkillMetadata(skill.storagePath);
684
- const updated = await prisma.skill.update({
685
- where: { id },
686
- data: {
687
- description: parsed.description,
688
- metadata: JSON.stringify(parsed.metadata)
497
+ async updateSkill(id, data) {
498
+ const skill = await this.getSkillById(id);
499
+ const updatedSkill = await prisma.skill.update({
500
+ where: { id },
501
+ data: {
502
+ ...data.name && { name: data.name },
503
+ ...data.description !== void 0 && { description: data.description },
504
+ ...data.metadata && { metadata: JSON.stringify(data.metadata) }
505
+ }
506
+ });
507
+ return updatedSkill;
689
508
  }
690
- });
691
- return updated;
692
- }
693
- async upgradeSkill(id) {
694
- const skill = await this.getSkillById(id);
695
- if (skill.source !== "git" || !skill.sourceUrl) {
696
- throw new AppError(ErrorCode.VALIDATION_ERROR, "Cannot upgrade non-git skill", 400);
697
- }
698
- let tempPath = null;
699
- let tempSkillPath = null;
700
- try {
701
- const { repoUrl, subdir } = parseGitUrl(skill.sourceUrl);
702
- tempPath = join2(TEMP_DIR, `upgrade-${Date.now()}`);
703
- await mkdir(tempPath, { recursive: true });
704
- const git = simpleGit();
705
- console.log(`Cloning ${repoUrl} to temp for upgrade...`);
706
- await git.clone(repoUrl, tempPath);
707
- let commitHash = "";
708
- try {
709
- commitHash = await simpleGit(tempPath).revparse(["HEAD"]);
710
- } catch (e) {
711
- console.warn("Failed to capture commit hash during upgrade:", e);
712
- }
713
- let sourcePath = tempPath;
714
- if (subdir) {
715
- sourcePath = join2(tempPath, subdir);
716
- if (!existsSync2(sourcePath)) {
717
- throw new AppError(ErrorCode.GIT_ERROR, `Subdirectory "${subdir}" not found`, 400);
718
- }
719
- }
720
- const hasSkillMd = existsSync2(join2(sourcePath, "SKILL.md"));
721
- const hasSkillJson = existsSync2(join2(sourcePath, "skill.json"));
722
- const hasPackageJson = existsSync2(join2(sourcePath, "package.json"));
723
- if (!hasSkillMd && !hasSkillJson && !hasPackageJson) {
724
- throw new AppError(ErrorCode.VALIDATION_ERROR, "Invalid skill structure in new version", 400);
725
- }
726
- const files = await import("fs/promises").then((fs) => fs.readdir(skill.storagePath));
727
- for (const file of files) {
728
- await rm(join2(skill.storagePath, file), { recursive: true, force: true });
729
- }
730
- await cp(sourcePath, skill.storagePath, { recursive: true });
731
- const parsed = await this.parseSkillMetadata(skill.storagePath);
732
- const updatedSkill = await prisma.skill.update({
733
- where: { id },
734
- data: {
735
- commitHash,
736
- updateAvailable: false,
737
- lastUpdateCheck: /* @__PURE__ */ new Date(),
738
- installDate: /* @__PURE__ */ new Date(),
739
- // Considering upgrade as reinstall? Or user prefer generic updatedAt
740
- // Keeping installDate as "last installed/upgraded"
741
- description: parsed.description,
742
- metadata: JSON.stringify(parsed.metadata)
509
+ async deleteSkill(id, removeFiles = true) {
510
+ const skill = await this.getSkillById(id);
511
+ if (skill.linkedWorkspaces && skill.linkedWorkspaces.length > 0) {
512
+ for (const link of skill.linkedWorkspaces) {
513
+ try {
514
+ const workspacePath = link.workspace.path;
515
+ const symlinkPath = join3(workspacePath, skill.name);
516
+ if (existsSync3(symlinkPath)) {
517
+ const stats = await import("fs/promises").then((fs) => fs.lstat(symlinkPath));
518
+ if (stats.isSymbolicLink()) {
519
+ await unlink(symlinkPath);
520
+ console.log(`Removed symlink for ${skill.name} in workspace ${workspacePath}`);
521
+ }
522
+ }
523
+ } catch (error) {
524
+ console.warn(`Failed to remove symlink for ${skill.name} in workspace ${link.workspace.path}:`, error);
525
+ }
526
+ }
743
527
  }
744
- });
745
- return updatedSkill;
746
- } catch (error) {
747
- console.error("Upgrade error:", error);
748
- throw new AppError(ErrorCode.GIT_ERROR, `Failed to upgrade skill: ${error.message}`, 500);
749
- } finally {
750
- if (tempPath) await rm(tempPath, { recursive: true, force: true }).catch(() => {
751
- });
752
- }
528
+ await prisma.skill.delete({
529
+ where: { id }
530
+ });
531
+ if (removeFiles && skill.storagePath) {
532
+ await rm(skill.storagePath, { recursive: true, force: true }).catch((err) => {
533
+ console.warn(`Failed to delete skill directory ${skill.storagePath}:`, err);
534
+ });
535
+ }
536
+ return { success: true, message: "Skill deleted successfully" };
537
+ }
538
+ // Check for updates
539
+ async checkForUpdate(id) {
540
+ const skill = await this.getSkillById(id);
541
+ if (skill.source !== "git" || !skill.repoUrl) {
542
+ return {
543
+ hasUpdate: false,
544
+ currentHash: skill.commitHash,
545
+ remoteHash: null,
546
+ message: "Not a git skill or missing repo URL"
547
+ };
548
+ }
549
+ const tempPath = join3(TEMP_DIR, `check-update-${Date.now()}`);
550
+ await mkdir(tempPath, { recursive: true });
551
+ try {
552
+ const git = simpleGit();
553
+ console.log(`Checking updates for ${skill.name} from ${skill.repoUrl}...`);
554
+ const remote = await git.listRemote([skill.repoUrl, "HEAD"]);
555
+ if (!remote) {
556
+ throw new Error("Failed to get remote HEAD");
557
+ }
558
+ const remoteHash = remote.split(" ")[0];
559
+ const hasUpdate = remoteHash !== skill.commitHash;
560
+ await prisma.skill.update({
561
+ where: { id },
562
+ data: {
563
+ lastUpdateCheck: /* @__PURE__ */ new Date(),
564
+ updateAvailable: hasUpdate
565
+ }
566
+ });
567
+ return {
568
+ hasUpdate,
569
+ currentHash: skill.commitHash,
570
+ remoteHash
571
+ };
572
+ } catch (error) {
573
+ console.error("Update check error:", error);
574
+ throw new AppError(ErrorCode.GIT_ERROR, `Failed to check for updates: ${error.message}`, 500);
575
+ } finally {
576
+ await rm(tempPath, { recursive: true, force: true }).catch(() => {
577
+ });
578
+ }
579
+ }
580
+ async checkUpdates(ids) {
581
+ const whereClause = {
582
+ source: "git",
583
+ repoUrl: { not: null }
584
+ };
585
+ if (ids && ids.length > 0) {
586
+ whereClause.id = { in: ids };
587
+ }
588
+ const skills = await prisma.skill.findMany({ where: whereClause });
589
+ const results = {};
590
+ console.log(`Checking updates for ${skills.length} skills...`);
591
+ await Promise.all(skills.map(async (skill) => {
592
+ try {
593
+ const result = await this.checkForUpdate(skill.id);
594
+ results[skill.id] = result;
595
+ } catch (e) {
596
+ results[skill.id] = { error: e.message };
597
+ }
598
+ }));
599
+ return results;
600
+ }
601
+ async refreshMetadata(id) {
602
+ const skill = await this.getSkillById(id);
603
+ const parsed = await this.parseSkillMetadata(skill.storagePath);
604
+ const updated = await prisma.skill.update({
605
+ where: { id },
606
+ data: {
607
+ description: parsed.description,
608
+ metadata: JSON.stringify(parsed.metadata)
609
+ }
610
+ });
611
+ return updated;
612
+ }
613
+ async upgradeSkill(id) {
614
+ const skill = await this.getSkillById(id);
615
+ if (skill.source !== "git" || !skill.sourceUrl) {
616
+ throw new AppError(ErrorCode.VALIDATION_ERROR, "Cannot upgrade non-git skill", 400);
617
+ }
618
+ let tempPath = null;
619
+ let tempSkillPath = null;
620
+ try {
621
+ const { repoUrl, subdir } = parseGitUrl(skill.sourceUrl);
622
+ tempPath = join3(TEMP_DIR, `upgrade-${Date.now()}`);
623
+ await mkdir(tempPath, { recursive: true });
624
+ const git = simpleGit();
625
+ console.log(`Cloning ${repoUrl} to temp for upgrade...`);
626
+ await git.clone(repoUrl, tempPath);
627
+ let commitHash = "";
628
+ try {
629
+ commitHash = await simpleGit(tempPath).revparse(["HEAD"]);
630
+ } catch (e) {
631
+ console.warn("Failed to capture commit hash during upgrade:", e);
632
+ }
633
+ let sourcePath = tempPath;
634
+ if (subdir) {
635
+ sourcePath = join3(tempPath, subdir);
636
+ if (!existsSync3(sourcePath)) {
637
+ throw new AppError(ErrorCode.GIT_ERROR, `Subdirectory "${subdir}" not found`, 400);
638
+ }
639
+ }
640
+ const hasSkillMd = existsSync3(join3(sourcePath, "SKILL.md"));
641
+ const hasSkillJson = existsSync3(join3(sourcePath, "skill.json"));
642
+ const hasPackageJson = existsSync3(join3(sourcePath, "package.json"));
643
+ if (!hasSkillMd && !hasSkillJson && !hasPackageJson) {
644
+ throw new AppError(ErrorCode.VALIDATION_ERROR, "Invalid skill structure in new version", 400);
645
+ }
646
+ const files = await import("fs/promises").then((fs) => fs.readdir(skill.storagePath));
647
+ for (const file of files) {
648
+ await rm(join3(skill.storagePath, file), { recursive: true, force: true });
649
+ }
650
+ await cp(sourcePath, skill.storagePath, { recursive: true });
651
+ const parsed = await this.parseSkillMetadata(skill.storagePath);
652
+ const updatedSkill = await prisma.skill.update({
653
+ where: { id },
654
+ data: {
655
+ commitHash,
656
+ updateAvailable: false,
657
+ lastUpdateCheck: /* @__PURE__ */ new Date(),
658
+ installDate: /* @__PURE__ */ new Date(),
659
+ // Considering upgrade as reinstall? Or user prefer generic updatedAt
660
+ // Keeping installDate as "last installed/upgraded"
661
+ description: parsed.description,
662
+ metadata: JSON.stringify(parsed.metadata)
663
+ }
664
+ });
665
+ return updatedSkill;
666
+ } catch (error) {
667
+ console.error("Upgrade error:", error);
668
+ throw new AppError(ErrorCode.GIT_ERROR, `Failed to upgrade skill: ${error.message}`, 500);
669
+ } finally {
670
+ if (tempPath) await rm(tempPath, { recursive: true, force: true }).catch(() => {
671
+ });
672
+ }
673
+ }
674
+ };
675
+ skillService = new SkillService();
753
676
  }
754
- };
755
- var skillService = new SkillService();
677
+ });
756
678
 
757
679
  // src/services/workspaceService.ts
758
- import { join as join3 } from "path";
680
+ import { join as join4 } from "path";
759
681
  import { homedir } from "os";
760
682
  import { symlink, unlink as unlink2, rm as rm2, mkdir as mkdir2, appendFile, readFile as readFile2 } from "fs/promises";
761
- import { existsSync as existsSync3, lstatSync } from "fs";
683
+ import { existsSync as existsSync4, lstatSync } from "fs";
762
684
  function computeSkillsPath(projectPath, type, scope) {
763
685
  const pathConfig = WORKSPACE_SKILLS_PATHS[type];
764
686
  if (scope === "global") {
765
687
  return pathConfig.global.replace("~", homedir());
766
688
  }
767
- return join3(projectPath, pathConfig.project);
689
+ return join4(projectPath, pathConfig.project);
768
690
  }
769
- var WorkspaceService = class {
770
- /**
771
- * Helper to get expected skills path for a project
772
- */
773
- getSkillsPath(projectPath, type, scope) {
774
- return computeSkillsPath(projectPath, type, scope);
775
- }
776
- /**
777
- * Sync database links with filesystem state
778
- * Removes links from DB if the symlink is missing from the workspace
779
- */
780
- async syncLinks(workspace) {
781
- if (!workspace.linkedSkills || workspace.linkedSkills.length === 0) return;
782
- for (const link of workspace.linkedSkills) {
783
- if (!link.skill) continue;
784
- const linkPath = join3(workspace.path, link.skill.name);
785
- if (!existsSync3(linkPath)) {
786
- console.log(`Link missing for skill "${link.skill.name}" in workspace "${workspace.name}". Removing from DB.`);
787
- try {
788
- await prisma.skillWorkspace.delete({
789
- where: { id: link.id }
790
- });
791
- workspace.linkedSkills = workspace.linkedSkills.filter((l) => l.id !== link.id);
792
- } catch (err) {
793
- console.error(`Failed to sync link for ${link.skill.name}:`, err);
691
+ var WorkspaceService, workspaceService;
692
+ var init_workspaceService = __esm({
693
+ "src/services/workspaceService.ts"() {
694
+ "use strict";
695
+ init_db();
696
+ init_errorHandler();
697
+ init_dist();
698
+ WorkspaceService = class {
699
+ /**
700
+ * Helper to get expected skills path for a project
701
+ */
702
+ getSkillsPath(projectPath, type, scope) {
703
+ return computeSkillsPath(projectPath, type, scope);
704
+ }
705
+ /**
706
+ * Sync database links with filesystem state
707
+ * Removes links from DB if the symlink is missing from the workspace
708
+ */
709
+ async syncLinks(workspace) {
710
+ if (!workspace.linkedSkills || workspace.linkedSkills.length === 0) return;
711
+ for (const link of workspace.linkedSkills) {
712
+ if (!link.skill) continue;
713
+ const linkPath = join4(workspace.path, link.skill.name);
714
+ if (!existsSync4(linkPath)) {
715
+ console.log(`Link missing for skill "${link.skill.name}" in workspace "${workspace.name}". Removing from DB.`);
716
+ try {
717
+ await prisma.skillWorkspace.delete({
718
+ where: { id: link.id }
719
+ });
720
+ workspace.linkedSkills = workspace.linkedSkills.filter((l) => l.id !== link.id);
721
+ } catch (err) {
722
+ console.error(`Failed to sync link for ${link.skill.name}:`, err);
723
+ }
724
+ }
794
725
  }
795
726
  }
796
- }
797
- }
798
- async findWorkspaceByPath(path) {
799
- const workspace = await prisma.workspace.findUnique({
800
- where: { path },
801
- include: {
802
- linkedSkills: {
727
+ async findWorkspaceByPath(path) {
728
+ const workspace = await prisma.workspace.findUnique({
729
+ where: { path },
803
730
  include: {
804
- skill: true
731
+ linkedSkills: {
732
+ include: {
733
+ skill: true
734
+ }
735
+ }
805
736
  }
806
- }
737
+ });
738
+ return workspace;
807
739
  }
808
- });
809
- return workspace;
810
- }
811
- async getAllWorkspaces() {
812
- const workspaces = await prisma.workspace.findMany({
813
- include: {
814
- linkedSkills: {
740
+ async getAllWorkspaces() {
741
+ const workspaces = await prisma.workspace.findMany({
815
742
  include: {
816
- skill: true
743
+ linkedSkills: {
744
+ include: {
745
+ skill: true
746
+ }
747
+ }
748
+ },
749
+ orderBy: {
750
+ createdAt: "desc"
817
751
  }
818
- }
819
- },
820
- orderBy: {
821
- createdAt: "desc"
752
+ });
753
+ await Promise.all(workspaces.map((w) => this.syncLinks(w)));
754
+ return workspaces.map((workspace) => ({
755
+ ...workspace,
756
+ isPathValid: existsSync4(workspace.path)
757
+ }));
822
758
  }
823
- });
824
- await Promise.all(workspaces.map((w) => this.syncLinks(w)));
825
- return workspaces.map((workspace) => ({
826
- ...workspace,
827
- isPathValid: existsSync3(workspace.path)
828
- }));
829
- }
830
- async getWorkspaceById(id) {
831
- const workspace = await prisma.workspace.findUnique({
832
- where: { id },
833
- include: {
834
- linkedSkills: {
759
+ async getWorkspaceById(id) {
760
+ const workspace = await prisma.workspace.findUnique({
761
+ where: { id },
835
762
  include: {
836
- skill: true
763
+ linkedSkills: {
764
+ include: {
765
+ skill: true
766
+ }
767
+ }
837
768
  }
769
+ });
770
+ if (!workspace) {
771
+ throw new AppError(ErrorCode.NOT_FOUND, "Workspace not found", 404);
838
772
  }
773
+ await this.syncLinks(workspace);
774
+ return {
775
+ ...workspace,
776
+ isPathValid: existsSync4(workspace.path)
777
+ };
839
778
  }
840
- });
841
- if (!workspace) {
842
- throw new AppError(ErrorCode.NOT_FOUND, "Workspace not found", 404);
843
- }
844
- await this.syncLinks(workspace);
845
- return {
846
- ...workspace,
847
- isPathValid: existsSync3(workspace.path)
848
- };
849
- }
850
- async createWorkspace(name, projectPath, type, scope) {
851
- const skillsPath = computeSkillsPath(projectPath, type, scope);
852
- if (scope === "project" && projectPath && !existsSync3(projectPath)) {
853
- throw new AppError(ErrorCode.FILE_SYSTEM_ERROR, `Project path does not exist: ${projectPath}`, 400);
854
- }
855
- if (!existsSync3(skillsPath)) {
856
- try {
857
- await mkdir2(skillsPath, { recursive: true });
858
- } catch (err) {
859
- throw new AppError(
860
- ErrorCode.FILE_SYSTEM_ERROR,
861
- `Failed to create skills directory: ${err.message}`,
862
- 500
863
- );
864
- }
865
- }
866
- const existingWorkspace = await prisma.workspace.findUnique({
867
- where: { path: skillsPath }
868
- });
869
- if (existingWorkspace) {
870
- throw new AppError(ErrorCode.ALREADY_EXISTS, "Workspace already exists at this path", 409);
871
- }
872
- const workspace = await prisma.workspace.create({
873
- data: {
874
- name,
875
- path: skillsPath,
876
- type,
877
- scope
878
- }
879
- });
880
- if (scope === "project" && projectPath && existsSync3(projectPath)) {
881
- try {
882
- const gitignorePath = join3(projectPath, ".gitignore");
883
- const relativeSkillsPath = WORKSPACE_SKILLS_PATHS[type].project;
884
- let shouldAppend = false;
885
- if (existsSync3(gitignorePath)) {
886
- const content = await readFile2(gitignorePath, "utf-8");
887
- if (!content.includes(relativeSkillsPath)) {
888
- shouldAppend = true;
779
+ async createWorkspace(name, projectPath, type, scope) {
780
+ const skillsPath = computeSkillsPath(projectPath, type, scope);
781
+ if (scope === "project" && projectPath && !existsSync4(projectPath)) {
782
+ throw new AppError(ErrorCode.FILE_SYSTEM_ERROR, `Project path does not exist: ${projectPath}`, 400);
783
+ }
784
+ if (!existsSync4(skillsPath)) {
785
+ try {
786
+ await mkdir2(skillsPath, { recursive: true });
787
+ } catch (err) {
788
+ throw new AppError(
789
+ ErrorCode.FILE_SYSTEM_ERROR,
790
+ `Failed to create skills directory: ${err.message}`,
791
+ 500
792
+ );
889
793
  }
890
- } else {
891
- shouldAppend = true;
892
794
  }
893
- if (shouldAppend) {
894
- const ignoreEntry = `
795
+ const existingWorkspace = await prisma.workspace.findUnique({
796
+ where: { path: skillsPath }
797
+ });
798
+ if (existingWorkspace) {
799
+ throw new AppError(ErrorCode.ALREADY_EXISTS, "Workspace already exists at this path", 409);
800
+ }
801
+ const workspace = await prisma.workspace.create({
802
+ data: {
803
+ name,
804
+ path: skillsPath,
805
+ type,
806
+ scope
807
+ }
808
+ });
809
+ if (scope === "project" && projectPath && existsSync4(projectPath)) {
810
+ try {
811
+ const gitignorePath = join4(projectPath, ".gitignore");
812
+ const relativeSkillsPath = WORKSPACE_SKILLS_PATHS[type].project;
813
+ let shouldAppend = false;
814
+ if (existsSync4(gitignorePath)) {
815
+ const content = await readFile2(gitignorePath, "utf-8");
816
+ if (!content.includes(relativeSkillsPath)) {
817
+ shouldAppend = true;
818
+ }
819
+ } else {
820
+ shouldAppend = true;
821
+ }
822
+ if (shouldAppend) {
823
+ const ignoreEntry = `
895
824
 
896
825
  # SkillVerse
897
826
  ${relativeSkillsPath}/
898
827
  `;
899
- await appendFile(gitignorePath, ignoreEntry);
900
- console.log(`Added ${relativeSkillsPath}/ to .gitignore in ${projectPath}`);
828
+ await appendFile(gitignorePath, ignoreEntry);
829
+ console.log(`Added ${relativeSkillsPath}/ to .gitignore in ${projectPath}`);
830
+ }
831
+ } catch (err) {
832
+ console.warn("Failed to update .gitignore:", err);
833
+ }
901
834
  }
902
- } catch (err) {
903
- console.warn("Failed to update .gitignore:", err);
835
+ return {
836
+ ...workspace,
837
+ isPathValid: true
838
+ };
904
839
  }
905
- }
906
- return {
907
- ...workspace,
908
- isPathValid: true
909
- };
910
- }
911
- async updateWorkspace(id, data) {
912
- await this.getWorkspaceById(id);
913
- const workspace = await prisma.workspace.update({
914
- where: { id },
915
- data
916
- });
917
- return {
918
- ...workspace,
919
- isPathValid: existsSync3(workspace.path)
920
- };
921
- }
922
- async deleteWorkspace(id) {
923
- const workspace = await this.getWorkspaceById(id);
924
- const links = await prisma.skillWorkspace.findMany({
925
- where: { workspaceId: id },
926
- include: { skill: true }
927
- });
928
- for (const link of links) {
929
- try {
930
- const linkPath = join3(workspace.path, link.skill.name);
931
- if (existsSync3(linkPath)) {
932
- await rm2(linkPath, { recursive: true, force: true });
933
- }
934
- } catch (error) {
935
- console.error(`Failed to remove symlink for ${link.skill.name}:`, error);
840
+ async updateWorkspace(id, data) {
841
+ await this.getWorkspaceById(id);
842
+ const workspace = await prisma.workspace.update({
843
+ where: { id },
844
+ data
845
+ });
846
+ return {
847
+ ...workspace,
848
+ isPathValid: existsSync4(workspace.path)
849
+ };
936
850
  }
937
- }
938
- await prisma.workspace.delete({ where: { id } });
939
- return { success: true, message: "Workspace deleted successfully" };
940
- }
941
- async linkSkillToWorkspace(skillId, workspaceId) {
942
- const skill = await prisma.skill.findUnique({ where: { id: skillId } });
943
- const workspace = await prisma.workspace.findUnique({ where: { id: workspaceId } });
944
- if (!skill) {
945
- throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
946
- }
947
- if (!workspace) {
948
- throw new AppError(ErrorCode.NOT_FOUND, "Workspace not found", 404);
949
- }
950
- if (!existsSync3(workspace.path)) {
951
- throw new AppError(
952
- ErrorCode.FILE_SYSTEM_ERROR,
953
- `Workspace skills directory does not exist: ${workspace.path}. The folder may have been deleted. Please recreate it or remove this workspace.`,
954
- 400
955
- );
956
- }
957
- const existingLink = await prisma.skillWorkspace.findFirst({
958
- where: {
959
- skillId,
960
- workspaceId
851
+ async deleteWorkspace(id) {
852
+ const workspace = await this.getWorkspaceById(id);
853
+ const links = await prisma.skillWorkspace.findMany({
854
+ where: { workspaceId: id },
855
+ include: { skill: true }
856
+ });
857
+ for (const link of links) {
858
+ try {
859
+ const linkPath = join4(workspace.path, link.skill.name);
860
+ if (existsSync4(linkPath)) {
861
+ await rm2(linkPath, { recursive: true, force: true });
862
+ }
863
+ } catch (error) {
864
+ console.error(`Failed to remove symlink for ${link.skill.name}:`, error);
865
+ }
866
+ }
867
+ await prisma.workspace.delete({ where: { id } });
868
+ return { success: true, message: "Workspace deleted successfully" };
961
869
  }
962
- });
963
- if (existingLink) {
964
- throw new AppError(ErrorCode.ALREADY_EXISTS, "Skill is already linked to this workspace", 409);
965
- }
966
- const targetPath = join3(workspace.path, skill.name);
967
- try {
968
- if (existsSync3(targetPath)) {
969
- const stats = lstatSync(targetPath);
970
- if (stats.isSymbolicLink()) {
971
- await unlink2(targetPath);
972
- } else {
870
+ async linkSkillToWorkspace(skillId, workspaceId) {
871
+ const skill = await prisma.skill.findUnique({ where: { id: skillId } });
872
+ const workspace = await prisma.workspace.findUnique({ where: { id: workspaceId } });
873
+ if (!skill) {
874
+ throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
875
+ }
876
+ if (!workspace) {
877
+ throw new AppError(ErrorCode.NOT_FOUND, "Workspace not found", 404);
878
+ }
879
+ if (!existsSync4(workspace.path)) {
973
880
  throw new AppError(
974
881
  ErrorCode.FILE_SYSTEM_ERROR,
975
- `A file or directory already exists at ${targetPath}`,
976
- 409
882
+ `Workspace skills directory does not exist: ${workspace.path}. The folder may have been deleted. Please recreate it or remove this workspace.`,
883
+ 400
977
884
  );
978
885
  }
979
- }
980
- await symlink(skill.storagePath, targetPath, "dir");
981
- const link = await prisma.skillWorkspace.create({
982
- data: {
983
- skillId,
984
- workspaceId
985
- },
986
- include: {
987
- skill: true,
988
- workspace: true
886
+ const existingLink = await prisma.skillWorkspace.findFirst({
887
+ where: {
888
+ skillId,
889
+ workspaceId
890
+ }
891
+ });
892
+ if (existingLink) {
893
+ throw new AppError(ErrorCode.ALREADY_EXISTS, "Skill is already linked to this workspace", 409);
989
894
  }
990
- });
991
- return link;
992
- } catch (error) {
993
- if (error instanceof AppError) throw error;
994
- console.error("Symlink creation error:", error);
995
- throw new AppError(
996
- ErrorCode.SYMLINK_ERROR,
997
- `Failed to create symlink: ${error.message}`,
998
- 500,
999
- error
1000
- );
1001
- }
1002
- }
1003
- async unlinkSkillFromWorkspace(skillId, workspaceId) {
1004
- const link = await prisma.skillWorkspace.findFirst({
1005
- where: {
1006
- skillId,
1007
- workspaceId
1008
- },
1009
- include: {
1010
- skill: true,
1011
- workspace: true
1012
- }
1013
- });
1014
- if (!link) {
1015
- throw new AppError(ErrorCode.NOT_FOUND, "Link not found", 404);
1016
- }
1017
- const linkPath = join3(link.workspace.path, link.skill.name);
1018
- try {
1019
- if (existsSync3(linkPath)) {
1020
- await rm2(linkPath, { recursive: true, force: true });
1021
- }
1022
- await prisma.skillWorkspace.delete({
1023
- where: { id: link.id }
1024
- });
1025
- return { success: true, message: "Skill unlinked successfully" };
1026
- } catch (error) {
1027
- console.error("Unlink error:", error);
1028
- throw new AppError(
1029
- ErrorCode.FILE_SYSTEM_ERROR,
1030
- `Failed to unlink skill: ${error.message}`,
1031
- 500,
1032
- error
1033
- );
1034
- }
1035
- }
1036
- /**
1037
- * Detect existing skills in a workspace skills directory
1038
- * Returns list of skill names that could be migrated
1039
- */
1040
- async detectExistingSkills(skillsPath) {
1041
- if (!existsSync3(skillsPath)) {
1042
- return [];
1043
- }
1044
- const { readdir: readdir2, stat: stat2, readFile: readFile3 } = await import("fs/promises");
1045
- const items = await readdir2(skillsPath);
1046
- const existingSkills = [];
1047
- for (const item of items) {
1048
- if (item.startsWith(".")) continue;
1049
- const itemPath = join3(skillsPath, item);
1050
- try {
1051
- const itemStat = await stat2(itemPath);
1052
- const lstats = lstatSync(itemPath);
1053
- if (lstats.isSymbolicLink()) continue;
1054
- if (itemStat.isDirectory()) {
1055
- const hasSkillMd = existsSync3(join3(itemPath, "SKILL.md"));
1056
- const hasSkillJson = existsSync3(join3(itemPath, "skill.json"));
1057
- const hasPackageJson = existsSync3(join3(itemPath, "package.json"));
1058
- if (hasSkillMd || hasSkillJson || hasPackageJson) {
1059
- existingSkills.push({
1060
- name: item,
1061
- hasSkillMd,
1062
- path: itemPath
1063
- });
895
+ const targetPath = join4(workspace.path, skill.name);
896
+ try {
897
+ if (existsSync4(targetPath)) {
898
+ const stats = lstatSync(targetPath);
899
+ if (stats.isSymbolicLink()) {
900
+ await unlink2(targetPath);
901
+ } else {
902
+ throw new AppError(
903
+ ErrorCode.FILE_SYSTEM_ERROR,
904
+ `A file or directory already exists at ${targetPath}`,
905
+ 409
906
+ );
907
+ }
1064
908
  }
909
+ await symlink(skill.storagePath, targetPath, "dir");
910
+ const link = await prisma.skillWorkspace.create({
911
+ data: {
912
+ skillId,
913
+ workspaceId
914
+ },
915
+ include: {
916
+ skill: true,
917
+ workspace: true
918
+ }
919
+ });
920
+ return link;
921
+ } catch (error) {
922
+ if (error instanceof AppError) throw error;
923
+ console.error("Symlink creation error:", error);
924
+ throw new AppError(
925
+ ErrorCode.SYMLINK_ERROR,
926
+ `Failed to create symlink: ${error.message}`,
927
+ 500,
928
+ error
929
+ );
1065
930
  }
1066
- } catch (error) {
1067
- console.warn(`Failed to check ${itemPath}:`, error);
1068
931
  }
1069
- }
1070
- return existingSkills;
1071
- }
1072
- /**
1073
- * Migrate existing skills from workspace to unified storage
1074
- * 1. Move skills to ~/.skillverse/skills/
1075
- * 2. Register in database
1076
- * 3. Create symlinks back to workspace
1077
- */
1078
- async migrateExistingSkills(workspaceId, skillNames) {
1079
- const workspace = await this.getWorkspaceById(workspaceId);
1080
- const SKILLVERSE_HOME3 = process.env.SKILLVERSE_HOME || join3(homedir(), ".skillverse");
1081
- const SKILLS_DIR2 = process.env.SKILLS_DIR || join3(SKILLVERSE_HOME3, "skills");
1082
- if (!existsSync3(SKILLS_DIR2)) {
1083
- await mkdir2(SKILLS_DIR2, { recursive: true });
1084
- }
1085
- const { rename, readFile: readFile3, cp: cp2 } = await import("fs/promises");
1086
- const migrated = [];
1087
- const errors = [];
1088
- for (const skillName of skillNames) {
1089
- const sourcePath = join3(workspace.path, skillName);
1090
- const targetPath = join3(SKILLS_DIR2, skillName);
1091
- try {
1092
- if (!existsSync3(sourcePath)) {
1093
- errors.push(`${skillName}: Source path not found`);
1094
- continue;
1095
- }
1096
- const lstats = lstatSync(sourcePath);
1097
- if (lstats.isSymbolicLink()) {
1098
- errors.push(`${skillName}: Already a symlink, skipping`);
1099
- continue;
932
+ async unlinkSkillFromWorkspace(skillId, workspaceId) {
933
+ const link = await prisma.skillWorkspace.findFirst({
934
+ where: {
935
+ skillId,
936
+ workspaceId
937
+ },
938
+ include: {
939
+ skill: true,
940
+ workspace: true
941
+ }
942
+ });
943
+ if (!link) {
944
+ throw new AppError(ErrorCode.NOT_FOUND, "Link not found", 404);
1100
945
  }
1101
- if (existsSync3(targetPath)) {
1102
- errors.push(`${skillName}: Already exists in unified storage`);
1103
- continue;
946
+ const linkPath = join4(link.workspace.path, link.skill.name);
947
+ try {
948
+ if (existsSync4(linkPath)) {
949
+ await rm2(linkPath, { recursive: true, force: true });
950
+ }
951
+ await prisma.skillWorkspace.delete({
952
+ where: { id: link.id }
953
+ });
954
+ return { success: true, message: "Skill unlinked successfully" };
955
+ } catch (error) {
956
+ console.error("Unlink error:", error);
957
+ throw new AppError(
958
+ ErrorCode.FILE_SYSTEM_ERROR,
959
+ `Failed to unlink skill: ${error.message}`,
960
+ 500,
961
+ error
962
+ );
1104
963
  }
1105
- const existingSkill = await prisma.skill.findUnique({
1106
- where: { name: skillName }
1107
- });
1108
- if (existingSkill) {
1109
- errors.push(`${skillName}: Already registered in database`);
1110
- continue;
964
+ }
965
+ /**
966
+ * Detect existing skills in a workspace skills directory
967
+ * Returns list of skill names that could be migrated
968
+ */
969
+ async detectExistingSkills(skillsPath) {
970
+ if (!existsSync4(skillsPath)) {
971
+ return [];
1111
972
  }
1112
- const skillMdPath = join3(sourcePath, "SKILL.md");
1113
- let description = "";
1114
- let metadata = {};
1115
- if (existsSync3(skillMdPath)) {
973
+ const { readdir: readdir2, stat: stat2, readFile: readFile3 } = await import("fs/promises");
974
+ const items = await readdir2(skillsPath);
975
+ const existingSkills = [];
976
+ for (const item of items) {
977
+ if (item.startsWith(".")) continue;
978
+ const itemPath = join4(skillsPath, item);
1116
979
  try {
1117
- const matter2 = await import("gray-matter");
1118
- const fileContent = await readFile3(skillMdPath, "utf-8");
1119
- const parsed = matter2.default(fileContent);
1120
- description = parsed.data.description || "";
1121
- metadata = parsed.data;
1122
- } catch (e) {
1123
- console.warn(`Failed to parse SKILL.md for ${skillName}:`, e);
980
+ const itemStat = await stat2(itemPath);
981
+ const lstats = lstatSync(itemPath);
982
+ if (lstats.isSymbolicLink()) continue;
983
+ if (itemStat.isDirectory()) {
984
+ const hasSkillMd = existsSync4(join4(itemPath, "SKILL.md"));
985
+ const hasSkillJson = existsSync4(join4(itemPath, "skill.json"));
986
+ const hasPackageJson = existsSync4(join4(itemPath, "package.json"));
987
+ if (hasSkillMd || hasSkillJson || hasPackageJson) {
988
+ existingSkills.push({
989
+ name: item,
990
+ hasSkillMd,
991
+ path: itemPath
992
+ });
993
+ }
994
+ }
995
+ } catch (error) {
996
+ console.warn(`Failed to check ${itemPath}:`, error);
1124
997
  }
1125
998
  }
1126
- await cp2(sourcePath, targetPath, { recursive: true });
1127
- await rm2(sourcePath, { recursive: true, force: true });
1128
- const skill = await prisma.skill.create({
1129
- data: {
1130
- name: skillName,
1131
- source: "local",
1132
- description,
1133
- storagePath: targetPath,
1134
- metadata: JSON.stringify(metadata)
1135
- }
1136
- });
1137
- await symlink(targetPath, sourcePath, "dir");
1138
- await prisma.skillWorkspace.create({
1139
- data: {
1140
- skillId: skill.id,
1141
- workspaceId: workspace.id
999
+ return existingSkills;
1000
+ }
1001
+ /**
1002
+ * Migrate existing skills from workspace to unified storage
1003
+ * 1. Move skills to ~/.skillverse/skills/
1004
+ * 2. Register in database
1005
+ * 3. Create symlinks back to workspace
1006
+ */
1007
+ async migrateExistingSkills(workspaceId, skillNames) {
1008
+ const workspace = await this.getWorkspaceById(workspaceId);
1009
+ const SKILLVERSE_HOME3 = process.env.SKILLVERSE_HOME || join4(homedir(), ".skillverse");
1010
+ const SKILLS_DIR2 = process.env.SKILLS_DIR || join4(SKILLVERSE_HOME3, "skills");
1011
+ if (!existsSync4(SKILLS_DIR2)) {
1012
+ await mkdir2(SKILLS_DIR2, { recursive: true });
1013
+ }
1014
+ const { rename, readFile: readFile3, cp: cp2 } = await import("fs/promises");
1015
+ const migrated = [];
1016
+ const errors = [];
1017
+ for (const skillName of skillNames) {
1018
+ const sourcePath = join4(workspace.path, skillName);
1019
+ const targetPath = join4(SKILLS_DIR2, skillName);
1020
+ try {
1021
+ if (!existsSync4(sourcePath)) {
1022
+ errors.push(`${skillName}: Source path not found`);
1023
+ continue;
1024
+ }
1025
+ const lstats = lstatSync(sourcePath);
1026
+ if (lstats.isSymbolicLink()) {
1027
+ errors.push(`${skillName}: Already a symlink, skipping`);
1028
+ continue;
1029
+ }
1030
+ if (existsSync4(targetPath)) {
1031
+ errors.push(`${skillName}: Already exists in unified storage`);
1032
+ continue;
1033
+ }
1034
+ const existingSkill = await prisma.skill.findUnique({
1035
+ where: { name: skillName }
1036
+ });
1037
+ if (existingSkill) {
1038
+ errors.push(`${skillName}: Already registered in database`);
1039
+ continue;
1040
+ }
1041
+ const skillMdPath = join4(sourcePath, "SKILL.md");
1042
+ let description = "";
1043
+ let metadata = {};
1044
+ if (existsSync4(skillMdPath)) {
1045
+ try {
1046
+ const matter2 = await import("gray-matter");
1047
+ const fileContent = await readFile3(skillMdPath, "utf-8");
1048
+ const parsed = matter2.default(fileContent);
1049
+ description = parsed.data.description || "";
1050
+ metadata = parsed.data;
1051
+ } catch (e) {
1052
+ console.warn(`Failed to parse SKILL.md for ${skillName}:`, e);
1053
+ }
1054
+ }
1055
+ await cp2(sourcePath, targetPath, { recursive: true });
1056
+ await rm2(sourcePath, { recursive: true, force: true });
1057
+ const skill = await prisma.skill.create({
1058
+ data: {
1059
+ name: skillName,
1060
+ source: "local",
1061
+ description,
1062
+ storagePath: targetPath,
1063
+ metadata: JSON.stringify(metadata)
1064
+ }
1065
+ });
1066
+ await symlink(targetPath, sourcePath, "dir");
1067
+ await prisma.skillWorkspace.create({
1068
+ data: {
1069
+ skillId: skill.id,
1070
+ workspaceId: workspace.id
1071
+ }
1072
+ });
1073
+ migrated.push(skillName);
1074
+ console.log(`\u2705 Migrated skill "${skillName}" to unified storage`);
1075
+ } catch (error) {
1076
+ console.error(`Failed to migrate ${skillName}:`, error);
1077
+ errors.push(`${skillName}: ${error.message}`);
1142
1078
  }
1143
- });
1144
- migrated.push(skillName);
1145
- console.log(`\u2705 Migrated skill "${skillName}" to unified storage`);
1146
- } catch (error) {
1147
- console.error(`Failed to migrate ${skillName}:`, error);
1148
- errors.push(`${skillName}: ${error.message}`);
1079
+ }
1080
+ return {
1081
+ success: errors.length === 0,
1082
+ migrated,
1083
+ errors
1084
+ };
1149
1085
  }
1150
- }
1151
- return {
1152
- success: errors.length === 0,
1153
- migrated,
1154
- errors
1155
1086
  };
1087
+ workspaceService = new WorkspaceService();
1156
1088
  }
1157
- };
1158
- var workspaceService = new WorkspaceService();
1089
+ });
1159
1090
 
1160
1091
  // src/routes/skills.ts
1161
- var router = Router();
1162
- var TEMP_DIR2 = process.env.TEMP_DIR || join4(process.env.HOME || "", ".skillverse", "temp");
1163
- if (!existsSync4(TEMP_DIR2)) {
1164
- mkdir3(TEMP_DIR2, { recursive: true });
1165
- }
1166
- var storage = multer.diskStorage({
1167
- destination: (req, file, cb) => {
1168
- cb(null, TEMP_DIR2);
1169
- },
1170
- filename: (req, file, cb) => {
1171
- const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
1172
- cb(null, uniqueSuffix + "-" + file.originalname);
1173
- }
1174
- });
1175
- var upload = multer({
1176
- storage,
1177
- limits: {
1178
- fileSize: 100 * 1024 * 1024
1179
- // 100MB limit
1180
- },
1181
- fileFilter: (req, file, cb) => {
1182
- if (file.mimetype === "application/zip" || file.originalname.endsWith(".zip")) {
1183
- cb(null, true);
1184
- } else {
1185
- cb(new Error("Only ZIP files are allowed"));
1186
- }
1187
- }
1188
- });
1189
- router.get("/", async (req, res, next) => {
1190
- try {
1191
- const skills = await skillService.getAllSkills();
1192
- res.json({
1193
- success: true,
1194
- data: skills
1092
+ import { Router } from "express";
1093
+ import multer from "multer";
1094
+ import { join as join5 } from "path";
1095
+ import { existsSync as existsSync5 } from "fs";
1096
+ import { mkdir as mkdir3, rm as rm3 } from "fs/promises";
1097
+ var router, TEMP_DIR2, storage, upload, skills_default;
1098
+ var init_skills = __esm({
1099
+ "src/routes/skills.ts"() {
1100
+ "use strict";
1101
+ init_skillService();
1102
+ init_workspaceService();
1103
+ router = Router();
1104
+ TEMP_DIR2 = process.env.TEMP_DIR || join5(process.env.HOME || "", ".skillverse", "temp");
1105
+ if (!existsSync5(TEMP_DIR2)) {
1106
+ mkdir3(TEMP_DIR2, { recursive: true });
1107
+ }
1108
+ storage = multer.diskStorage({
1109
+ destination: (req, file, cb) => {
1110
+ cb(null, TEMP_DIR2);
1111
+ },
1112
+ filename: (req, file, cb) => {
1113
+ const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
1114
+ cb(null, uniqueSuffix + "-" + file.originalname);
1115
+ }
1195
1116
  });
1196
- } catch (error) {
1197
- next(error);
1198
- }
1199
- });
1200
- router.get("/:id", async (req, res, next) => {
1201
- try {
1202
- const skill = await skillService.getSkillById(req.params.id);
1203
- res.json({
1204
- success: true,
1205
- data: skill
1117
+ upload = multer({
1118
+ storage,
1119
+ limits: {
1120
+ fileSize: 100 * 1024 * 1024
1121
+ // 100MB limit
1122
+ },
1123
+ fileFilter: (req, file, cb) => {
1124
+ if (file.mimetype === "application/zip" || file.originalname.endsWith(".zip")) {
1125
+ cb(null, true);
1126
+ } else {
1127
+ cb(new Error("Only ZIP files are allowed"));
1128
+ }
1129
+ }
1206
1130
  });
1207
- } catch (error) {
1208
- next(error);
1209
- }
1210
- });
1211
- router.post("/from-git", async (req, res, next) => {
1212
- try {
1213
- const { gitUrl, description } = req.body;
1214
- if (!gitUrl) {
1215
- return res.status(400).json({
1216
- success: false,
1217
- error: "gitUrl is required"
1218
- });
1219
- }
1220
- const skill = await skillService.createSkillFromGit(gitUrl, description);
1221
- res.status(201).json({
1222
- success: true,
1223
- data: skill,
1224
- message: "Skill created successfully from Git repository"
1131
+ router.get("/", async (req, res, next) => {
1132
+ try {
1133
+ const skills = await skillService.getAllSkills();
1134
+ res.json({
1135
+ success: true,
1136
+ data: skills
1137
+ });
1138
+ } catch (error) {
1139
+ next(error);
1140
+ }
1225
1141
  });
1226
- } catch (error) {
1227
- next(error);
1228
- }
1229
- });
1230
- router.post(
1231
- "/from-local",
1232
- upload.single("file"),
1233
- async (req, res, next) => {
1234
- try {
1235
- const { name, description } = req.body;
1236
- const file = req.file;
1237
- if (!name) {
1238
- if (file) {
1239
- await rm3(file.path, { force: true });
1240
- }
1241
- return res.status(400).json({
1242
- success: false,
1243
- error: "name is required"
1142
+ router.get("/:id", async (req, res, next) => {
1143
+ try {
1144
+ const skill = await skillService.getSkillById(req.params.id);
1145
+ res.json({
1146
+ success: true,
1147
+ data: skill
1244
1148
  });
1149
+ } catch (error) {
1150
+ next(error);
1245
1151
  }
1246
- if (!file) {
1247
- return res.status(400).json({
1248
- success: false,
1249
- error: "ZIP file is required"
1152
+ });
1153
+ router.post("/from-git", async (req, res, next) => {
1154
+ try {
1155
+ const { gitUrl, description } = req.body;
1156
+ if (!gitUrl) {
1157
+ return res.status(400).json({
1158
+ success: false,
1159
+ error: "gitUrl is required"
1160
+ });
1161
+ }
1162
+ const skill = await skillService.createSkillFromGit(gitUrl, description);
1163
+ res.status(201).json({
1164
+ success: true,
1165
+ data: skill,
1166
+ message: "Skill created successfully from Git repository"
1250
1167
  });
1168
+ } catch (error) {
1169
+ next(error);
1251
1170
  }
1252
- const skill = await skillService.createSkillFromLocal(name, file.path, description);
1253
- await rm3(file.path, { force: true });
1254
- res.status(201).json({
1255
- success: true,
1256
- data: skill,
1257
- message: "Skill created successfully from local file"
1258
- });
1259
- } catch (error) {
1260
- if (req.file) {
1261
- await rm3(req.file.path, { force: true }).catch(() => {
1171
+ });
1172
+ router.post(
1173
+ "/from-local",
1174
+ upload.single("file"),
1175
+ async (req, res, next) => {
1176
+ try {
1177
+ const { name, description } = req.body;
1178
+ const file = req.file;
1179
+ if (!name) {
1180
+ if (file) {
1181
+ await rm3(file.path, { force: true });
1182
+ }
1183
+ return res.status(400).json({
1184
+ success: false,
1185
+ error: "name is required"
1186
+ });
1187
+ }
1188
+ if (!file) {
1189
+ return res.status(400).json({
1190
+ success: false,
1191
+ error: "ZIP file is required"
1192
+ });
1193
+ }
1194
+ const skill = await skillService.createSkillFromLocal(name, file.path, description);
1195
+ await rm3(file.path, { force: true });
1196
+ res.status(201).json({
1197
+ success: true,
1198
+ data: skill,
1199
+ message: "Skill created successfully from local file"
1200
+ });
1201
+ } catch (error) {
1202
+ if (req.file) {
1203
+ await rm3(req.file.path, { force: true }).catch(() => {
1204
+ });
1205
+ }
1206
+ next(error);
1207
+ }
1208
+ }
1209
+ );
1210
+ router.put("/:id", async (req, res, next) => {
1211
+ try {
1212
+ const { name, description, metadata } = req.body;
1213
+ const skill = await skillService.updateSkill(req.params.id, {
1214
+ name,
1215
+ description,
1216
+ metadata
1217
+ });
1218
+ res.json({
1219
+ success: true,
1220
+ data: skill,
1221
+ message: "Skill updated successfully"
1262
1222
  });
1223
+ } catch (error) {
1224
+ next(error);
1263
1225
  }
1264
- next(error);
1265
- }
1266
- }
1267
- );
1268
- router.put("/:id", async (req, res, next) => {
1269
- try {
1270
- const { name, description, metadata } = req.body;
1271
- const skill = await skillService.updateSkill(req.params.id, {
1272
- name,
1273
- description,
1274
- metadata
1275
1226
  });
1276
- res.json({
1277
- success: true,
1278
- data: skill,
1279
- message: "Skill updated successfully"
1227
+ router.delete("/:id", async (req, res, next) => {
1228
+ try {
1229
+ const removeFiles = req.query.removeFiles !== "false";
1230
+ const result = await skillService.deleteSkill(req.params.id, removeFiles);
1231
+ res.json(result);
1232
+ } catch (error) {
1233
+ next(error);
1234
+ }
1280
1235
  });
1281
- } catch (error) {
1282
- next(error);
1283
- }
1284
- });
1285
- router.delete("/:id", async (req, res, next) => {
1286
- try {
1287
- const removeFiles = req.query.removeFiles !== "false";
1288
- const result = await skillService.deleteSkill(req.params.id, removeFiles);
1289
- res.json(result);
1290
- } catch (error) {
1291
- next(error);
1292
- }
1293
- });
1294
- router.post("/:id/link", async (req, res, next) => {
1295
- try {
1296
- const { workspaceId } = req.body;
1297
- if (!workspaceId) {
1298
- return res.status(400).json({
1299
- success: false,
1300
- error: "workspaceId is required"
1301
- });
1302
- }
1303
- const link = await workspaceService.linkSkillToWorkspace(req.params.id, workspaceId);
1304
- res.status(201).json({
1305
- success: true,
1306
- data: link,
1307
- message: "Skill linked to workspace successfully"
1236
+ router.post("/:id/link", async (req, res, next) => {
1237
+ try {
1238
+ const { workspaceId } = req.body;
1239
+ if (!workspaceId) {
1240
+ return res.status(400).json({
1241
+ success: false,
1242
+ error: "workspaceId is required"
1243
+ });
1244
+ }
1245
+ const link = await workspaceService.linkSkillToWorkspace(req.params.id, workspaceId);
1246
+ res.status(201).json({
1247
+ success: true,
1248
+ data: link,
1249
+ message: "Skill linked to workspace successfully"
1250
+ });
1251
+ } catch (error) {
1252
+ next(error);
1253
+ }
1308
1254
  });
1309
- } catch (error) {
1310
- next(error);
1311
- }
1312
- });
1313
- router.delete(
1314
- "/:id/unlink/:workspaceId",
1315
- async (req, res, next) => {
1316
- try {
1317
- const result = await workspaceService.unlinkSkillFromWorkspace(
1318
- req.params.id,
1319
- req.params.workspaceId
1320
- );
1321
- res.json(result);
1322
- } catch (error) {
1323
- next(error);
1324
- }
1325
- }
1326
- );
1327
- router.get("/:id/check-update", async (req, res, next) => {
1328
- try {
1329
- const result = await skillService.checkForUpdate(req.params.id);
1330
- res.json({
1331
- success: true,
1332
- data: result
1255
+ router.delete(
1256
+ "/:id/unlink/:workspaceId",
1257
+ async (req, res, next) => {
1258
+ try {
1259
+ const result = await workspaceService.unlinkSkillFromWorkspace(
1260
+ req.params.id,
1261
+ req.params.workspaceId
1262
+ );
1263
+ res.json(result);
1264
+ } catch (error) {
1265
+ next(error);
1266
+ }
1267
+ }
1268
+ );
1269
+ router.get("/:id/check-update", async (req, res, next) => {
1270
+ try {
1271
+ const result = await skillService.checkForUpdate(req.params.id);
1272
+ res.json({
1273
+ success: true,
1274
+ data: result
1275
+ });
1276
+ } catch (error) {
1277
+ next(error);
1278
+ }
1333
1279
  });
1334
- } catch (error) {
1335
- next(error);
1336
- }
1337
- });
1338
- router.post("/:id/upgrade", async (req, res, next) => {
1339
- try {
1340
- const skill = await skillService.upgradeSkill(req.params.id);
1341
- res.json({
1342
- success: true,
1343
- data: skill,
1344
- message: "Skill upgraded successfully"
1280
+ router.post("/:id/upgrade", async (req, res, next) => {
1281
+ try {
1282
+ const skill = await skillService.upgradeSkill(req.params.id);
1283
+ res.json({
1284
+ success: true,
1285
+ data: skill,
1286
+ message: "Skill upgraded successfully"
1287
+ });
1288
+ } catch (error) {
1289
+ next(error);
1290
+ }
1345
1291
  });
1346
- } catch (error) {
1347
- next(error);
1348
- }
1349
- });
1350
- router.post("/check-updates", async (req, res, next) => {
1351
- try {
1352
- const { ids } = req.body;
1353
- const results = await skillService.checkUpdates(ids);
1354
- res.json({
1355
- success: true,
1356
- data: results
1292
+ router.post("/check-updates", async (req, res, next) => {
1293
+ try {
1294
+ const { ids } = req.body;
1295
+ const results = await skillService.checkUpdates(ids);
1296
+ res.json({
1297
+ success: true,
1298
+ data: results
1299
+ });
1300
+ } catch (error) {
1301
+ next(error);
1302
+ }
1357
1303
  });
1358
- } catch (error) {
1359
- next(error);
1360
- }
1361
- });
1362
- router.post("/:id/refresh-metadata", async (req, res, next) => {
1363
- try {
1364
- const skill = await skillService.refreshMetadata(req.params.id);
1365
- res.json({
1366
- success: true,
1367
- data: skill,
1368
- message: "Metadata refreshed successfully"
1304
+ router.post("/:id/refresh-metadata", async (req, res, next) => {
1305
+ try {
1306
+ const skill = await skillService.refreshMetadata(req.params.id);
1307
+ res.json({
1308
+ success: true,
1309
+ data: skill,
1310
+ message: "Metadata refreshed successfully"
1311
+ });
1312
+ } catch (error) {
1313
+ next(error);
1314
+ }
1369
1315
  });
1370
- } catch (error) {
1371
- next(error);
1372
- }
1373
- });
1374
- router.get("/:id/skill-md", async (req, res, next) => {
1375
- try {
1376
- const { readFile: readFile3 } = await import("fs/promises");
1377
- const skill = await skillService.getSkillById(req.params.id);
1378
- const skillMdPath = join4(skill.storagePath, "SKILL.md");
1379
- if (!existsSync4(skillMdPath)) {
1380
- return res.json({
1381
- success: true,
1382
- data: {
1383
- exists: false,
1384
- content: null
1316
+ router.get("/:id/skill-md", async (req, res, next) => {
1317
+ try {
1318
+ const { readFile: readFile3 } = await import("fs/promises");
1319
+ const skill = await skillService.getSkillById(req.params.id);
1320
+ const skillMdPath = join5(skill.storagePath, "SKILL.md");
1321
+ if (!existsSync5(skillMdPath)) {
1322
+ return res.json({
1323
+ success: true,
1324
+ data: {
1325
+ exists: false,
1326
+ content: null
1327
+ }
1328
+ });
1385
1329
  }
1386
- });
1387
- }
1388
- const content = await readFile3(skillMdPath, "utf-8");
1389
- res.json({
1390
- success: true,
1391
- data: {
1392
- exists: true,
1393
- content
1330
+ const content = await readFile3(skillMdPath, "utf-8");
1331
+ res.json({
1332
+ success: true,
1333
+ data: {
1334
+ exists: true,
1335
+ content
1336
+ }
1337
+ });
1338
+ } catch (error) {
1339
+ next(error);
1394
1340
  }
1395
1341
  });
1396
- } catch (error) {
1397
- next(error);
1342
+ skills_default = router;
1398
1343
  }
1399
1344
  });
1400
- var skills_default = router;
1401
1345
 
1402
1346
  // src/routes/workspaces.ts
1403
1347
  import { Router as Router2 } from "express";
1404
- var router2 = Router2();
1405
- router2.get("/", async (req, res, next) => {
1406
- try {
1407
- const workspaces = await workspaceService.getAllWorkspaces();
1408
- res.json({
1409
- success: true,
1410
- data: workspaces
1348
+ var router2, workspaces_default;
1349
+ var init_workspaces = __esm({
1350
+ "src/routes/workspaces.ts"() {
1351
+ "use strict";
1352
+ init_workspaceService();
1353
+ router2 = Router2();
1354
+ router2.get("/", async (req, res, next) => {
1355
+ try {
1356
+ const workspaces = await workspaceService.getAllWorkspaces();
1357
+ res.json({
1358
+ success: true,
1359
+ data: workspaces
1360
+ });
1361
+ } catch (error) {
1362
+ next(error);
1363
+ }
1411
1364
  });
1412
- } catch (error) {
1413
- next(error);
1414
- }
1415
- });
1416
- router2.get("/:id", async (req, res, next) => {
1417
- try {
1418
- const workspace = await workspaceService.getWorkspaceById(req.params.id);
1419
- res.json({
1420
- success: true,
1421
- data: workspace
1365
+ router2.get("/:id", async (req, res, next) => {
1366
+ try {
1367
+ const workspace = await workspaceService.getWorkspaceById(req.params.id);
1368
+ res.json({
1369
+ success: true,
1370
+ data: workspace
1371
+ });
1372
+ } catch (error) {
1373
+ next(error);
1374
+ }
1422
1375
  });
1423
- } catch (error) {
1424
- next(error);
1425
- }
1426
- });
1427
- router2.post("/", async (req, res, next) => {
1428
- try {
1429
- const { name, projectPath, type, scope } = req.body;
1430
- if (!name || !type || !scope) {
1431
- return res.status(400).json({
1432
- success: false,
1433
- error: "name, type, and scope are required"
1434
- });
1435
- }
1436
- if (scope === "project" && !projectPath) {
1437
- return res.status(400).json({
1438
- success: false,
1439
- error: "projectPath is required for project scope"
1440
- });
1441
- }
1442
- const validTypes = ["vscode", "cursor", "claude-desktop", "codex", "antigravity", "custom"];
1443
- if (!validTypes.includes(type)) {
1444
- return res.status(400).json({
1445
- success: false,
1446
- error: `type must be one of: ${validTypes.join(", ")}`
1447
- });
1448
- }
1449
- const validScopes = ["project", "global"];
1450
- if (!validScopes.includes(scope)) {
1451
- return res.status(400).json({
1452
- success: false,
1453
- error: `scope must be one of: ${validScopes.join(", ")}`
1454
- });
1455
- }
1456
- const workspace = await workspaceService.createWorkspace(name, projectPath || "", type, scope);
1457
- res.status(201).json({
1458
- success: true,
1459
- data: workspace,
1460
- message: "Workspace created successfully"
1376
+ router2.post("/", async (req, res, next) => {
1377
+ try {
1378
+ const { name, projectPath, type, scope } = req.body;
1379
+ if (!name || !type || !scope) {
1380
+ return res.status(400).json({
1381
+ success: false,
1382
+ error: "name, type, and scope are required"
1383
+ });
1384
+ }
1385
+ if (scope === "project" && !projectPath) {
1386
+ return res.status(400).json({
1387
+ success: false,
1388
+ error: "projectPath is required for project scope"
1389
+ });
1390
+ }
1391
+ const validTypes = ["vscode", "cursor", "claude-desktop", "codex", "antigravity", "custom"];
1392
+ if (!validTypes.includes(type)) {
1393
+ return res.status(400).json({
1394
+ success: false,
1395
+ error: `type must be one of: ${validTypes.join(", ")}`
1396
+ });
1397
+ }
1398
+ const validScopes = ["project", "global"];
1399
+ if (!validScopes.includes(scope)) {
1400
+ return res.status(400).json({
1401
+ success: false,
1402
+ error: `scope must be one of: ${validScopes.join(", ")}`
1403
+ });
1404
+ }
1405
+ const workspace = await workspaceService.createWorkspace(name, projectPath || "", type, scope);
1406
+ res.status(201).json({
1407
+ success: true,
1408
+ data: workspace,
1409
+ message: "Workspace created successfully"
1410
+ });
1411
+ } catch (error) {
1412
+ next(error);
1413
+ }
1461
1414
  });
1462
- } catch (error) {
1463
- next(error);
1464
- }
1465
- });
1466
- router2.put("/:id", async (req, res, next) => {
1467
- try {
1468
- const { name, path, type, scope } = req.body;
1469
- if (type) {
1470
- const validTypes = ["vscode", "cursor", "claude-desktop", "codex", "antigravity", "custom"];
1471
- if (!validTypes.includes(type)) {
1472
- return res.status(400).json({
1473
- success: false,
1474
- error: `type must be one of: ${validTypes.join(", ")}`
1415
+ router2.put("/:id", async (req, res, next) => {
1416
+ try {
1417
+ const { name, path, type, scope } = req.body;
1418
+ if (type) {
1419
+ const validTypes = ["vscode", "cursor", "claude-desktop", "codex", "antigravity", "custom"];
1420
+ if (!validTypes.includes(type)) {
1421
+ return res.status(400).json({
1422
+ success: false,
1423
+ error: `type must be one of: ${validTypes.join(", ")}`
1424
+ });
1425
+ }
1426
+ }
1427
+ if (scope) {
1428
+ const validScopes = ["project", "global"];
1429
+ if (!validScopes.includes(scope)) {
1430
+ return res.status(400).json({
1431
+ success: false,
1432
+ error: `scope must be one of: ${validScopes.join(", ")}`
1433
+ });
1434
+ }
1435
+ }
1436
+ const workspace = await workspaceService.updateWorkspace(req.params.id, {
1437
+ name,
1438
+ path,
1439
+ type,
1440
+ scope
1475
1441
  });
1442
+ res.json({
1443
+ success: true,
1444
+ data: workspace,
1445
+ message: "Workspace updated successfully"
1446
+ });
1447
+ } catch (error) {
1448
+ next(error);
1476
1449
  }
1477
- }
1478
- if (scope) {
1479
- const validScopes = ["project", "global"];
1480
- if (!validScopes.includes(scope)) {
1481
- return res.status(400).json({
1482
- success: false,
1483
- error: `scope must be one of: ${validScopes.join(", ")}`
1450
+ });
1451
+ router2.delete("/:id", async (req, res, next) => {
1452
+ try {
1453
+ const result = await workspaceService.deleteWorkspace(req.params.id);
1454
+ res.json(result);
1455
+ } catch (error) {
1456
+ next(error);
1457
+ }
1458
+ });
1459
+ router2.post("/:id/detect-skills", async (req, res, next) => {
1460
+ try {
1461
+ const workspace = await workspaceService.getWorkspaceById(req.params.id);
1462
+ const existingSkills = await workspaceService.detectExistingSkills(workspace.path);
1463
+ res.json({
1464
+ success: true,
1465
+ data: existingSkills
1484
1466
  });
1467
+ } catch (error) {
1468
+ next(error);
1485
1469
  }
1486
- }
1487
- const workspace = await workspaceService.updateWorkspace(req.params.id, {
1488
- name,
1489
- path,
1490
- type,
1491
- scope
1492
1470
  });
1493
- res.json({
1494
- success: true,
1495
- data: workspace,
1496
- message: "Workspace updated successfully"
1471
+ router2.post("/:id/migrate-skills", async (req, res, next) => {
1472
+ try {
1473
+ const { skillNames } = req.body;
1474
+ if (!skillNames || !Array.isArray(skillNames) || skillNames.length === 0) {
1475
+ return res.status(400).json({
1476
+ success: false,
1477
+ error: "skillNames array is required"
1478
+ });
1479
+ }
1480
+ const result = await workspaceService.migrateExistingSkills(req.params.id, skillNames);
1481
+ res.json({
1482
+ success: true,
1483
+ data: result
1484
+ });
1485
+ } catch (error) {
1486
+ next(error);
1487
+ }
1488
+ });
1489
+ workspaces_default = router2;
1490
+ }
1491
+ });
1492
+
1493
+ // src/services/bundleService.ts
1494
+ var bundleService_exports = {};
1495
+ __export(bundleService_exports, {
1496
+ bundleService: () => bundleService,
1497
+ createBundle: () => createBundle,
1498
+ deleteBundle: () => deleteBundle,
1499
+ extractBundle: () => extractBundle,
1500
+ getBundleSize: () => getBundleSize,
1501
+ listBundles: () => listBundles
1502
+ });
1503
+ import { createWriteStream, existsSync as existsSync6, createReadStream } from "fs";
1504
+ import { mkdir as mkdir4, rm as rm4, readdir, stat } from "fs/promises";
1505
+ import { join as join6 } from "path";
1506
+ import { pipeline } from "stream/promises";
1507
+ import archiver from "archiver";
1508
+ import { createGunzip } from "zlib";
1509
+ import { extract } from "tar";
1510
+ async function ensureBundlesDir() {
1511
+ if (!existsSync6(BUNDLES_DIR)) {
1512
+ await mkdir4(BUNDLES_DIR, { recursive: true });
1513
+ }
1514
+ }
1515
+ async function createBundle(skillPath, skillName) {
1516
+ await ensureBundlesDir();
1517
+ const bundleName = `${skillName}-${Date.now()}.tar.gz`;
1518
+ const bundlePath = join6(BUNDLES_DIR, bundleName);
1519
+ return new Promise((resolve, reject) => {
1520
+ const output = createWriteStream(bundlePath);
1521
+ const archive = archiver("tar", {
1522
+ gzip: true,
1523
+ gzipOptions: { level: 9 }
1524
+ });
1525
+ output.on("close", () => {
1526
+ console.log(`\u{1F4E6} Bundle created: ${bundlePath} (${archive.pointer()} bytes)`);
1527
+ resolve(bundlePath);
1528
+ });
1529
+ archive.on("error", (err) => {
1530
+ reject(err);
1497
1531
  });
1498
- } catch (error) {
1499
- next(error);
1532
+ archive.pipe(output);
1533
+ archive.directory(skillPath, false);
1534
+ archive.finalize();
1535
+ });
1536
+ }
1537
+ async function extractBundle(bundlePath, targetDir) {
1538
+ if (!existsSync6(bundlePath)) {
1539
+ throw new Error(`Bundle not found: ${bundlePath}`);
1500
1540
  }
1501
- });
1502
- router2.delete("/:id", async (req, res, next) => {
1503
- try {
1504
- const result = await workspaceService.deleteWorkspace(req.params.id);
1505
- res.json(result);
1506
- } catch (error) {
1507
- next(error);
1541
+ if (!existsSync6(targetDir)) {
1542
+ await mkdir4(targetDir, { recursive: true });
1508
1543
  }
1509
- });
1510
- router2.post("/:id/detect-skills", async (req, res, next) => {
1511
- try {
1512
- const workspace = await workspaceService.getWorkspaceById(req.params.id);
1513
- const existingSkills = await workspaceService.detectExistingSkills(workspace.path);
1514
- res.json({
1515
- success: true,
1516
- data: existingSkills
1517
- });
1518
- } catch (error) {
1519
- next(error);
1544
+ await pipeline(
1545
+ createReadStream(bundlePath),
1546
+ createGunzip(),
1547
+ extract({ cwd: targetDir })
1548
+ );
1549
+ console.log(`\u{1F4C2} Bundle extracted to: ${targetDir}`);
1550
+ }
1551
+ async function deleteBundle(bundlePath) {
1552
+ if (existsSync6(bundlePath)) {
1553
+ await rm4(bundlePath);
1554
+ console.log(`\u{1F5D1}\uFE0F Bundle deleted: ${bundlePath}`);
1520
1555
  }
1521
- });
1522
- router2.post("/:id/migrate-skills", async (req, res, next) => {
1523
- try {
1524
- const { skillNames } = req.body;
1525
- if (!skillNames || !Array.isArray(skillNames) || skillNames.length === 0) {
1526
- return res.status(400).json({
1527
- success: false,
1528
- error: "skillNames array is required"
1529
- });
1530
- }
1531
- const result = await workspaceService.migrateExistingSkills(req.params.id, skillNames);
1532
- res.json({
1533
- success: true,
1534
- data: result
1535
- });
1536
- } catch (error) {
1537
- next(error);
1556
+ }
1557
+ async function getBundleSize(bundlePath) {
1558
+ if (!existsSync6(bundlePath)) {
1559
+ return 0;
1560
+ }
1561
+ const stats = await stat(bundlePath);
1562
+ return stats.size;
1563
+ }
1564
+ async function listBundles() {
1565
+ await ensureBundlesDir();
1566
+ const files = await readdir(BUNDLES_DIR);
1567
+ return files.filter((f) => f.endsWith(".tar.gz")).map((f) => join6(BUNDLES_DIR, f));
1568
+ }
1569
+ var BUNDLES_DIR, bundleService;
1570
+ var init_bundleService = __esm({
1571
+ "src/services/bundleService.ts"() {
1572
+ "use strict";
1573
+ BUNDLES_DIR = join6(
1574
+ process.env.SKILLVERSE_HOME || join6(process.env.HOME || "", ".skillverse"),
1575
+ "bundles"
1576
+ );
1577
+ bundleService = {
1578
+ createBundle,
1579
+ extractBundle,
1580
+ deleteBundle,
1581
+ getBundleSize,
1582
+ listBundles,
1583
+ BUNDLES_DIR
1584
+ };
1538
1585
  }
1539
1586
  });
1540
- var workspaces_default = router2;
1541
1587
 
1542
1588
  // src/routes/marketplace.ts
1543
1589
  import { Router as Router3 } from "express";
1544
- init_bundleService();
1545
- import { existsSync as existsSync6 } from "fs";
1546
- var router3 = Router3();
1547
- router3.get("/skills", async (req, res, next) => {
1548
- try {
1549
- const { search, page = "1", pageSize = "20" } = req.query;
1550
- const where = {};
1551
- if (search) {
1552
- where.skill = {
1553
- OR: [
1554
- { name: { contains: search } },
1555
- { description: { contains: search } }
1556
- ]
1557
- };
1558
- }
1559
- const [items, total] = await Promise.all([
1560
- prisma.marketplaceSkill.findMany({
1561
- where,
1562
- include: {
1563
- skill: true
1564
- },
1565
- orderBy: {
1566
- downloads: "desc"
1567
- },
1568
- skip: (parseInt(page) - 1) * parseInt(pageSize),
1569
- take: parseInt(pageSize)
1570
- }),
1571
- prisma.marketplaceSkill.count({ where })
1572
- ]);
1573
- res.json({
1574
- success: true,
1575
- data: {
1576
- items,
1577
- total,
1578
- page: parseInt(page),
1579
- pageSize: parseInt(pageSize)
1590
+ import { existsSync as existsSync7 } from "fs";
1591
+ var router3, marketplace_default;
1592
+ var init_marketplace = __esm({
1593
+ "src/routes/marketplace.ts"() {
1594
+ "use strict";
1595
+ init_db();
1596
+ init_skillService();
1597
+ init_bundleService();
1598
+ init_errorHandler();
1599
+ init_dist();
1600
+ router3 = Router3();
1601
+ router3.get("/skills", async (req, res, next) => {
1602
+ try {
1603
+ const { search, page = "1", pageSize = "20" } = req.query;
1604
+ const where = {};
1605
+ if (search) {
1606
+ where.skill = {
1607
+ OR: [
1608
+ { name: { contains: search } },
1609
+ { description: { contains: search } }
1610
+ ]
1611
+ };
1612
+ }
1613
+ const [items, total] = await Promise.all([
1614
+ prisma.marketplaceSkill.findMany({
1615
+ where,
1616
+ include: {
1617
+ skill: true
1618
+ },
1619
+ orderBy: {
1620
+ downloads: "desc"
1621
+ },
1622
+ skip: (parseInt(page) - 1) * parseInt(pageSize),
1623
+ take: parseInt(pageSize)
1624
+ }),
1625
+ prisma.marketplaceSkill.count({ where })
1626
+ ]);
1627
+ res.json({
1628
+ success: true,
1629
+ data: {
1630
+ items,
1631
+ total,
1632
+ page: parseInt(page),
1633
+ pageSize: parseInt(pageSize)
1634
+ }
1635
+ });
1636
+ } catch (error) {
1637
+ next(error);
1580
1638
  }
1581
1639
  });
1582
- } catch (error) {
1583
- next(error);
1584
- }
1585
- });
1586
- router3.get("/skills/:id", async (req, res, next) => {
1587
- try {
1588
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1589
- where: { id: req.params.id },
1590
- include: {
1591
- skill: true
1640
+ router3.get("/skills/:id", async (req, res, next) => {
1641
+ try {
1642
+ const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1643
+ where: { id: req.params.id },
1644
+ include: {
1645
+ skill: true
1646
+ }
1647
+ });
1648
+ if (!marketplaceSkill) {
1649
+ throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1650
+ }
1651
+ res.json({
1652
+ success: true,
1653
+ data: marketplaceSkill
1654
+ });
1655
+ } catch (error) {
1656
+ next(error);
1592
1657
  }
1593
1658
  });
1594
- if (!marketplaceSkill) {
1595
- throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1596
- }
1597
- res.json({
1598
- success: true,
1599
- data: marketplaceSkill
1600
- });
1601
- } catch (error) {
1602
- next(error);
1603
- }
1604
- });
1605
- router3.get("/download/:id", async (req, res, next) => {
1606
- try {
1607
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1608
- where: { id: req.params.id },
1609
- include: {
1610
- skill: true
1659
+ router3.get("/download/:id", async (req, res, next) => {
1660
+ try {
1661
+ const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1662
+ where: { id: req.params.id },
1663
+ include: {
1664
+ skill: true
1665
+ }
1666
+ });
1667
+ if (!marketplaceSkill) {
1668
+ throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1669
+ }
1670
+ if (marketplaceSkill.skill.source === "git" && marketplaceSkill.skill.sourceUrl) {
1671
+ return res.json({
1672
+ success: true,
1673
+ data: {
1674
+ type: "git",
1675
+ sourceUrl: marketplaceSkill.skill.sourceUrl
1676
+ }
1677
+ });
1678
+ }
1679
+ if (marketplaceSkill.bundlePath && existsSync7(marketplaceSkill.bundlePath)) {
1680
+ res.setHeader("Content-Type", "application/gzip");
1681
+ res.setHeader(
1682
+ "Content-Disposition",
1683
+ `attachment; filename="${marketplaceSkill.skill.name}.tar.gz"`
1684
+ );
1685
+ return res.sendFile(marketplaceSkill.bundlePath);
1686
+ }
1687
+ throw new AppError(ErrorCode.NOT_FOUND, "Bundle not available for this skill", 404);
1688
+ } catch (error) {
1689
+ next(error);
1611
1690
  }
1612
1691
  });
1613
- if (!marketplaceSkill) {
1614
- throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1615
- }
1616
- if (marketplaceSkill.skill.source === "git" && marketplaceSkill.skill.sourceUrl) {
1617
- return res.json({
1618
- success: true,
1619
- data: {
1620
- type: "git",
1621
- sourceUrl: marketplaceSkill.skill.sourceUrl
1692
+ router3.post("/publish", async (req, res, next) => {
1693
+ try {
1694
+ const { skillId, publisherName } = req.body;
1695
+ if (!skillId) {
1696
+ return res.status(400).json({
1697
+ success: false,
1698
+ error: "skillId is required"
1699
+ });
1622
1700
  }
1623
- });
1624
- }
1625
- if (marketplaceSkill.bundlePath && existsSync6(marketplaceSkill.bundlePath)) {
1626
- res.setHeader("Content-Type", "application/gzip");
1627
- res.setHeader(
1628
- "Content-Disposition",
1629
- `attachment; filename="${marketplaceSkill.skill.name}.tar.gz"`
1630
- );
1631
- return res.sendFile(marketplaceSkill.bundlePath);
1632
- }
1633
- throw new AppError(ErrorCode.NOT_FOUND, "Bundle not available for this skill", 404);
1634
- } catch (error) {
1635
- next(error);
1636
- }
1637
- });
1638
- router3.post("/publish", async (req, res, next) => {
1639
- try {
1640
- const { skillId, publisherName } = req.body;
1641
- if (!skillId) {
1642
- return res.status(400).json({
1643
- success: false,
1644
- error: "skillId is required"
1645
- });
1646
- }
1647
- const skill = await prisma.skill.findUnique({
1648
- where: { id: skillId }
1649
- });
1650
- if (!skill) {
1651
- throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
1652
- }
1653
- const existingEntry = await prisma.marketplaceSkill.findUnique({
1654
- where: { skillId }
1655
- });
1656
- if (existingEntry) {
1657
- throw new AppError(ErrorCode.ALREADY_EXISTS, "Skill is already published to marketplace", 409);
1658
- }
1659
- let bundlePath = null;
1660
- let bundleSize = null;
1661
- if (skill.source === "local" && existsSync6(skill.storagePath)) {
1662
- bundlePath = await bundleService.createBundle(skill.storagePath, skill.name);
1663
- bundleSize = await bundleService.getBundleSize(bundlePath);
1664
- }
1665
- const marketplaceSkill = await prisma.marketplaceSkill.create({
1666
- data: {
1667
- skillId,
1668
- publisherName: publisherName || "Anonymous",
1669
- bundlePath,
1670
- bundleSize
1671
- },
1672
- include: {
1673
- skill: true
1701
+ const skill = await prisma.skill.findUnique({
1702
+ where: { id: skillId }
1703
+ });
1704
+ if (!skill) {
1705
+ throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
1706
+ }
1707
+ const existingEntry = await prisma.marketplaceSkill.findUnique({
1708
+ where: { skillId }
1709
+ });
1710
+ if (existingEntry) {
1711
+ throw new AppError(ErrorCode.ALREADY_EXISTS, "Skill is already published to marketplace", 409);
1712
+ }
1713
+ let bundlePath = null;
1714
+ let bundleSize = null;
1715
+ if (skill.source === "local" && existsSync7(skill.storagePath)) {
1716
+ bundlePath = await bundleService.createBundle(skill.storagePath, skill.name);
1717
+ bundleSize = await bundleService.getBundleSize(bundlePath);
1718
+ }
1719
+ const marketplaceSkill = await prisma.marketplaceSkill.create({
1720
+ data: {
1721
+ skillId,
1722
+ publisherName: publisherName || "Anonymous",
1723
+ bundlePath,
1724
+ bundleSize
1725
+ },
1726
+ include: {
1727
+ skill: true
1728
+ }
1729
+ });
1730
+ res.status(201).json({
1731
+ success: true,
1732
+ data: marketplaceSkill,
1733
+ message: "Skill published to marketplace successfully"
1734
+ });
1735
+ } catch (error) {
1736
+ next(error);
1674
1737
  }
1675
1738
  });
1676
- res.status(201).json({
1677
- success: true,
1678
- data: marketplaceSkill,
1679
- message: "Skill published to marketplace successfully"
1680
- });
1681
- } catch (error) {
1682
- next(error);
1683
- }
1684
- });
1685
- router3.post("/install/:id", async (req, res, next) => {
1686
- try {
1687
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1688
- where: { id: req.params.id },
1689
- include: {
1690
- skill: true
1739
+ router3.post("/install/:id", async (req, res, next) => {
1740
+ try {
1741
+ const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1742
+ where: { id: req.params.id },
1743
+ include: {
1744
+ skill: true
1745
+ }
1746
+ });
1747
+ if (!marketplaceSkill) {
1748
+ throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1749
+ }
1750
+ const sourceSkill = marketplaceSkill.skill;
1751
+ if (sourceSkill.source === "git" && sourceSkill.sourceUrl) {
1752
+ const newSkill = await skillService.createSkillFromGit(
1753
+ sourceSkill.sourceUrl,
1754
+ sourceSkill.description || void 0
1755
+ );
1756
+ await prisma.marketplaceSkill.update({
1757
+ where: { id: req.params.id },
1758
+ data: { downloads: { increment: 1 } }
1759
+ });
1760
+ return res.status(201).json({
1761
+ success: true,
1762
+ data: newSkill,
1763
+ message: "Skill installed from marketplace successfully"
1764
+ });
1765
+ }
1766
+ if (sourceSkill.source === "local" && marketplaceSkill.bundlePath && existsSync7(marketplaceSkill.bundlePath)) {
1767
+ const newSkill = await skillService.createSkillFromBundle(
1768
+ marketplaceSkill.bundlePath,
1769
+ sourceSkill.name,
1770
+ sourceSkill.description || void 0
1771
+ );
1772
+ await prisma.marketplaceSkill.update({
1773
+ where: { id: req.params.id },
1774
+ data: { downloads: { increment: 1 } }
1775
+ });
1776
+ return res.status(201).json({
1777
+ success: true,
1778
+ data: newSkill,
1779
+ message: "Skill installed from marketplace successfully"
1780
+ });
1781
+ }
1782
+ return res.status(400).json({
1783
+ success: false,
1784
+ error: "Cannot install this skill. Bundle not available."
1785
+ });
1786
+ } catch (error) {
1787
+ next(error);
1691
1788
  }
1692
1789
  });
1693
- if (!marketplaceSkill) {
1694
- throw new AppError(ErrorCode.NOT_FOUND, "Marketplace skill not found", 404);
1695
- }
1696
- const sourceSkill = marketplaceSkill.skill;
1697
- if (sourceSkill.source === "git" && sourceSkill.sourceUrl) {
1698
- const newSkill = await skillService.createSkillFromGit(
1699
- sourceSkill.sourceUrl,
1700
- sourceSkill.description || void 0
1701
- );
1702
- await prisma.marketplaceSkill.update({
1703
- where: { id: req.params.id },
1704
- data: { downloads: { increment: 1 } }
1705
- });
1706
- return res.status(201).json({
1707
- success: true,
1708
- data: newSkill,
1709
- message: "Skill installed from marketplace successfully"
1710
- });
1711
- }
1712
- if (sourceSkill.source === "local" && marketplaceSkill.bundlePath && existsSync6(marketplaceSkill.bundlePath)) {
1713
- const newSkill = await skillService.createSkillFromBundle(
1714
- marketplaceSkill.bundlePath,
1715
- sourceSkill.name,
1716
- sourceSkill.description || void 0
1717
- );
1718
- await prisma.marketplaceSkill.update({
1719
- where: { id: req.params.id },
1720
- data: { downloads: { increment: 1 } }
1721
- });
1722
- return res.status(201).json({
1723
- success: true,
1724
- data: newSkill,
1725
- message: "Skill installed from marketplace successfully"
1726
- });
1727
- }
1728
- return res.status(400).json({
1729
- success: false,
1730
- error: "Cannot install this skill. Bundle not available."
1731
- });
1732
- } catch (error) {
1733
- next(error);
1734
- }
1735
- });
1736
- router3.delete("/unpublish/:skillId", async (req, res, next) => {
1737
- try {
1738
- const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1739
- where: { skillId: req.params.skillId }
1740
- });
1741
- if (!marketplaceSkill) {
1742
- throw new AppError(ErrorCode.NOT_FOUND, "Skill is not published to marketplace", 404);
1743
- }
1744
- await prisma.marketplaceSkill.delete({
1745
- where: { id: marketplaceSkill.id }
1746
- });
1747
- res.json({
1748
- success: true,
1749
- message: "Skill unpublished from marketplace successfully"
1790
+ router3.delete("/unpublish/:skillId", async (req, res, next) => {
1791
+ try {
1792
+ const marketplaceSkill = await prisma.marketplaceSkill.findUnique({
1793
+ where: { skillId: req.params.skillId }
1794
+ });
1795
+ if (!marketplaceSkill) {
1796
+ throw new AppError(ErrorCode.NOT_FOUND, "Skill is not published to marketplace", 404);
1797
+ }
1798
+ await prisma.marketplaceSkill.delete({
1799
+ where: { id: marketplaceSkill.id }
1800
+ });
1801
+ res.json({
1802
+ success: true,
1803
+ message: "Skill unpublished from marketplace successfully"
1804
+ });
1805
+ } catch (error) {
1806
+ next(error);
1807
+ }
1750
1808
  });
1751
- } catch (error) {
1752
- next(error);
1809
+ marketplace_default = router3;
1753
1810
  }
1754
1811
  });
1755
- var marketplace_default = router3;
1756
1812
 
1757
1813
  // src/routes/dashboard.ts
1758
1814
  import { Router as Router4 } from "express";
1759
- var router4 = Router4();
1760
- router4.get("/stats", async (req, res, next) => {
1761
- try {
1762
- const [totalSkills, totalWorkspaces, totalLinks, marketplaceSkills, recentSkills] = await Promise.all([
1763
- prisma.skill.count(),
1764
- prisma.workspace.count(),
1765
- prisma.skillWorkspace.count(),
1766
- prisma.marketplaceSkill.count(),
1767
- prisma.skill.findMany({
1768
- orderBy: { installDate: "desc" },
1769
- take: 5,
1770
- include: {
1771
- linkedWorkspaces: {
1815
+ var router4, dashboard_default;
1816
+ var init_dashboard = __esm({
1817
+ "src/routes/dashboard.ts"() {
1818
+ "use strict";
1819
+ init_db();
1820
+ router4 = Router4();
1821
+ router4.get("/stats", async (req, res, next) => {
1822
+ try {
1823
+ const [totalSkills, totalWorkspaces, totalLinks, marketplaceSkills, recentSkills] = await Promise.all([
1824
+ prisma.skill.count(),
1825
+ prisma.workspace.count(),
1826
+ prisma.skillWorkspace.count(),
1827
+ prisma.marketplaceSkill.count(),
1828
+ prisma.skill.findMany({
1829
+ orderBy: { installDate: "desc" },
1830
+ take: 5,
1772
1831
  include: {
1773
- workspace: true
1832
+ linkedWorkspaces: {
1833
+ include: {
1834
+ workspace: true
1835
+ }
1836
+ }
1774
1837
  }
1838
+ })
1839
+ ]);
1840
+ res.json({
1841
+ success: true,
1842
+ data: {
1843
+ totalSkills,
1844
+ totalWorkspaces,
1845
+ totalLinks,
1846
+ marketplaceSkills,
1847
+ recentSkills
1775
1848
  }
1776
- }
1777
- })
1778
- ]);
1779
- res.json({
1780
- success: true,
1781
- data: {
1782
- totalSkills,
1783
- totalWorkspaces,
1784
- totalLinks,
1785
- marketplaceSkills,
1786
- recentSkills
1849
+ });
1850
+ } catch (error) {
1851
+ next(error);
1787
1852
  }
1788
1853
  });
1789
- } catch (error) {
1790
- next(error);
1791
- }
1792
- });
1793
- router4.get("/activity", async (req, res, next) => {
1794
- try {
1795
- const [recentSkills, recentLinks, recentPublished] = await Promise.all([
1796
- prisma.skill.findMany({
1797
- orderBy: { createdAt: "desc" },
1798
- take: 10,
1799
- select: {
1800
- id: true,
1801
- name: true,
1802
- source: true,
1803
- createdAt: true
1804
- }
1805
- }),
1806
- prisma.skillWorkspace.findMany({
1807
- orderBy: { linkedAt: "desc" },
1808
- take: 10,
1809
- include: {
1810
- skill: { select: { name: true } },
1811
- workspace: { select: { name: true } }
1812
- }
1813
- }),
1814
- prisma.marketplaceSkill.findMany({
1815
- orderBy: { publishDate: "desc" },
1816
- take: 10,
1817
- include: {
1818
- skill: { select: { name: true } }
1819
- }
1820
- })
1821
- ]);
1822
- const activities = [
1823
- ...recentSkills.map((s) => ({
1824
- type: "skill_added",
1825
- date: s.createdAt,
1826
- data: { skillName: s.name, source: s.source }
1827
- })),
1828
- ...recentLinks.map((l) => ({
1829
- type: "skill_linked",
1830
- date: l.linkedAt,
1831
- data: { skillName: l.skill.name, workspaceName: l.workspace.name }
1832
- })),
1833
- ...recentPublished.map((p) => ({
1834
- type: "skill_published",
1835
- date: p.publishDate,
1836
- data: { skillName: p.skill.name }
1837
- }))
1838
- ].sort((a, b) => b.date.getTime() - a.date.getTime());
1839
- res.json({
1840
- success: true,
1841
- data: activities.slice(0, 20)
1854
+ router4.get("/activity", async (req, res, next) => {
1855
+ try {
1856
+ const [recentSkills, recentLinks, recentPublished] = await Promise.all([
1857
+ prisma.skill.findMany({
1858
+ orderBy: { createdAt: "desc" },
1859
+ take: 10,
1860
+ select: {
1861
+ id: true,
1862
+ name: true,
1863
+ source: true,
1864
+ createdAt: true
1865
+ }
1866
+ }),
1867
+ prisma.skillWorkspace.findMany({
1868
+ orderBy: { linkedAt: "desc" },
1869
+ take: 10,
1870
+ include: {
1871
+ skill: { select: { name: true } },
1872
+ workspace: { select: { name: true } }
1873
+ }
1874
+ }),
1875
+ prisma.marketplaceSkill.findMany({
1876
+ orderBy: { publishDate: "desc" },
1877
+ take: 10,
1878
+ include: {
1879
+ skill: { select: { name: true } }
1880
+ }
1881
+ })
1882
+ ]);
1883
+ const activities = [
1884
+ ...recentSkills.map((s) => ({
1885
+ type: "skill_added",
1886
+ date: s.createdAt,
1887
+ data: { skillName: s.name, source: s.source }
1888
+ })),
1889
+ ...recentLinks.map((l) => ({
1890
+ type: "skill_linked",
1891
+ date: l.linkedAt,
1892
+ data: { skillName: l.skill.name, workspaceName: l.workspace.name }
1893
+ })),
1894
+ ...recentPublished.map((p) => ({
1895
+ type: "skill_published",
1896
+ date: p.publishDate,
1897
+ data: { skillName: p.skill.name }
1898
+ }))
1899
+ ].sort((a, b) => b.date.getTime() - a.date.getTime());
1900
+ res.json({
1901
+ success: true,
1902
+ data: activities.slice(0, 20)
1903
+ });
1904
+ } catch (error) {
1905
+ next(error);
1906
+ }
1842
1907
  });
1843
- } catch (error) {
1844
- next(error);
1908
+ dashboard_default = router4;
1845
1909
  }
1846
1910
  });
1847
- var dashboard_default = router4;
1848
1911
 
1849
1912
  // src/middleware/logger.ts
1850
1913
  function requestLogger(req, res, next) {
@@ -1855,54 +1918,31 @@ function requestLogger(req, res, next) {
1855
1918
  });
1856
1919
  next();
1857
1920
  }
1921
+ var init_logger = __esm({
1922
+ "src/middleware/logger.ts"() {
1923
+ "use strict";
1924
+ }
1925
+ });
1858
1926
 
1859
1927
  // src/index.ts
1860
- dotenv2.config();
1861
- var __filename2 = fileURLToPath2(import.meta.url);
1862
- var __dirname2 = dirname2(__filename2);
1863
- var app = express();
1864
- var PORT = process.env.PORT || 3001;
1865
- app.use(cors());
1866
- app.use(express.json());
1867
- app.use(express.urlencoded({ extended: true }));
1868
- app.use(requestLogger);
1869
- var possiblePublicDirs = [
1870
- join6(__dirname2, "../public"),
1871
- // Production: dist/index.js -> public
1872
- join6(__dirname2, "../../public"),
1873
- // Dev tsx might resolve here
1874
- join6(process.cwd(), "public"),
1875
- // Fallback: relative to cwd
1876
- join6(process.cwd(), "server/public")
1877
- // Fallback: from root
1878
- ];
1879
- var publicDir = possiblePublicDirs.find((dir) => existsSync7(dir));
1880
- if (publicDir) {
1881
- app.use(express.static(publicDir));
1882
- console.log(`\u{1F4C2} Serving static files from: ${publicDir}`);
1883
- } else {
1884
- console.warn('\u26A0\uFE0F No public directory found. Run "npm run build" in client first.');
1885
- }
1886
- app.use("/api/skills", skills_default);
1887
- app.use("/api/workspaces", workspaces_default);
1888
- app.use("/api/marketplace", marketplace_default);
1889
- app.use("/api/dashboard", dashboard_default);
1890
- app.get("/health", (req, res) => {
1891
- res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
1928
+ var index_exports = {};
1929
+ __export(index_exports, {
1930
+ startServer: () => startServer
1892
1931
  });
1893
- if (publicDir && existsSync7(publicDir)) {
1894
- app.get("*", (req, res) => {
1895
- res.sendFile(join6(publicDir, "index.html"));
1896
- });
1897
- }
1898
- app.use(errorHandler);
1932
+ import express from "express";
1933
+ import cors from "cors";
1934
+ import dotenv2 from "dotenv";
1935
+ import { fileURLToPath as fileURLToPath3 } from "url";
1936
+ import { dirname as dirname3, join as join7 } from "path";
1937
+ import { mkdir as mkdir5 } from "fs/promises";
1938
+ import { existsSync as existsSync8 } from "fs";
1899
1939
  async function initializeStorage() {
1900
- const skillverseHome = process.env.SKILLVERSE_HOME || join6(process.env.HOME || "", ".skillverse");
1901
- const skillsDir = process.env.SKILLS_DIR || join6(skillverseHome, "skills");
1902
- const marketplaceDir = process.env.MARKETPLACE_DIR || join6(skillverseHome, "marketplace");
1940
+ const skillverseHome = process.env.SKILLVERSE_HOME || join7(process.env.HOME || "", ".skillverse");
1941
+ const skillsDir = process.env.SKILLS_DIR || join7(skillverseHome, "skills");
1942
+ const marketplaceDir = process.env.MARKETPLACE_DIR || join7(skillverseHome, "marketplace");
1903
1943
  const dirs = [skillverseHome, skillsDir, marketplaceDir];
1904
1944
  for (const dir of dirs) {
1905
- if (!existsSync7(dir)) {
1945
+ if (!existsSync8(dir)) {
1906
1946
  await mkdir5(dir, { recursive: true });
1907
1947
  console.log(`Created directory: ${dir}`);
1908
1948
  }
@@ -1914,7 +1954,7 @@ async function startServer(port = parseInt(process.env.PORT || "3001")) {
1914
1954
  return new Promise((resolve) => {
1915
1955
  app.listen(port, () => {
1916
1956
  console.log(`\u{1F680} SkillVerse server running on http://localhost:${port}`);
1917
- console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join6(process.env.HOME || "", ".skillverse")}`);
1957
+ console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join7(process.env.HOME || "", ".skillverse")}`);
1918
1958
  resolve();
1919
1959
  });
1920
1960
  });
@@ -1923,28 +1963,151 @@ async function startServer(port = parseInt(process.env.PORT || "3001")) {
1923
1963
  process.exit(1);
1924
1964
  }
1925
1965
  }
1926
- if (process.argv[1] === fileURLToPath2(import.meta.url)) {
1927
- startServer();
1928
- }
1966
+ var __filename3, __dirname3, app, PORT, possiblePublicDirs, publicDir;
1967
+ var init_index = __esm({
1968
+ "src/index.ts"() {
1969
+ "use strict";
1970
+ init_skills();
1971
+ init_workspaces();
1972
+ init_marketplace();
1973
+ init_dashboard();
1974
+ init_errorHandler();
1975
+ init_logger();
1976
+ dotenv2.config();
1977
+ __filename3 = fileURLToPath3(import.meta.url);
1978
+ __dirname3 = dirname3(__filename3);
1979
+ app = express();
1980
+ PORT = process.env.PORT || 3001;
1981
+ app.use(cors());
1982
+ app.use(express.json());
1983
+ app.use(express.urlencoded({ extended: true }));
1984
+ app.use(requestLogger);
1985
+ possiblePublicDirs = [
1986
+ join7(__dirname3, "../public"),
1987
+ // Production: dist/index.js -> public
1988
+ join7(__dirname3, "../../public"),
1989
+ // Dev tsx might resolve here
1990
+ join7(process.cwd(), "public"),
1991
+ // Fallback: relative to cwd
1992
+ join7(process.cwd(), "server/public")
1993
+ // Fallback: from root
1994
+ ];
1995
+ publicDir = possiblePublicDirs.find((dir) => existsSync8(dir));
1996
+ if (publicDir) {
1997
+ app.use(express.static(publicDir));
1998
+ console.log(`\u{1F4C2} Serving static files from: ${publicDir}`);
1999
+ } else {
2000
+ console.warn('\u26A0\uFE0F No public directory found. Run "npm run build" in client first.');
2001
+ }
2002
+ app.use("/api/skills", skills_default);
2003
+ app.use("/api/workspaces", workspaces_default);
2004
+ app.use("/api/marketplace", marketplace_default);
2005
+ app.use("/api/dashboard", dashboard_default);
2006
+ app.get("/health", (req, res) => {
2007
+ res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
2008
+ });
2009
+ if (publicDir && existsSync8(publicDir)) {
2010
+ app.get("*", (req, res) => {
2011
+ res.sendFile(join7(publicDir, "index.html"));
2012
+ });
2013
+ }
2014
+ app.use(errorHandler);
2015
+ if (process.argv[1] === fileURLToPath3(import.meta.url)) {
2016
+ startServer();
2017
+ }
2018
+ }
2019
+ });
1929
2020
 
1930
2021
  // src/bin.ts
2022
+ init_config();
1931
2023
  import { program } from "commander";
1932
2024
  import open from "open";
1933
- import { existsSync as existsSync8, readFileSync, writeFileSync } from "fs";
1934
- import { join as join7 } from "path";
2025
+ import { existsSync as existsSync9, readFileSync, writeFileSync } from "fs";
2026
+ import { join as join8 } from "path";
1935
2027
  import readline from "readline";
2028
+
2029
+ // src/lib/initDb.ts
2030
+ init_db();
2031
+ import { existsSync as existsSync2 } from "fs";
2032
+ import { join as join2, dirname as dirname2 } from "path";
2033
+ import { fileURLToPath as fileURLToPath2 } from "url";
2034
+ import { spawnSync } from "child_process";
2035
+ var __filename2 = fileURLToPath2(import.meta.url);
2036
+ var __dirname2 = dirname2(__filename2);
2037
+ function getSchemaPath() {
2038
+ const possiblePaths = [
2039
+ join2(__dirname2, "../../prisma/schema.prisma"),
2040
+ // From dist/lib or src/lib
2041
+ join2(__dirname2, "../../../prisma/schema.prisma"),
2042
+ join2(process.cwd(), "prisma/schema.prisma"),
2043
+ // Fallback
2044
+ join2(process.cwd(), "server/prisma/schema.prisma")
2045
+ ];
2046
+ return possiblePaths.find((p) => existsSync2(p)) || null;
2047
+ }
2048
+ async function ensureDatabaseInitialized() {
2049
+ try {
2050
+ try {
2051
+ await prisma.skill.count();
2052
+ return;
2053
+ } catch (error) {
2054
+ if (error.code === "P2021" || error.message.includes("table") || error.message.includes("does not exist")) {
2055
+ console.log("\u{1F4E6} Initializing database...");
2056
+ await initializeDatabase();
2057
+ } else {
2058
+ throw error;
2059
+ }
2060
+ }
2061
+ } catch (error) {
2062
+ throw error;
2063
+ }
2064
+ }
2065
+ async function initializeDatabase() {
2066
+ const schemaPath = getSchemaPath();
2067
+ if (!schemaPath) {
2068
+ throw new Error("Could not find schema.prisma");
2069
+ }
2070
+ const possiblePrismaBins = [
2071
+ join2(__dirname2, "../../node_modules/.bin/prisma"),
2072
+ join2(__dirname2, "../../../node_modules/.bin/prisma"),
2073
+ join2(process.cwd(), "node_modules/.bin/prisma")
2074
+ ];
2075
+ const prismaBin = possiblePrismaBins.find((p) => existsSync2(p)) || "prisma";
2076
+ console.log(` Using schema: ${schemaPath}`);
2077
+ const result = spawnSync(prismaBin, ["db", "push", "--schema", schemaPath], {
2078
+ stdio: "inherit",
2079
+ env: { ...process.env }
2080
+ // Ensure env vars (DATABASE_URL) are passed
2081
+ });
2082
+ if (result.error) {
2083
+ throw result.error;
2084
+ }
2085
+ if (result.status !== 0) {
2086
+ throw new Error(`Database initialization failed with status ${result.status}`);
2087
+ }
2088
+ console.log("\u2705 Database initialized successfully");
2089
+ }
2090
+
2091
+ // src/bin.ts
2092
+ init_skillService();
2093
+ init_workspaceService();
1936
2094
  var { skillverseHome: SKILLVERSE_HOME2 } = config;
1937
- var AUTH_FILE = join7(SKILLVERSE_HOME2, "auth.json");
1938
- var CONFIG_FILE = join7(SKILLVERSE_HOME2, "config.json");
2095
+ var AUTH_FILE = join8(SKILLVERSE_HOME2, "auth.json");
2096
+ var CONFIG_FILE = join8(SKILLVERSE_HOME2, "config.json");
2097
+ program.hook("preAction", async (thisCommand, actionCommand) => {
2098
+ if (["list", "start", "install", "add", "remove", "import", "search"].includes(actionCommand.name())) {
2099
+ await ensureDatabaseInitialized();
2100
+ }
2101
+ });
1939
2102
  function getRegistryUrl() {
1940
- if (existsSync8(CONFIG_FILE)) {
2103
+ if (existsSync9(CONFIG_FILE)) {
1941
2104
  const config2 = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
1942
2105
  return config2.registryUrl || "http://localhost:4000";
1943
2106
  }
1944
2107
  return process.env.SKILLVERSE_REGISTRY || "http://localhost:4000";
1945
2108
  }
1946
2109
  function getAuthToken() {
1947
- if (existsSync8(AUTH_FILE)) {
2110
+ if (existsSync9(AUTH_FILE)) {
1948
2111
  const auth = JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
1949
2112
  return auth.token || null;
1950
2113
  }
@@ -1996,7 +2159,8 @@ program.command("start").description("Start the SkillVerse local server").option
1996
2159
  console.log(" \u2551 \u{1F680} SkillVerse CLI \u2551");
1997
2160
  console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
1998
2161
  console.log("");
1999
- await startServer(port);
2162
+ const { startServer: startServer2 } = await Promise.resolve().then(() => (init_index(), index_exports));
2163
+ await startServer2(port);
2000
2164
  if (options.open) {
2001
2165
  const url = `http://localhost:${port}`;
2002
2166
  console.log(`
@@ -2041,7 +2205,7 @@ program.command("publish [path]").description("Publish a skill to the Registry")
2041
2205
  }
2042
2206
  const targetPath = skillPath || process.cwd();
2043
2207
  const registryUrl = options.registry || getRegistryUrl();
2044
- if (!existsSync8(targetPath)) {
2208
+ if (!existsSync9(targetPath)) {
2045
2209
  console.error(`
2046
2210
  \u274C Path not found: ${targetPath}`);
2047
2211
  process.exit(1);
@@ -2115,7 +2279,7 @@ program.command("search <query>").description("Search for skills in the Registry
2115
2279
  }
2116
2280
  });
2117
2281
  program.command("config").description("Configure SkillVerse settings").option("-r, --registry <url>", "Set default registry URL").action((options) => {
2118
- const config2 = existsSync8(CONFIG_FILE) ? JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) : {};
2282
+ const config2 = existsSync9(CONFIG_FILE) ? JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) : {};
2119
2283
  if (options.registry) {
2120
2284
  config2.registryUrl = options.registry;
2121
2285
  writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2));
@@ -2168,8 +2332,8 @@ program.command("install [gitUrl]").description("Install a skill from Git URL").
2168
2332
  }
2169
2333
  });
2170
2334
  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) => {
2171
- const sourcePath = options.path.startsWith("/") ? options.path : join7(process.cwd(), options.path);
2172
- if (!existsSync8(sourcePath)) {
2335
+ const sourcePath = options.path.startsWith("/") ? options.path : join8(process.cwd(), options.path);
2336
+ if (!existsSync9(sourcePath)) {
2173
2337
  console.error(`\u274C Source path not found: ${sourcePath}`);
2174
2338
  process.exit(1);
2175
2339
  }