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