skillverse 0.1.3 → 0.1.5
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 +283 -64
- package/dist/bin.js.map +1 -1
- package/dist/index.js +842 -574
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/prisma/dev.db +0 -0
- package/public/assets/index-DVybgO9W.js +20 -0
- package/public/assets/index-li8hN2px.css +1 -0
- package/public/index.html +2 -2
- package/public/assets/index-BsYtpZSa.css +0 -1
- package/public/assets/index-Dfr_6UV8.js +0 -20
package/dist/index.js
CHANGED
|
@@ -1,38 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// src/routes/skills.ts
|
|
11
|
-
import { Router } from "express";
|
|
12
|
-
import multer from "multer";
|
|
13
|
-
import { join as join4 } from "path";
|
|
14
|
-
import { existsSync as existsSync4 } from "fs";
|
|
15
|
-
import { mkdir as mkdir3, rm as rm3 } from "fs/promises";
|
|
16
|
-
|
|
17
|
-
// src/services/skillService.ts
|
|
18
|
-
import { join as join2, basename } from "path";
|
|
19
|
-
import { existsSync as existsSync2 } from "fs";
|
|
20
|
-
import { mkdir, rm, cp, readFile, unlink } from "fs/promises";
|
|
21
|
-
import matter from "gray-matter";
|
|
22
|
-
import simpleGit from "simple-git";
|
|
23
|
-
import AdmZip from "adm-zip";
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
24
10
|
|
|
25
11
|
// src/config.ts
|
|
26
12
|
import { join, dirname } from "path";
|
|
27
|
-
import { existsSync, mkdirSync } from "fs";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
28
14
|
import { fileURLToPath } from "url";
|
|
29
15
|
import dotenv from "dotenv";
|
|
30
|
-
dotenv.config();
|
|
31
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
32
|
-
var __dirname = dirname(__filename);
|
|
33
16
|
function setupEnvironment() {
|
|
34
17
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
35
|
-
const
|
|
18
|
+
const defaultHome = join(home, ".skillverse");
|
|
19
|
+
const bootstrapConfigPath = join(defaultHome, "config.json");
|
|
20
|
+
let skillverseHome = process.env.SKILLVERSE_HOME || defaultHome;
|
|
21
|
+
if (existsSync(bootstrapConfigPath)) {
|
|
22
|
+
try {
|
|
23
|
+
const config3 = JSON.parse(readFileSync(bootstrapConfigPath, "utf-8"));
|
|
24
|
+
if (config3.skillverseHome) {
|
|
25
|
+
skillverseHome = config3.skillverseHome;
|
|
26
|
+
process.env.SKILLVERSE_HOME = skillverseHome;
|
|
27
|
+
console.log(`Common config found. Using custom home: ${skillverseHome}`);
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.warn("Failed to read bootstrap config:", e);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
36
33
|
const dbUrl = process.env.DATABASE_URL;
|
|
37
34
|
if (!existsSync(skillverseHome)) {
|
|
38
35
|
try {
|
|
@@ -41,73 +38,99 @@ function setupEnvironment() {
|
|
|
41
38
|
console.error("Failed to create .skillverse directory:", error);
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
|
-
if (!dbUrl) {
|
|
41
|
+
if (!dbUrl || skillverseHome !== defaultHome) {
|
|
45
42
|
const dbPath = join(skillverseHome, "skillverse.db");
|
|
46
43
|
process.env.DATABASE_URL = `file:${dbPath}`;
|
|
44
|
+
console.log(`Using database at: ${dbPath}`);
|
|
47
45
|
}
|
|
46
|
+
process.env.SKILLS_DIR = join(skillverseHome, "skills");
|
|
47
|
+
process.env.MARKETPLACE_DIR = join(skillverseHome, "marketplace");
|
|
48
|
+
process.env.TEMP_DIR = join(skillverseHome, "temp");
|
|
48
49
|
return {
|
|
49
50
|
skillverseHome,
|
|
50
51
|
databaseUrl: process.env.DATABASE_URL
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
|
-
var
|
|
54
|
+
var __filename, __dirname, config;
|
|
55
|
+
var init_config = __esm({
|
|
56
|
+
"src/config.ts"() {
|
|
57
|
+
"use strict";
|
|
58
|
+
dotenv.config();
|
|
59
|
+
__filename = fileURLToPath(import.meta.url);
|
|
60
|
+
__dirname = dirname(__filename);
|
|
61
|
+
config = setupEnvironment();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
54
64
|
|
|
55
65
|
// src/lib/db.ts
|
|
56
66
|
import { PrismaClient } from "@prisma/client";
|
|
57
|
-
var globalForPrisma
|
|
58
|
-
var
|
|
59
|
-
|
|
67
|
+
var config2, globalForPrisma, currentDbUrl, prisma;
|
|
68
|
+
var init_db = __esm({
|
|
69
|
+
"src/lib/db.ts"() {
|
|
70
|
+
"use strict";
|
|
71
|
+
init_config();
|
|
72
|
+
config2 = setupEnvironment();
|
|
73
|
+
globalForPrisma = globalThis;
|
|
74
|
+
currentDbUrl = process.env.DATABASE_URL;
|
|
75
|
+
console.log(`\u{1F4CA} DB connection target: ${currentDbUrl}`);
|
|
76
|
+
if (globalForPrisma.prisma && globalForPrisma.lastDbUrl !== currentDbUrl) {
|
|
77
|
+
console.log(`\u{1F504} DATABASE_URL changed from ${globalForPrisma.lastDbUrl} to ${currentDbUrl}, reconnecting...`);
|
|
78
|
+
globalForPrisma.prisma.$disconnect().catch(console.error);
|
|
79
|
+
globalForPrisma.prisma = void 0;
|
|
80
|
+
}
|
|
81
|
+
prisma = globalForPrisma.prisma ?? new PrismaClient({
|
|
82
|
+
log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"]
|
|
83
|
+
});
|
|
84
|
+
globalForPrisma.prisma = prisma;
|
|
85
|
+
globalForPrisma.lastDbUrl = currentDbUrl;
|
|
86
|
+
}
|
|
60
87
|
});
|
|
61
|
-
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
|
62
88
|
|
|
63
89
|
// ../shared/dist/index.js
|
|
64
|
-
var WORKSPACE_SKILLS_PATHS
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
var WORKSPACE_SKILLS_PATHS, ErrorCode;
|
|
91
|
+
var init_dist = __esm({
|
|
92
|
+
"../shared/dist/index.js"() {
|
|
93
|
+
"use strict";
|
|
94
|
+
WORKSPACE_SKILLS_PATHS = {
|
|
95
|
+
vscode: {
|
|
96
|
+
project: ".github/skills",
|
|
97
|
+
global: "~/.copilot/skills"
|
|
98
|
+
},
|
|
99
|
+
cursor: {
|
|
100
|
+
project: ".cursor/skills",
|
|
101
|
+
global: "~/.cursor/skills"
|
|
102
|
+
},
|
|
103
|
+
"claude-desktop": {
|
|
104
|
+
project: ".claude/skills",
|
|
105
|
+
global: "~/.claude/skills"
|
|
106
|
+
},
|
|
107
|
+
codex: {
|
|
108
|
+
project: ".codex/skills",
|
|
109
|
+
global: "~/.codex/skills"
|
|
110
|
+
},
|
|
111
|
+
antigravity: {
|
|
112
|
+
project: ".agent/skills",
|
|
113
|
+
global: "~/.agent/skills"
|
|
114
|
+
},
|
|
115
|
+
custom: {
|
|
116
|
+
project: "skills",
|
|
117
|
+
global: ""
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
ErrorCode = {
|
|
121
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
122
|
+
NOT_FOUND: "NOT_FOUND",
|
|
123
|
+
ALREADY_EXISTS: "ALREADY_EXISTS",
|
|
124
|
+
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
125
|
+
GIT_ERROR: "GIT_ERROR",
|
|
126
|
+
FILE_SYSTEM_ERROR: "FILE_SYSTEM_ERROR",
|
|
127
|
+
PERMISSION_ERROR: "PERMISSION_ERROR",
|
|
128
|
+
SYMLINK_ERROR: "SYMLINK_ERROR"
|
|
129
|
+
};
|
|
88
130
|
}
|
|
89
|
-
};
|
|
90
|
-
var ErrorCode = {
|
|
91
|
-
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
92
|
-
NOT_FOUND: "NOT_FOUND",
|
|
93
|
-
ALREADY_EXISTS: "ALREADY_EXISTS",
|
|
94
|
-
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
95
|
-
GIT_ERROR: "GIT_ERROR",
|
|
96
|
-
FILE_SYSTEM_ERROR: "FILE_SYSTEM_ERROR",
|
|
97
|
-
PERMISSION_ERROR: "PERMISSION_ERROR",
|
|
98
|
-
SYMLINK_ERROR: "SYMLINK_ERROR"
|
|
99
|
-
};
|
|
131
|
+
});
|
|
100
132
|
|
|
101
133
|
// src/middleware/errorHandler.ts
|
|
102
|
-
var AppError = class extends Error {
|
|
103
|
-
constructor(code, message, statusCode = 500, details) {
|
|
104
|
-
super(message);
|
|
105
|
-
this.code = code;
|
|
106
|
-
this.statusCode = statusCode;
|
|
107
|
-
this.details = details;
|
|
108
|
-
this.name = "AppError";
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
134
|
function errorHandler(err, req, res, next) {
|
|
112
135
|
console.error("Error:", err);
|
|
113
136
|
if (err instanceof AppError) {
|
|
@@ -125,11 +148,35 @@ function errorHandler(err, req, res, next) {
|
|
|
125
148
|
message: process.env.NODE_ENV === "development" ? err.message : void 0
|
|
126
149
|
});
|
|
127
150
|
}
|
|
151
|
+
var AppError;
|
|
152
|
+
var init_errorHandler = __esm({
|
|
153
|
+
"src/middleware/errorHandler.ts"() {
|
|
154
|
+
"use strict";
|
|
155
|
+
init_dist();
|
|
156
|
+
AppError = class extends Error {
|
|
157
|
+
constructor(code, message, statusCode = 500, details) {
|
|
158
|
+
super(message);
|
|
159
|
+
this.code = code;
|
|
160
|
+
this.statusCode = statusCode;
|
|
161
|
+
this.details = details;
|
|
162
|
+
this.name = "AppError";
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
});
|
|
128
167
|
|
|
129
168
|
// src/services/skillService.ts
|
|
130
|
-
var
|
|
131
|
-
|
|
132
|
-
|
|
169
|
+
var skillService_exports = {};
|
|
170
|
+
__export(skillService_exports, {
|
|
171
|
+
SkillService: () => SkillService,
|
|
172
|
+
skillService: () => skillService
|
|
173
|
+
});
|
|
174
|
+
import { join as join2, basename } from "path";
|
|
175
|
+
import { existsSync as existsSync2 } from "fs";
|
|
176
|
+
import { mkdir, rm, cp, readFile, unlink } from "fs/promises";
|
|
177
|
+
import matter from "gray-matter";
|
|
178
|
+
import simpleGit from "simple-git";
|
|
179
|
+
import AdmZip from "adm-zip";
|
|
133
180
|
function parseGitUrl(url) {
|
|
134
181
|
if (!url.includes("/tree/")) {
|
|
135
182
|
const urlParts = url.split("/");
|
|
@@ -144,511 +191,621 @@ function parseGitUrl(url) {
|
|
|
144
191
|
const skillName = pathParts[pathParts.length - 1];
|
|
145
192
|
return { repoUrl, subdir, skillName };
|
|
146
193
|
}
|
|
147
|
-
var SkillService
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
194
|
+
var getPaths, SkillService, skillService;
|
|
195
|
+
var init_skillService = __esm({
|
|
196
|
+
"src/services/skillService.ts"() {
|
|
197
|
+
"use strict";
|
|
198
|
+
init_db();
|
|
199
|
+
init_errorHandler();
|
|
200
|
+
init_dist();
|
|
201
|
+
getPaths = () => {
|
|
202
|
+
const home = process.env.SKILLVERSE_HOME || join2(process.env.HOME || "", ".skillverse");
|
|
203
|
+
return {
|
|
204
|
+
home,
|
|
205
|
+
skills: process.env.SKILLS_DIR || join2(home, "skills"),
|
|
206
|
+
temp: process.env.TEMP_DIR || join2(home, "temp")
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
SkillService = class {
|
|
210
|
+
async getAllSkills() {
|
|
211
|
+
const skills = await prisma.skill.findMany({
|
|
152
212
|
include: {
|
|
153
|
-
|
|
213
|
+
linkedWorkspaces: {
|
|
214
|
+
include: {
|
|
215
|
+
workspace: true
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
marketplaceEntry: true
|
|
219
|
+
},
|
|
220
|
+
orderBy: {
|
|
221
|
+
installDate: "desc"
|
|
154
222
|
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
},
|
|
158
|
-
orderBy: {
|
|
159
|
-
installDate: "desc"
|
|
223
|
+
});
|
|
224
|
+
return skills;
|
|
160
225
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
async getSkillById(id) {
|
|
165
|
-
const skill = await prisma.skill.findUnique({
|
|
166
|
-
where: { id },
|
|
167
|
-
include: {
|
|
168
|
-
linkedWorkspaces: {
|
|
226
|
+
async getSkillById(id) {
|
|
227
|
+
const skill = await prisma.skill.findUnique({
|
|
228
|
+
where: { id },
|
|
169
229
|
include: {
|
|
170
|
-
|
|
230
|
+
linkedWorkspaces: {
|
|
231
|
+
include: {
|
|
232
|
+
workspace: true
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
marketplaceEntry: true
|
|
171
236
|
}
|
|
172
|
-
}
|
|
173
|
-
|
|
237
|
+
});
|
|
238
|
+
if (!skill) {
|
|
239
|
+
throw new AppError(ErrorCode.NOT_FOUND, "Skill not found", 404);
|
|
240
|
+
}
|
|
241
|
+
return skill;
|
|
174
242
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
return skill;
|
|
180
|
-
}
|
|
181
|
-
async getSkillByName(name) {
|
|
182
|
-
const skill = await prisma.skill.findUnique({
|
|
183
|
-
where: { name },
|
|
184
|
-
include: {
|
|
185
|
-
linkedWorkspaces: {
|
|
243
|
+
async getSkillByName(name) {
|
|
244
|
+
const skill = await prisma.skill.findUnique({
|
|
245
|
+
where: { name },
|
|
186
246
|
include: {
|
|
187
|
-
|
|
247
|
+
linkedWorkspaces: {
|
|
248
|
+
include: {
|
|
249
|
+
workspace: true
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
marketplaceEntry: true
|
|
188
253
|
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
193
|
-
if (!skill) {
|
|
194
|
-
throw new AppError(ErrorCode.NOT_FOUND, `Skill "${name}" not found`, 404);
|
|
195
|
-
}
|
|
196
|
-
return skill;
|
|
197
|
-
}
|
|
198
|
-
async parseSkillMetadata(skillPath) {
|
|
199
|
-
try {
|
|
200
|
-
const skillMdPath = join2(skillPath, "SKILL.md");
|
|
201
|
-
let description = "";
|
|
202
|
-
let metadata = {};
|
|
203
|
-
if (existsSync2(skillMdPath)) {
|
|
204
|
-
const fileContent = await readFile(skillMdPath, "utf-8");
|
|
205
|
-
const parsed = matter(fileContent);
|
|
206
|
-
description = parsed.data.description || "";
|
|
207
|
-
metadata = parsed.data;
|
|
208
|
-
}
|
|
209
|
-
if (!description) {
|
|
210
|
-
const packageJsonPath = join2(skillPath, "package.json");
|
|
211
|
-
const skillJsonPath = join2(skillPath, "skill.json");
|
|
212
|
-
if (existsSync2(skillJsonPath)) {
|
|
213
|
-
const content = await readFile(skillJsonPath, "utf-8");
|
|
214
|
-
const json = JSON.parse(content);
|
|
215
|
-
description = json.description || "";
|
|
216
|
-
metadata = { ...metadata, ...json };
|
|
217
|
-
} else if (existsSync2(packageJsonPath)) {
|
|
218
|
-
const content = await readFile(packageJsonPath, "utf-8");
|
|
219
|
-
const pkg = JSON.parse(content);
|
|
220
|
-
description = pkg.description || "";
|
|
221
|
-
metadata = { ...metadata, name: pkg.name, version: pkg.version };
|
|
254
|
+
});
|
|
255
|
+
if (!skill) {
|
|
256
|
+
throw new AppError(ErrorCode.NOT_FOUND, `Skill "${name}" not found`, 404);
|
|
222
257
|
}
|
|
258
|
+
return skill;
|
|
223
259
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
260
|
+
async parseSkillMetadata(skillPath) {
|
|
261
|
+
try {
|
|
262
|
+
const skillMdPath = join2(skillPath, "SKILL.md");
|
|
263
|
+
let description = "";
|
|
264
|
+
let metadata = {};
|
|
265
|
+
if (existsSync2(skillMdPath)) {
|
|
266
|
+
const fileContent = await readFile(skillMdPath, "utf-8");
|
|
267
|
+
const parsed = matter(fileContent);
|
|
268
|
+
description = parsed.data.description || "";
|
|
269
|
+
metadata = parsed.data;
|
|
270
|
+
}
|
|
271
|
+
if (!description) {
|
|
272
|
+
const packageJsonPath = join2(skillPath, "package.json");
|
|
273
|
+
const skillJsonPath = join2(skillPath, "skill.json");
|
|
274
|
+
if (existsSync2(skillJsonPath)) {
|
|
275
|
+
const content = await readFile(skillJsonPath, "utf-8");
|
|
276
|
+
const json = JSON.parse(content);
|
|
277
|
+
description = json.description || "";
|
|
278
|
+
metadata = { ...metadata, ...json };
|
|
279
|
+
} else if (existsSync2(packageJsonPath)) {
|
|
280
|
+
const content = await readFile(packageJsonPath, "utf-8");
|
|
281
|
+
const pkg = JSON.parse(content);
|
|
282
|
+
description = pkg.description || "";
|
|
283
|
+
metadata = { ...metadata, name: pkg.name, version: pkg.version };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return { description, metadata };
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.warn("Failed to parse skill metadata:", error);
|
|
289
|
+
return { description: "", metadata: {} };
|
|
290
|
+
}
|
|
243
291
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
tempPath = join2(TEMP_DIR, `git-clone-${Date.now()}`);
|
|
247
|
-
await mkdir(tempPath, { recursive: true });
|
|
248
|
-
console.log(`Cloning ${repoUrl} to temp path for extraction...`);
|
|
249
|
-
const git = simpleGit();
|
|
250
|
-
await git.clone(repoUrl, tempPath);
|
|
292
|
+
async createSkillFromGit(gitUrl, description) {
|
|
293
|
+
let tempPath = null;
|
|
251
294
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
295
|
+
const { repoUrl, subdir, skillName } = parseGitUrl(gitUrl);
|
|
296
|
+
const existingSkill = await prisma.skill.findUnique({
|
|
297
|
+
where: { name: skillName }
|
|
298
|
+
});
|
|
299
|
+
if (existingSkill) {
|
|
300
|
+
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${skillName}" already exists`, 409);
|
|
301
|
+
}
|
|
302
|
+
const { skills: skillsDir, temp: tempDir } = getPaths();
|
|
303
|
+
const skillPath = join2(skillsDir, skillName);
|
|
304
|
+
if (existsSync2(skillPath)) {
|
|
305
|
+
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${skillName}" already exists`, 409);
|
|
306
|
+
}
|
|
307
|
+
let commitHash = "";
|
|
308
|
+
if (subdir) {
|
|
309
|
+
tempPath = join2(tempDir, `git-clone-${Date.now()}`);
|
|
310
|
+
await mkdir(tempPath, { recursive: true });
|
|
311
|
+
console.log(`Cloning ${repoUrl} to temp path for extraction...`);
|
|
312
|
+
const git = simpleGit();
|
|
313
|
+
await git.clone(repoUrl, tempPath);
|
|
314
|
+
try {
|
|
315
|
+
commitHash = await simpleGit(tempPath).revparse(["HEAD"]);
|
|
316
|
+
console.log(`Captured commit hash: ${commitHash}`);
|
|
317
|
+
} catch (e) {
|
|
318
|
+
console.warn("Failed to capture commit hash:", e);
|
|
319
|
+
}
|
|
320
|
+
const sourcePath = join2(tempPath, subdir);
|
|
321
|
+
if (!existsSync2(sourcePath)) {
|
|
322
|
+
throw new AppError(
|
|
323
|
+
ErrorCode.GIT_ERROR,
|
|
324
|
+
`Subdirectory "${subdir}" not found in repository`,
|
|
325
|
+
400
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
const hasSkillMd = existsSync2(join2(sourcePath, "SKILL.md"));
|
|
329
|
+
const hasSkillJson = existsSync2(join2(sourcePath, "skill.json"));
|
|
330
|
+
const hasPackageJson = existsSync2(join2(sourcePath, "package.json"));
|
|
331
|
+
if (!hasSkillMd && !hasSkillJson && !hasPackageJson) {
|
|
332
|
+
throw new AppError(
|
|
333
|
+
ErrorCode.VALIDATION_ERROR,
|
|
334
|
+
`Invalid skill structure: "${subdir}" does not contain SKILL.md, skill.json, or package.json`,
|
|
335
|
+
400
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
await mkdir(skillPath, { recursive: true });
|
|
339
|
+
await cp(sourcePath, skillPath, { recursive: true });
|
|
340
|
+
} else {
|
|
341
|
+
if (!existsSync2(skillPath)) {
|
|
342
|
+
await mkdir(skillPath, { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
const git = simpleGit();
|
|
345
|
+
console.log(`Cloning ${gitUrl} to ${skillPath}...`);
|
|
346
|
+
await git.clone(gitUrl, skillPath);
|
|
347
|
+
try {
|
|
348
|
+
commitHash = await git.cwd(skillPath).revparse(["HEAD"]);
|
|
349
|
+
console.log(`Captured commit hash: ${commitHash}`);
|
|
350
|
+
} catch (e) {
|
|
351
|
+
console.warn("Failed to capture commit hash:", e);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const parsed = await this.parseSkillMetadata(skillPath);
|
|
355
|
+
const skill = await prisma.skill.create({
|
|
356
|
+
data: {
|
|
357
|
+
name: skillName,
|
|
358
|
+
source: "git",
|
|
359
|
+
sourceUrl: gitUrl,
|
|
360
|
+
repoUrl,
|
|
361
|
+
commitHash,
|
|
362
|
+
description: description || parsed.description || "",
|
|
363
|
+
storagePath: skillPath,
|
|
364
|
+
metadata: JSON.stringify(parsed.metadata)
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
return skill;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
throw error;
|
|
370
|
+
} finally {
|
|
371
|
+
if (tempPath && existsSync2(tempPath)) {
|
|
372
|
+
await rm(tempPath, { recursive: true, force: true }).catch(console.error);
|
|
373
|
+
}
|
|
256
374
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
`Subdirectory "${subdir}" not found in repository`,
|
|
262
|
-
400
|
|
263
|
-
);
|
|
375
|
+
}
|
|
376
|
+
async createSkillFromDirectory(path, name, description) {
|
|
377
|
+
if (!existsSync2(path)) {
|
|
378
|
+
throw new AppError(ErrorCode.FILE_SYSTEM_ERROR, `Source path not found: ${path}`, 400);
|
|
264
379
|
}
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
);
|
|
380
|
+
const { skills: skillsDir } = getPaths();
|
|
381
|
+
const skillName = name || basename(path);
|
|
382
|
+
const skillPath = join2(skillsDir, skillName);
|
|
383
|
+
const existingSkill = await prisma.skill.findUnique({
|
|
384
|
+
where: { name: skillName }
|
|
385
|
+
});
|
|
386
|
+
if (existingSkill) {
|
|
387
|
+
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${skillName}" already exists`, 409);
|
|
274
388
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
} else {
|
|
278
|
-
if (!existsSync2(skillPath)) {
|
|
279
|
-
await mkdir(skillPath, { recursive: true });
|
|
389
|
+
if (existsSync2(skillPath)) {
|
|
390
|
+
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${skillName}" already exists`, 409);
|
|
280
391
|
}
|
|
281
|
-
const git = simpleGit();
|
|
282
|
-
console.log(`Cloning ${gitUrl} to ${skillPath}...`);
|
|
283
|
-
await git.clone(gitUrl, skillPath);
|
|
284
392
|
try {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
393
|
+
await mkdir(skillPath, { recursive: true });
|
|
394
|
+
await cp(path, skillPath, { recursive: true });
|
|
395
|
+
const parsed = await this.parseSkillMetadata(skillPath);
|
|
396
|
+
const skill = await prisma.skill.create({
|
|
397
|
+
data: {
|
|
398
|
+
name: skillName,
|
|
399
|
+
source: "local",
|
|
400
|
+
description: description || parsed.description || "",
|
|
401
|
+
storagePath: skillPath,
|
|
402
|
+
metadata: JSON.stringify(parsed.metadata)
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
return skill;
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (existsSync2(skillPath)) {
|
|
408
|
+
await rm(skillPath, { recursive: true, force: true }).catch(() => {
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
289
412
|
}
|
|
290
413
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Helper to handle cases where the archive contains a single top-level directory.
|
|
416
|
+
* Moves contents up one level if that's the case.
|
|
417
|
+
*/
|
|
418
|
+
async stripTopLevelDirectory(targetPath) {
|
|
419
|
+
const { readdir: readdir2, rename, rmdir, stat: stat2 } = await import("fs/promises");
|
|
420
|
+
const items = await readdir2(targetPath);
|
|
421
|
+
const validItems = items.filter((i) => i !== ".DS_Store" && i !== "__MACOSX");
|
|
422
|
+
if (validItems.length === 1) {
|
|
423
|
+
const topLevelItem = validItems[0];
|
|
424
|
+
const topLevelPath = join2(targetPath, topLevelItem);
|
|
425
|
+
const stats = await stat2(topLevelPath);
|
|
426
|
+
if (stats.isDirectory()) {
|
|
427
|
+
console.log(`Striping top-level directory: ${topLevelItem}`);
|
|
428
|
+
const subItems = await readdir2(topLevelPath);
|
|
429
|
+
for (const item of subItems) {
|
|
430
|
+
await rename(join2(topLevelPath, item), join2(targetPath, item));
|
|
431
|
+
}
|
|
432
|
+
await rmdir(topLevelPath);
|
|
433
|
+
}
|
|
302
434
|
}
|
|
303
|
-
});
|
|
304
|
-
return skill;
|
|
305
|
-
} catch (error) {
|
|
306
|
-
throw error;
|
|
307
|
-
} finally {
|
|
308
|
-
if (tempPath && existsSync2(tempPath)) {
|
|
309
|
-
await rm(tempPath, { recursive: true, force: true }).catch(console.error);
|
|
310
435
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
const skillName = name || basename(path);
|
|
318
|
-
const skillPath = join2(SKILLS_DIR, skillName);
|
|
319
|
-
const existingSkill = await prisma.skill.findUnique({
|
|
320
|
-
where: { name: skillName }
|
|
321
|
-
});
|
|
322
|
-
if (existingSkill) {
|
|
323
|
-
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${skillName}" already exists`, 409);
|
|
324
|
-
}
|
|
325
|
-
if (existsSync2(skillPath)) {
|
|
326
|
-
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${skillName}" already exists`, 409);
|
|
327
|
-
}
|
|
328
|
-
try {
|
|
329
|
-
await mkdir(skillPath, { recursive: true });
|
|
330
|
-
await cp(path, skillPath, { recursive: true });
|
|
331
|
-
const parsed = await this.parseSkillMetadata(skillPath);
|
|
332
|
-
const skill = await prisma.skill.create({
|
|
333
|
-
data: {
|
|
334
|
-
name: skillName,
|
|
335
|
-
source: "local",
|
|
336
|
-
description: description || parsed.description || "",
|
|
337
|
-
storagePath: skillPath,
|
|
338
|
-
metadata: JSON.stringify(parsed.metadata)
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
return skill;
|
|
342
|
-
} catch (error) {
|
|
343
|
-
if (existsSync2(skillPath)) {
|
|
344
|
-
await rm(skillPath, { recursive: true, force: true }).catch(() => {
|
|
436
|
+
async createSkillFromLocal(name, zipPath, description) {
|
|
437
|
+
const { skills: skillsDir, temp: tempDir } = getPaths();
|
|
438
|
+
const skillPath = join2(skillsDir, name);
|
|
439
|
+
const existingSkill = await prisma.skill.findUnique({
|
|
440
|
+
where: { name }
|
|
345
441
|
});
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
442
|
+
if (existingSkill) {
|
|
443
|
+
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${name}" already exists`, 409);
|
|
444
|
+
}
|
|
445
|
+
if (existsSync2(skillPath)) {
|
|
446
|
+
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
|
|
447
|
+
}
|
|
448
|
+
const extractPath = join2(tempDir, `extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
|
|
449
|
+
await mkdir(extractPath, { recursive: true });
|
|
450
|
+
try {
|
|
451
|
+
const zip = new AdmZip(zipPath);
|
|
452
|
+
zip.extractAllTo(extractPath, true);
|
|
453
|
+
await this.stripTopLevelDirectory(extractPath);
|
|
454
|
+
const parsed = await this.parseSkillMetadata(extractPath);
|
|
455
|
+
if (existsSync2(skillPath)) {
|
|
456
|
+
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
|
|
457
|
+
}
|
|
458
|
+
await import("fs/promises").then((fs) => fs.rename(extractPath, skillPath));
|
|
459
|
+
const skill = await prisma.skill.create({
|
|
460
|
+
data: {
|
|
461
|
+
name,
|
|
462
|
+
source: "local",
|
|
463
|
+
description: description || parsed.description || "",
|
|
464
|
+
storagePath: skillPath,
|
|
465
|
+
metadata: JSON.stringify(parsed.metadata)
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
return skill;
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (existsSync2(skillPath)) {
|
|
471
|
+
await rm(skillPath, { recursive: true, force: true }).catch(() => {
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (existsSync2(extractPath)) {
|
|
475
|
+
await rm(extractPath, { recursive: true, force: true }).catch(() => {
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
throw error;
|
|
367
479
|
}
|
|
368
|
-
await rmdir(topLevelPath);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
async createSkillFromLocal(name, zipPath, description) {
|
|
373
|
-
const skillPath = join2(SKILLS_DIR, name);
|
|
374
|
-
const existingSkill = await prisma.skill.findUnique({
|
|
375
|
-
where: { name }
|
|
376
|
-
});
|
|
377
|
-
if (existingSkill) {
|
|
378
|
-
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill "${name}" already exists`, 409);
|
|
379
|
-
}
|
|
380
|
-
if (existsSync2(skillPath)) {
|
|
381
|
-
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
|
|
382
|
-
}
|
|
383
|
-
const extractPath = join2(TEMP_DIR, `extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
|
|
384
|
-
await mkdir(extractPath, { recursive: true });
|
|
385
|
-
try {
|
|
386
|
-
const zip = new AdmZip(zipPath);
|
|
387
|
-
zip.extractAllTo(extractPath, true);
|
|
388
|
-
await this.stripTopLevelDirectory(extractPath);
|
|
389
|
-
const parsed = await this.parseSkillMetadata(extractPath);
|
|
390
|
-
if (existsSync2(skillPath)) {
|
|
391
|
-
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
|
|
392
480
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
481
|
+
/**
|
|
482
|
+
* Create a skill from a tar.gz bundle (marketplace install)
|
|
483
|
+
* @param bundlePath - Path to the .tar.gz bundle
|
|
484
|
+
* @param originalName - Original skill name
|
|
485
|
+
* @param description - Optional skill description
|
|
486
|
+
*/
|
|
487
|
+
async createSkillFromBundle(bundlePath, originalName, description) {
|
|
488
|
+
let name = originalName;
|
|
489
|
+
let counter = 1;
|
|
490
|
+
while (await prisma.skill.findUnique({ where: { name } })) {
|
|
491
|
+
name = `${originalName}-${counter}`;
|
|
492
|
+
counter++;
|
|
401
493
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
494
|
+
const { skills: skillsDir, temp: tempDir } = getPaths();
|
|
495
|
+
const skillPath = join2(skillsDir, name);
|
|
496
|
+
const extractPath = join2(tempDir, `extract-bundle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
|
|
497
|
+
await mkdir(extractPath, { recursive: true });
|
|
498
|
+
try {
|
|
499
|
+
const { createReadStream: createReadStream2 } = await import("fs");
|
|
500
|
+
const { createGunzip: createGunzip2 } = await import("zlib");
|
|
501
|
+
const tar = await import("tar");
|
|
502
|
+
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
503
|
+
await pipeline2(
|
|
504
|
+
createReadStream2(bundlePath),
|
|
505
|
+
createGunzip2(),
|
|
506
|
+
tar.extract({ cwd: extractPath })
|
|
507
|
+
);
|
|
508
|
+
await this.stripTopLevelDirectory(extractPath);
|
|
509
|
+
const parsed = await this.parseSkillMetadata(extractPath);
|
|
510
|
+
if (existsSync2(skillPath)) {
|
|
511
|
+
throw new AppError(ErrorCode.ALREADY_EXISTS, `Skill directory "${name}" already exists`, 409);
|
|
512
|
+
}
|
|
513
|
+
await import("fs/promises").then((fs) => fs.rename(extractPath, skillPath));
|
|
514
|
+
const skill = await prisma.skill.create({
|
|
515
|
+
data: {
|
|
516
|
+
name,
|
|
517
|
+
source: "local",
|
|
518
|
+
description: description || parsed.description || "",
|
|
519
|
+
storagePath: skillPath,
|
|
520
|
+
metadata: JSON.stringify(parsed.metadata)
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
console.log(`\u{1F4E6} Installed skill "${name}" from bundle`);
|
|
524
|
+
return skill;
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (existsSync2(skillPath)) {
|
|
527
|
+
await rm(skillPath, { recursive: true, force: true }).catch(() => {
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
if (existsSync2(extractPath)) {
|
|
531
|
+
await rm(extractPath, { recursive: true, force: true }).catch(() => {
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async updateSkill(id, data) {
|
|
538
|
+
const skill = await this.getSkillById(id);
|
|
539
|
+
const updatedSkill = await prisma.skill.update({
|
|
540
|
+
where: { id },
|
|
541
|
+
data: {
|
|
542
|
+
...data.name && { name: data.name },
|
|
543
|
+
...data.description !== void 0 && { description: data.description },
|
|
544
|
+
...data.metadata && { metadata: JSON.stringify(data.metadata) }
|
|
545
|
+
}
|
|
407
546
|
});
|
|
547
|
+
return updatedSkill;
|
|
408
548
|
}
|
|
409
|
-
|
|
410
|
-
|
|
549
|
+
async deleteSkill(id, removeFiles = true) {
|
|
550
|
+
const skill = await this.getSkillById(id);
|
|
551
|
+
if (skill.linkedWorkspaces && skill.linkedWorkspaces.length > 0) {
|
|
552
|
+
for (const link of skill.linkedWorkspaces) {
|
|
553
|
+
try {
|
|
554
|
+
const workspacePath = link.workspace.path;
|
|
555
|
+
const symlinkPath = join2(workspacePath, skill.name);
|
|
556
|
+
if (existsSync2(symlinkPath)) {
|
|
557
|
+
const stats = await import("fs/promises").then((fs) => fs.lstat(symlinkPath));
|
|
558
|
+
if (stats.isSymbolicLink()) {
|
|
559
|
+
await unlink(symlinkPath);
|
|
560
|
+
console.log(`Removed symlink for ${skill.name} in workspace ${workspacePath}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
} catch (error) {
|
|
564
|
+
console.warn(`Failed to remove symlink for ${skill.name} in workspace ${link.workspace.path}:`, error);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
await prisma.skill.delete({
|
|
569
|
+
where: { id }
|
|
411
570
|
});
|
|
571
|
+
if (removeFiles && skill.storagePath) {
|
|
572
|
+
await rm(skill.storagePath, { recursive: true, force: true }).catch((err) => {
|
|
573
|
+
console.warn(`Failed to delete skill directory ${skill.storagePath}:`, err);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
return { success: true, message: "Skill deleted successfully" };
|
|
412
577
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
578
|
+
// Check for updates
|
|
579
|
+
async checkForUpdate(id) {
|
|
580
|
+
const skill = await this.getSkillById(id);
|
|
581
|
+
if (skill.source !== "git" || !skill.repoUrl) {
|
|
582
|
+
return {
|
|
583
|
+
hasUpdate: false,
|
|
584
|
+
currentHash: skill.commitHash,
|
|
585
|
+
remoteHash: null,
|
|
586
|
+
message: "Not a git skill or missing repo URL"
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const { temp: tempDir } = getPaths();
|
|
590
|
+
const tempPath = join2(tempDir, `check-update-${Date.now()}`);
|
|
591
|
+
await mkdir(tempPath, { recursive: true });
|
|
592
|
+
try {
|
|
593
|
+
const git = simpleGit();
|
|
594
|
+
console.log(`Checking updates for ${skill.name} from ${skill.repoUrl}...`);
|
|
595
|
+
const remote = await git.listRemote([skill.repoUrl, "HEAD"]);
|
|
596
|
+
if (!remote) {
|
|
597
|
+
throw new Error("Failed to get remote HEAD");
|
|
598
|
+
}
|
|
599
|
+
const remoteHash = remote.split(" ")[0];
|
|
600
|
+
const hasUpdate = remoteHash !== skill.commitHash;
|
|
601
|
+
await prisma.skill.update({
|
|
602
|
+
where: { id },
|
|
603
|
+
data: {
|
|
604
|
+
lastUpdateCheck: /* @__PURE__ */ new Date(),
|
|
605
|
+
updateAvailable: hasUpdate
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
return {
|
|
609
|
+
hasUpdate,
|
|
610
|
+
currentHash: skill.commitHash,
|
|
611
|
+
remoteHash
|
|
612
|
+
};
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.error("Update check error:", error);
|
|
615
|
+
throw new AppError(ErrorCode.GIT_ERROR, `Failed to check for updates: ${error.message}`, 500);
|
|
616
|
+
} finally {
|
|
617
|
+
await rm(tempPath, { recursive: true, force: true }).catch(() => {
|
|
618
|
+
});
|
|
619
|
+
}
|
|
446
620
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
metadata: JSON.stringify(parsed.metadata)
|
|
621
|
+
async checkUpdates(ids) {
|
|
622
|
+
const whereClause = {
|
|
623
|
+
source: "git",
|
|
624
|
+
repoUrl: { not: null }
|
|
625
|
+
};
|
|
626
|
+
if (ids && ids.length > 0) {
|
|
627
|
+
whereClause.id = { in: ids };
|
|
455
628
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
629
|
+
const skills = await prisma.skill.findMany({ where: whereClause });
|
|
630
|
+
const results = {};
|
|
631
|
+
console.log(`Checking updates for ${skills.length} skills...`);
|
|
632
|
+
await Promise.all(skills.map(async (skill) => {
|
|
633
|
+
try {
|
|
634
|
+
const result = await this.checkForUpdate(skill.id);
|
|
635
|
+
results[skill.id] = result;
|
|
636
|
+
} catch (e) {
|
|
637
|
+
results[skill.id] = { error: e.message };
|
|
638
|
+
}
|
|
639
|
+
}));
|
|
640
|
+
return results;
|
|
463
641
|
}
|
|
464
|
-
|
|
465
|
-
|
|
642
|
+
async refreshMetadata(id) {
|
|
643
|
+
const skill = await this.getSkillById(id);
|
|
644
|
+
const parsed = await this.parseSkillMetadata(skill.storagePath);
|
|
645
|
+
const updated = await prisma.skill.update({
|
|
646
|
+
where: { id },
|
|
647
|
+
data: {
|
|
648
|
+
description: parsed.description,
|
|
649
|
+
metadata: JSON.stringify(parsed.metadata)
|
|
650
|
+
}
|
|
466
651
|
});
|
|
652
|
+
return updated;
|
|
467
653
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
data: {
|
|
476
|
-
...data.name && { name: data.name },
|
|
477
|
-
...data.description !== void 0 && { description: data.description },
|
|
478
|
-
...data.metadata && { metadata: JSON.stringify(data.metadata) }
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
return updatedSkill;
|
|
482
|
-
}
|
|
483
|
-
async deleteSkill(id, removeFiles = true) {
|
|
484
|
-
const skill = await this.getSkillById(id);
|
|
485
|
-
if (skill.linkedWorkspaces && skill.linkedWorkspaces.length > 0) {
|
|
486
|
-
for (const link of skill.linkedWorkspaces) {
|
|
654
|
+
async upgradeSkill(id) {
|
|
655
|
+
const skill = await this.getSkillById(id);
|
|
656
|
+
if (skill.source !== "git" || !skill.sourceUrl) {
|
|
657
|
+
throw new AppError(ErrorCode.VALIDATION_ERROR, "Cannot upgrade non-git skill", 400);
|
|
658
|
+
}
|
|
659
|
+
let tempPath = null;
|
|
660
|
+
let tempSkillPath = null;
|
|
487
661
|
try {
|
|
488
|
-
const
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
662
|
+
const { repoUrl, subdir } = parseGitUrl(skill.sourceUrl);
|
|
663
|
+
const { temp: tempDir } = getPaths();
|
|
664
|
+
tempPath = join2(tempDir, `upgrade-${Date.now()}`);
|
|
665
|
+
await mkdir(tempPath, { recursive: true });
|
|
666
|
+
const git = simpleGit();
|
|
667
|
+
console.log(`Cloning ${repoUrl} to temp for upgrade...`);
|
|
668
|
+
await git.clone(repoUrl, tempPath);
|
|
669
|
+
let commitHash = "";
|
|
670
|
+
try {
|
|
671
|
+
commitHash = await simpleGit(tempPath).revparse(["HEAD"]);
|
|
672
|
+
} catch (e) {
|
|
673
|
+
console.warn("Failed to capture commit hash during upgrade:", e);
|
|
674
|
+
}
|
|
675
|
+
let sourcePath = tempPath;
|
|
676
|
+
if (subdir) {
|
|
677
|
+
sourcePath = join2(tempPath, subdir);
|
|
678
|
+
if (!existsSync2(sourcePath)) {
|
|
679
|
+
throw new AppError(ErrorCode.GIT_ERROR, `Subdirectory "${subdir}" not found`, 400);
|
|
495
680
|
}
|
|
496
681
|
}
|
|
682
|
+
const hasSkillMd = existsSync2(join2(sourcePath, "SKILL.md"));
|
|
683
|
+
const hasSkillJson = existsSync2(join2(sourcePath, "skill.json"));
|
|
684
|
+
const hasPackageJson = existsSync2(join2(sourcePath, "package.json"));
|
|
685
|
+
if (!hasSkillMd && !hasSkillJson && !hasPackageJson) {
|
|
686
|
+
throw new AppError(ErrorCode.VALIDATION_ERROR, "Invalid skill structure in new version", 400);
|
|
687
|
+
}
|
|
688
|
+
const files = await import("fs/promises").then((fs) => fs.readdir(skill.storagePath));
|
|
689
|
+
for (const file of files) {
|
|
690
|
+
await rm(join2(skill.storagePath, file), { recursive: true, force: true });
|
|
691
|
+
}
|
|
692
|
+
await cp(sourcePath, skill.storagePath, { recursive: true });
|
|
693
|
+
const parsed = await this.parseSkillMetadata(skill.storagePath);
|
|
694
|
+
const updatedSkill = await prisma.skill.update({
|
|
695
|
+
where: { id },
|
|
696
|
+
data: {
|
|
697
|
+
commitHash,
|
|
698
|
+
updateAvailable: false,
|
|
699
|
+
lastUpdateCheck: /* @__PURE__ */ new Date(),
|
|
700
|
+
installDate: /* @__PURE__ */ new Date(),
|
|
701
|
+
// Considering upgrade as reinstall? Or user prefer generic updatedAt
|
|
702
|
+
// Keeping installDate as "last installed/upgraded"
|
|
703
|
+
description: parsed.description,
|
|
704
|
+
metadata: JSON.stringify(parsed.metadata)
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
return updatedSkill;
|
|
497
708
|
} catch (error) {
|
|
498
|
-
console.
|
|
709
|
+
console.error("Upgrade error:", error);
|
|
710
|
+
throw new AppError(ErrorCode.GIT_ERROR, `Failed to upgrade skill: ${error.message}`, 500);
|
|
711
|
+
} finally {
|
|
712
|
+
if (tempPath) await rm(tempPath, { recursive: true, force: true }).catch(() => {
|
|
713
|
+
});
|
|
499
714
|
}
|
|
500
715
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
console.warn(`Failed to delete skill directory ${skill.storagePath}:`, err);
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
return { success: true, message: "Skill deleted successfully" };
|
|
511
|
-
}
|
|
512
|
-
// Check for updates
|
|
513
|
-
async checkForUpdate(id) {
|
|
514
|
-
const skill = await this.getSkillById(id);
|
|
515
|
-
if (skill.source !== "git" || !skill.repoUrl) {
|
|
516
|
-
return {
|
|
517
|
-
hasUpdate: false,
|
|
518
|
-
currentHash: skill.commitHash,
|
|
519
|
-
remoteHash: null,
|
|
520
|
-
message: "Not a git skill or missing repo URL"
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
const tempPath = join2(TEMP_DIR, `check-update-${Date.now()}`);
|
|
524
|
-
await mkdir(tempPath, { recursive: true });
|
|
525
|
-
try {
|
|
526
|
-
const git = simpleGit();
|
|
527
|
-
console.log(`Checking updates for ${skill.name} from ${skill.repoUrl}...`);
|
|
528
|
-
const remote = await git.listRemote([skill.repoUrl, "HEAD"]);
|
|
529
|
-
if (!remote) {
|
|
530
|
-
throw new Error("Failed to get remote HEAD");
|
|
531
|
-
}
|
|
532
|
-
const remoteHash = remote.split(" ")[0];
|
|
533
|
-
const hasUpdate = remoteHash !== skill.commitHash;
|
|
534
|
-
await prisma.skill.update({
|
|
535
|
-
where: { id },
|
|
536
|
-
data: {
|
|
537
|
-
lastUpdateCheck: /* @__PURE__ */ new Date(),
|
|
538
|
-
updateAvailable: hasUpdate
|
|
716
|
+
async scanForSkills() {
|
|
717
|
+
const { skills: skillsDir } = getPaths();
|
|
718
|
+
console.log(`Scanning for skills in ${skillsDir}...`);
|
|
719
|
+
if (!existsSync2(skillsDir)) {
|
|
720
|
+
console.log("Skills directory does not exist, skipping scan.");
|
|
721
|
+
return;
|
|
539
722
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
tempPath = join2(TEMP_DIR, `upgrade-${Date.now()}`);
|
|
597
|
-
await mkdir(tempPath, { recursive: true });
|
|
598
|
-
const git = simpleGit();
|
|
599
|
-
console.log(`Cloning ${repoUrl} to temp for upgrade...`);
|
|
600
|
-
await git.clone(repoUrl, tempPath);
|
|
601
|
-
let commitHash = "";
|
|
602
|
-
try {
|
|
603
|
-
commitHash = await simpleGit(tempPath).revparse(["HEAD"]);
|
|
604
|
-
} catch (e) {
|
|
605
|
-
console.warn("Failed to capture commit hash during upgrade:", e);
|
|
606
|
-
}
|
|
607
|
-
let sourcePath = tempPath;
|
|
608
|
-
if (subdir) {
|
|
609
|
-
sourcePath = join2(tempPath, subdir);
|
|
610
|
-
if (!existsSync2(sourcePath)) {
|
|
611
|
-
throw new AppError(ErrorCode.GIT_ERROR, `Subdirectory "${subdir}" not found`, 400);
|
|
723
|
+
const entries = await import("fs/promises").then((fs) => fs.readdir(skillsDir, { withFileTypes: true }));
|
|
724
|
+
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
725
|
+
console.log(`Found ${directories.length} directories in skills folder.`);
|
|
726
|
+
let importedCount = 0;
|
|
727
|
+
for (const dirName of directories) {
|
|
728
|
+
const skillPath = join2(skillsDir, dirName);
|
|
729
|
+
const existingSkill = await prisma.skill.findUnique({
|
|
730
|
+
where: { name: dirName }
|
|
731
|
+
});
|
|
732
|
+
if (existingSkill) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
const hasSkillMd = existsSync2(join2(skillPath, "SKILL.md"));
|
|
736
|
+
const hasPackageJson = existsSync2(join2(skillPath, "package.json"));
|
|
737
|
+
const hasSkillJson = existsSync2(join2(skillPath, "skill.json"));
|
|
738
|
+
if (!hasSkillMd && !hasPackageJson && !hasSkillJson) {
|
|
739
|
+
console.log(`Skipping ${dirName}: No skill metadata found.`);
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
console.log(`Importing skill: ${dirName}`);
|
|
744
|
+
const parsed = await this.parseSkillMetadata(skillPath);
|
|
745
|
+
let source = "local";
|
|
746
|
+
let sourceUrl = void 0;
|
|
747
|
+
let repoUrl = void 0;
|
|
748
|
+
let commitHash = void 0;
|
|
749
|
+
if (existsSync2(join2(skillPath, ".git"))) {
|
|
750
|
+
source = "git";
|
|
751
|
+
try {
|
|
752
|
+
const git = simpleGit(skillPath);
|
|
753
|
+
const remotes = await git.getRemotes(true);
|
|
754
|
+
if (remotes.length > 0) {
|
|
755
|
+
sourceUrl = remotes[0].refs.fetch;
|
|
756
|
+
repoUrl = sourceUrl;
|
|
757
|
+
}
|
|
758
|
+
commitHash = await git.revparse(["HEAD"]);
|
|
759
|
+
} catch (e) {
|
|
760
|
+
console.warn(`Failed to read git info for ${dirName}:`, e);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
await prisma.skill.create({
|
|
764
|
+
data: {
|
|
765
|
+
name: dirName,
|
|
766
|
+
source,
|
|
767
|
+
sourceUrl,
|
|
768
|
+
repoUrl,
|
|
769
|
+
commitHash,
|
|
770
|
+
description: parsed.description,
|
|
771
|
+
storagePath: skillPath,
|
|
772
|
+
metadata: JSON.stringify(parsed.metadata)
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
importedCount++;
|
|
776
|
+
} catch (error) {
|
|
777
|
+
console.error(`Failed to import skill ${dirName}:`, error);
|
|
778
|
+
}
|
|
612
779
|
}
|
|
780
|
+
console.log(`Scan complete. Imported ${importedCount} new skills.`);
|
|
781
|
+
return importedCount;
|
|
613
782
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const hasPackageJson = existsSync2(join2(sourcePath, "package.json"));
|
|
617
|
-
if (!hasSkillMd && !hasSkillJson && !hasPackageJson) {
|
|
618
|
-
throw new AppError(ErrorCode.VALIDATION_ERROR, "Invalid skill structure in new version", 400);
|
|
619
|
-
}
|
|
620
|
-
const files = await import("fs/promises").then((fs) => fs.readdir(skill.storagePath));
|
|
621
|
-
for (const file of files) {
|
|
622
|
-
await rm(join2(skill.storagePath, file), { recursive: true, force: true });
|
|
623
|
-
}
|
|
624
|
-
await cp(sourcePath, skill.storagePath, { recursive: true });
|
|
625
|
-
const parsed = await this.parseSkillMetadata(skill.storagePath);
|
|
626
|
-
const updatedSkill = await prisma.skill.update({
|
|
627
|
-
where: { id },
|
|
628
|
-
data: {
|
|
629
|
-
commitHash,
|
|
630
|
-
updateAvailable: false,
|
|
631
|
-
lastUpdateCheck: /* @__PURE__ */ new Date(),
|
|
632
|
-
installDate: /* @__PURE__ */ new Date(),
|
|
633
|
-
// Considering upgrade as reinstall? Or user prefer generic updatedAt
|
|
634
|
-
// Keeping installDate as "last installed/upgraded"
|
|
635
|
-
description: parsed.description,
|
|
636
|
-
metadata: JSON.stringify(parsed.metadata)
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
return updatedSkill;
|
|
640
|
-
} catch (error) {
|
|
641
|
-
console.error("Upgrade error:", error);
|
|
642
|
-
throw new AppError(ErrorCode.GIT_ERROR, `Failed to upgrade skill: ${error.message}`, 500);
|
|
643
|
-
} finally {
|
|
644
|
-
if (tempPath) await rm(tempPath, { recursive: true, force: true }).catch(() => {
|
|
645
|
-
});
|
|
646
|
-
}
|
|
783
|
+
};
|
|
784
|
+
skillService = new SkillService();
|
|
647
785
|
}
|
|
648
|
-
};
|
|
649
|
-
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
// src/index.ts
|
|
789
|
+
import express from "express";
|
|
790
|
+
import cors from "cors";
|
|
791
|
+
import dotenv2 from "dotenv";
|
|
792
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
793
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
794
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
795
|
+
import { existsSync as existsSync9 } from "fs";
|
|
796
|
+
|
|
797
|
+
// src/routes/skills.ts
|
|
798
|
+
init_skillService();
|
|
799
|
+
import { Router } from "express";
|
|
800
|
+
import multer from "multer";
|
|
801
|
+
import { join as join4 } from "path";
|
|
802
|
+
import { existsSync as existsSync4 } from "fs";
|
|
803
|
+
import { mkdir as mkdir3, rm as rm3 } from "fs/promises";
|
|
650
804
|
|
|
651
805
|
// src/services/workspaceService.ts
|
|
806
|
+
init_db();
|
|
807
|
+
init_errorHandler();
|
|
808
|
+
init_dist();
|
|
652
809
|
import { join as join3 } from "path";
|
|
653
810
|
import { homedir } from "os";
|
|
654
811
|
import { symlink, unlink as unlink2, rm as rm2, mkdir as mkdir2, appendFile, readFile as readFile2 } from "fs/promises";
|
|
@@ -971,17 +1128,17 @@ ${relativeSkillsPath}/
|
|
|
971
1128
|
*/
|
|
972
1129
|
async migrateExistingSkills(workspaceId, skillNames) {
|
|
973
1130
|
const workspace = await this.getWorkspaceById(workspaceId);
|
|
974
|
-
const
|
|
975
|
-
const
|
|
976
|
-
if (!existsSync3(
|
|
977
|
-
await mkdir2(
|
|
1131
|
+
const SKILLVERSE_HOME = process.env.SKILLVERSE_HOME || join3(homedir(), ".skillverse");
|
|
1132
|
+
const SKILLS_DIR = process.env.SKILLS_DIR || join3(SKILLVERSE_HOME, "skills");
|
|
1133
|
+
if (!existsSync3(SKILLS_DIR)) {
|
|
1134
|
+
await mkdir2(SKILLS_DIR, { recursive: true });
|
|
978
1135
|
}
|
|
979
1136
|
const { rename, readFile: readFile3, cp: cp2 } = await import("fs/promises");
|
|
980
1137
|
const migrated = [];
|
|
981
1138
|
const errors = [];
|
|
982
1139
|
for (const skillName of skillNames) {
|
|
983
1140
|
const sourcePath = join3(workspace.path, skillName);
|
|
984
|
-
const targetPath = join3(
|
|
1141
|
+
const targetPath = join3(SKILLS_DIR, skillName);
|
|
985
1142
|
try {
|
|
986
1143
|
if (!existsSync3(sourcePath)) {
|
|
987
1144
|
errors.push(`${skillName}: Source path not found`);
|
|
@@ -1053,13 +1210,13 @@ var workspaceService = new WorkspaceService();
|
|
|
1053
1210
|
|
|
1054
1211
|
// src/routes/skills.ts
|
|
1055
1212
|
var router = Router();
|
|
1056
|
-
var
|
|
1057
|
-
if (!existsSync4(
|
|
1058
|
-
mkdir3(
|
|
1213
|
+
var TEMP_DIR = process.env.TEMP_DIR || join4(process.env.HOME || "", ".skillverse", "temp");
|
|
1214
|
+
if (!existsSync4(TEMP_DIR)) {
|
|
1215
|
+
mkdir3(TEMP_DIR, { recursive: true });
|
|
1059
1216
|
}
|
|
1060
1217
|
var storage = multer.diskStorage({
|
|
1061
1218
|
destination: (req, file, cb) => {
|
|
1062
|
-
cb(null,
|
|
1219
|
+
cb(null, TEMP_DIR);
|
|
1063
1220
|
},
|
|
1064
1221
|
filename: (req, file, cb) => {
|
|
1065
1222
|
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
|
|
@@ -1434,6 +1591,8 @@ router2.post("/:id/migrate-skills", async (req, res, next) => {
|
|
|
1434
1591
|
var workspaces_default = router2;
|
|
1435
1592
|
|
|
1436
1593
|
// src/routes/marketplace.ts
|
|
1594
|
+
init_db();
|
|
1595
|
+
init_skillService();
|
|
1437
1596
|
import { Router as Router3 } from "express";
|
|
1438
1597
|
|
|
1439
1598
|
// src/services/bundleService.ts
|
|
@@ -1517,6 +1676,8 @@ var bundleService = {
|
|
|
1517
1676
|
};
|
|
1518
1677
|
|
|
1519
1678
|
// src/routes/marketplace.ts
|
|
1679
|
+
init_errorHandler();
|
|
1680
|
+
init_dist();
|
|
1520
1681
|
import { existsSync as existsSync6 } from "fs";
|
|
1521
1682
|
var router3 = Router3();
|
|
1522
1683
|
router3.get("/skills", async (req, res, next) => {
|
|
@@ -1730,6 +1891,7 @@ router3.delete("/unpublish/:skillId", async (req, res, next) => {
|
|
|
1730
1891
|
var marketplace_default = router3;
|
|
1731
1892
|
|
|
1732
1893
|
// src/routes/dashboard.ts
|
|
1894
|
+
init_db();
|
|
1733
1895
|
import { Router as Router4 } from "express";
|
|
1734
1896
|
var router4 = Router4();
|
|
1735
1897
|
router4.get("/stats", async (req, res, next) => {
|
|
@@ -1821,6 +1983,107 @@ router4.get("/activity", async (req, res, next) => {
|
|
|
1821
1983
|
});
|
|
1822
1984
|
var dashboard_default = router4;
|
|
1823
1985
|
|
|
1986
|
+
// src/routes/config.ts
|
|
1987
|
+
import { Router as Router5 } from "express";
|
|
1988
|
+
import { join as join6 } from "path";
|
|
1989
|
+
import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1990
|
+
var router5 = Router5();
|
|
1991
|
+
var HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
1992
|
+
var BOOTSTRAP_HOME = join6(HOME, ".skillverse");
|
|
1993
|
+
var CONFIG_FILE = join6(BOOTSTRAP_HOME, "config.json");
|
|
1994
|
+
function readConfig() {
|
|
1995
|
+
if (existsSync7(CONFIG_FILE)) {
|
|
1996
|
+
try {
|
|
1997
|
+
return JSON.parse(readFileSync2(CONFIG_FILE, "utf-8"));
|
|
1998
|
+
} catch (e) {
|
|
1999
|
+
console.error("Failed to read config.json:", e);
|
|
2000
|
+
return {};
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
return {};
|
|
2004
|
+
}
|
|
2005
|
+
router5.get("/", (req, res, next) => {
|
|
2006
|
+
try {
|
|
2007
|
+
const fileConfig = readConfig();
|
|
2008
|
+
const config3 = {
|
|
2009
|
+
// Show saved preference if available, otherwise current env/default
|
|
2010
|
+
skillverseHome: fileConfig.skillverseHome || process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME,
|
|
2011
|
+
// registryUrl is potentially in config.json already
|
|
2012
|
+
registryUrl: fileConfig.registryUrl || process.env.SKILLVERSE_REGISTRY || "http://localhost:4000"
|
|
2013
|
+
// ... add other configurable items
|
|
2014
|
+
};
|
|
2015
|
+
res.json({
|
|
2016
|
+
success: true,
|
|
2017
|
+
data: config3
|
|
2018
|
+
});
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
next(error);
|
|
2021
|
+
}
|
|
2022
|
+
});
|
|
2023
|
+
router5.post("/", async (req, res, next) => {
|
|
2024
|
+
try {
|
|
2025
|
+
const { skillverseHome, registryUrl, migrate } = req.body;
|
|
2026
|
+
const currentConfig = readConfig();
|
|
2027
|
+
const oldHome = process.env.SKILLVERSE_HOME || BOOTSTRAP_HOME;
|
|
2028
|
+
if (migrate && skillverseHome && skillverseHome !== oldHome) {
|
|
2029
|
+
console.log(`Migrating data from ${oldHome} to ${skillverseHome}...`);
|
|
2030
|
+
const { cp: cp2 } = await import("fs/promises");
|
|
2031
|
+
if (!existsSync7(skillverseHome)) {
|
|
2032
|
+
const { mkdir: mkdir6 } = await import("fs/promises");
|
|
2033
|
+
await mkdir6(skillverseHome, { recursive: true });
|
|
2034
|
+
}
|
|
2035
|
+
const copyDir = async (srcName) => {
|
|
2036
|
+
const src = join6(oldHome, srcName);
|
|
2037
|
+
const dest = join6(skillverseHome, srcName);
|
|
2038
|
+
if (existsSync7(src)) {
|
|
2039
|
+
console.log(`Copying ${srcName}...`);
|
|
2040
|
+
await cp2(src, dest, { recursive: true, force: true });
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
await copyDir("skills");
|
|
2044
|
+
await copyDir("marketplace");
|
|
2045
|
+
const dbSrc = join6(oldHome, "skillverse.db");
|
|
2046
|
+
const dbDest = join6(skillverseHome, "skillverse.db");
|
|
2047
|
+
if (existsSync7(dbSrc)) {
|
|
2048
|
+
console.log("Copying database...");
|
|
2049
|
+
await cp2(dbSrc, dbDest, { force: true });
|
|
2050
|
+
const walSrc = dbSrc + "-wal";
|
|
2051
|
+
if (existsSync7(walSrc)) await cp2(walSrc, dbDest + "-wal", { force: true });
|
|
2052
|
+
const shmSrc = dbSrc + "-shm";
|
|
2053
|
+
if (existsSync7(shmSrc)) await cp2(shmSrc, dbDest + "-shm", { force: true });
|
|
2054
|
+
console.log("Updating skill paths in new database...");
|
|
2055
|
+
const { execSync } = await import("child_process");
|
|
2056
|
+
const oldSkillsPath = join6(oldHome, "skills");
|
|
2057
|
+
const newSkillsPath = join6(skillverseHome, "skills");
|
|
2058
|
+
const updateQuery = `UPDATE Skill SET storagePath = replace(storagePath, '${oldSkillsPath}', '${newSkillsPath}') WHERE storagePath LIKE '${oldSkillsPath}%';`;
|
|
2059
|
+
try {
|
|
2060
|
+
execSync(`sqlite3 "${dbDest}" "${updateQuery}"`, { encoding: "utf-8" });
|
|
2061
|
+
console.log("\u2705 Updated skill paths in new database");
|
|
2062
|
+
} catch (sqlErr) {
|
|
2063
|
+
console.error("Failed to update skill paths:", sqlErr);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
const newConfig = {
|
|
2068
|
+
...currentConfig,
|
|
2069
|
+
...skillverseHome && { skillverseHome },
|
|
2070
|
+
...registryUrl && { registryUrl }
|
|
2071
|
+
};
|
|
2072
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
|
|
2073
|
+
res.json({
|
|
2074
|
+
success: true,
|
|
2075
|
+
data: newConfig,
|
|
2076
|
+
message: "Configuration saved. Restart server for changes to take effect."
|
|
2077
|
+
});
|
|
2078
|
+
} catch (error) {
|
|
2079
|
+
next(error);
|
|
2080
|
+
}
|
|
2081
|
+
});
|
|
2082
|
+
var config_default = router5;
|
|
2083
|
+
|
|
2084
|
+
// src/index.ts
|
|
2085
|
+
init_errorHandler();
|
|
2086
|
+
|
|
1824
2087
|
// src/middleware/logger.ts
|
|
1825
2088
|
function requestLogger(req, res, next) {
|
|
1826
2089
|
const start = Date.now();
|
|
@@ -1832,8 +2095,9 @@ function requestLogger(req, res, next) {
|
|
|
1832
2095
|
}
|
|
1833
2096
|
|
|
1834
2097
|
// src/lib/initDb.ts
|
|
1835
|
-
|
|
1836
|
-
import {
|
|
2098
|
+
init_db();
|
|
2099
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2100
|
+
import { join as join7, dirname as dirname2 } from "path";
|
|
1837
2101
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1838
2102
|
import { spawnSync } from "child_process";
|
|
1839
2103
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
@@ -1841,15 +2105,15 @@ var __dirname2 = dirname2(__filename2);
|
|
|
1841
2105
|
function getSchemaPath() {
|
|
1842
2106
|
const possiblePaths = [
|
|
1843
2107
|
// Prod: dist/bin.js -> dist/../prisma/schema.prisma (i.e. ROOT/prisma/schema.prisma)
|
|
1844
|
-
|
|
2108
|
+
join7(__dirname2, "../prisma/schema.prisma"),
|
|
1845
2109
|
// Dev: src/lib/initDb.ts -> src/prisma/schema.prisma ? No, src/../prisma -> server/prisma
|
|
1846
2110
|
// If __dirname is src/lib: ../../prisma/schema.prisma
|
|
1847
|
-
|
|
2111
|
+
join7(__dirname2, "../../prisma/schema.prisma"),
|
|
1848
2112
|
// Fallbacks
|
|
1849
|
-
|
|
1850
|
-
|
|
2113
|
+
join7(process.cwd(), "prisma/schema.prisma"),
|
|
2114
|
+
join7(process.cwd(), "server/prisma/schema.prisma")
|
|
1851
2115
|
];
|
|
1852
|
-
return possiblePaths.find((p) =>
|
|
2116
|
+
return possiblePaths.find((p) => existsSync8(p)) || null;
|
|
1853
2117
|
}
|
|
1854
2118
|
async function ensureDatabaseInitialized() {
|
|
1855
2119
|
try {
|
|
@@ -1874,12 +2138,12 @@ async function initializeDatabase() {
|
|
|
1874
2138
|
throw new Error("Could not find schema.prisma");
|
|
1875
2139
|
}
|
|
1876
2140
|
const possiblePrismaBins = [
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
2141
|
+
join7(__dirname2, "../node_modules/.bin/prisma"),
|
|
2142
|
+
join7(__dirname2, "../../node_modules/.bin/prisma"),
|
|
2143
|
+
join7(__dirname2, "../../../node_modules/.bin/prisma"),
|
|
2144
|
+
join7(process.cwd(), "node_modules/.bin/prisma")
|
|
1881
2145
|
];
|
|
1882
|
-
const prismaBin = possiblePrismaBins.find((p) =>
|
|
2146
|
+
const prismaBin = possiblePrismaBins.find((p) => existsSync8(p)) || "prisma";
|
|
1883
2147
|
console.log(` Using schema: ${schemaPath}`);
|
|
1884
2148
|
const result = spawnSync(prismaBin, ["db", "push", "--schema", schemaPath], {
|
|
1885
2149
|
stdio: "inherit",
|
|
@@ -1906,16 +2170,16 @@ app.use(express.json());
|
|
|
1906
2170
|
app.use(express.urlencoded({ extended: true }));
|
|
1907
2171
|
app.use(requestLogger);
|
|
1908
2172
|
var possiblePublicDirs = [
|
|
1909
|
-
|
|
2173
|
+
join8(__dirname3, "../public"),
|
|
1910
2174
|
// Production: dist/index.js -> public
|
|
1911
|
-
|
|
2175
|
+
join8(__dirname3, "../../public"),
|
|
1912
2176
|
// Dev tsx might resolve here
|
|
1913
|
-
|
|
2177
|
+
join8(process.cwd(), "public"),
|
|
1914
2178
|
// Fallback: relative to cwd
|
|
1915
|
-
|
|
2179
|
+
join8(process.cwd(), "server/public")
|
|
1916
2180
|
// Fallback: from root
|
|
1917
2181
|
];
|
|
1918
|
-
var publicDir = possiblePublicDirs.find((dir) =>
|
|
2182
|
+
var publicDir = possiblePublicDirs.find((dir) => existsSync9(dir));
|
|
1919
2183
|
if (publicDir) {
|
|
1920
2184
|
app.use(express.static(publicDir));
|
|
1921
2185
|
console.log(`\u{1F4C2} Serving static files from: ${publicDir}`);
|
|
@@ -1926,35 +2190,39 @@ app.use("/api/skills", skills_default);
|
|
|
1926
2190
|
app.use("/api/workspaces", workspaces_default);
|
|
1927
2191
|
app.use("/api/marketplace", marketplace_default);
|
|
1928
2192
|
app.use("/api/dashboard", dashboard_default);
|
|
2193
|
+
app.use("/api/config", config_default);
|
|
1929
2194
|
app.get("/health", (req, res) => {
|
|
1930
2195
|
res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1931
2196
|
});
|
|
1932
|
-
if (publicDir &&
|
|
2197
|
+
if (publicDir && existsSync9(publicDir)) {
|
|
1933
2198
|
app.get("*", (req, res) => {
|
|
1934
|
-
res.sendFile(
|
|
2199
|
+
res.sendFile(join8(publicDir, "index.html"));
|
|
1935
2200
|
});
|
|
1936
2201
|
}
|
|
1937
2202
|
app.use(errorHandler);
|
|
1938
2203
|
async function initializeStorage() {
|
|
1939
|
-
const skillverseHome = process.env.SKILLVERSE_HOME ||
|
|
1940
|
-
const skillsDir = process.env.SKILLS_DIR ||
|
|
1941
|
-
const marketplaceDir = process.env.MARKETPLACE_DIR ||
|
|
2204
|
+
const skillverseHome = process.env.SKILLVERSE_HOME || join8(process.env.HOME || "", ".skillverse");
|
|
2205
|
+
const skillsDir = process.env.SKILLS_DIR || join8(skillverseHome, "skills");
|
|
2206
|
+
const marketplaceDir = process.env.MARKETPLACE_DIR || join8(skillverseHome, "marketplace");
|
|
1942
2207
|
const dirs = [skillverseHome, skillsDir, marketplaceDir];
|
|
1943
2208
|
for (const dir of dirs) {
|
|
1944
|
-
if (!
|
|
2209
|
+
if (!existsSync9(dir)) {
|
|
1945
2210
|
await mkdir5(dir, { recursive: true });
|
|
1946
2211
|
console.log(`Created directory: ${dir}`);
|
|
1947
2212
|
}
|
|
1948
2213
|
}
|
|
1949
2214
|
}
|
|
1950
|
-
async function startServer(port =
|
|
2215
|
+
async function startServer(port = 3001) {
|
|
1951
2216
|
try {
|
|
1952
2217
|
await initializeStorage();
|
|
1953
2218
|
await ensureDatabaseInitialized();
|
|
2219
|
+
console.log("\u{1F50D} Scanning for existing skills...");
|
|
2220
|
+
const { skillService: skillService2 } = await Promise.resolve().then(() => (init_skillService(), skillService_exports));
|
|
2221
|
+
await skillService2.scanForSkills();
|
|
1954
2222
|
return new Promise((resolve) => {
|
|
1955
2223
|
app.listen(port, () => {
|
|
1956
2224
|
console.log(`\u{1F680} SkillVerse server running on http://localhost:${port}`);
|
|
1957
|
-
console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME ||
|
|
2225
|
+
console.log(`\u{1F4C1} Storage: ${process.env.SKILLVERSE_HOME || join8(process.env.HOME || "", ".skillverse")}`);
|
|
1958
2226
|
resolve();
|
|
1959
2227
|
});
|
|
1960
2228
|
});
|