@aishelf/service 1.0.0
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/LICENSE +10 -0
- package/README.md +280 -0
- package/dist/cli.js +3761 -0
- package/dist/server.js +3436 -0
- package/package.json +67 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,3436 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
// ../shared/dist/api/types/common.js
|
|
34
|
+
var require_common = __commonJS({
|
|
35
|
+
"../shared/dist/api/types/common.js"(exports2) {
|
|
36
|
+
"use strict";
|
|
37
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ../shared/dist/api/types/localServiceAPI.js
|
|
42
|
+
var require_localServiceAPI = __commonJS({
|
|
43
|
+
"../shared/dist/api/types/localServiceAPI.js"(exports2) {
|
|
44
|
+
"use strict";
|
|
45
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
46
|
+
exports2.AccessLevel = void 0;
|
|
47
|
+
var AccessLevel5;
|
|
48
|
+
(function(AccessLevel6) {
|
|
49
|
+
AccessLevel6["WRITE"] = "write";
|
|
50
|
+
AccessLevel6["READ_ONLY"] = "read-only";
|
|
51
|
+
AccessLevel6["NONE"] = "none";
|
|
52
|
+
})(AccessLevel5 || (exports2.AccessLevel = AccessLevel5 = {}));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ../shared/dist/api/types/remoteServerAPI.js
|
|
57
|
+
var require_remoteServerAPI = __commonJS({
|
|
58
|
+
"../shared/dist/api/types/remoteServerAPI.js"(exports2) {
|
|
59
|
+
"use strict";
|
|
60
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ../shared/dist/api/index.js
|
|
65
|
+
var require_api = __commonJS({
|
|
66
|
+
"../shared/dist/api/index.js"(exports2) {
|
|
67
|
+
"use strict";
|
|
68
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
69
|
+
if (k2 === void 0) k2 = k;
|
|
70
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
71
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
72
|
+
desc = { enumerable: true, get: function() {
|
|
73
|
+
return m[k];
|
|
74
|
+
} };
|
|
75
|
+
}
|
|
76
|
+
Object.defineProperty(o, k2, desc);
|
|
77
|
+
}) : (function(o, m, k, k2) {
|
|
78
|
+
if (k2 === void 0) k2 = k;
|
|
79
|
+
o[k2] = m[k];
|
|
80
|
+
}));
|
|
81
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
82
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
83
|
+
};
|
|
84
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
85
|
+
__exportStar(require_common(), exports2);
|
|
86
|
+
__exportStar(require_localServiceAPI(), exports2);
|
|
87
|
+
__exportStar(require_remoteServerAPI(), exports2);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ../shared/dist/auth/config.js
|
|
92
|
+
var require_config = __commonJS({
|
|
93
|
+
"../shared/dist/auth/config.js"(exports2) {
|
|
94
|
+
"use strict";
|
|
95
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
96
|
+
exports2.CODE_TTL_SECONDS = void 0;
|
|
97
|
+
exports2.CODE_TTL_SECONDS = 300;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ../shared/dist/auth/index.js
|
|
102
|
+
var require_auth = __commonJS({
|
|
103
|
+
"../shared/dist/auth/index.js"(exports2) {
|
|
104
|
+
"use strict";
|
|
105
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
106
|
+
if (k2 === void 0) k2 = k;
|
|
107
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
108
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
109
|
+
desc = { enumerable: true, get: function() {
|
|
110
|
+
return m[k];
|
|
111
|
+
} };
|
|
112
|
+
}
|
|
113
|
+
Object.defineProperty(o, k2, desc);
|
|
114
|
+
}) : (function(o, m, k, k2) {
|
|
115
|
+
if (k2 === void 0) k2 = k;
|
|
116
|
+
o[k2] = m[k];
|
|
117
|
+
}));
|
|
118
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
119
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
120
|
+
};
|
|
121
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
122
|
+
__exportStar(require_config(), exports2);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ../shared/dist/templates/skillTemplate.js
|
|
127
|
+
var require_skillTemplate = __commonJS({
|
|
128
|
+
"../shared/dist/templates/skillTemplate.js"(exports2) {
|
|
129
|
+
"use strict";
|
|
130
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
131
|
+
exports2.default = getTemplate;
|
|
132
|
+
function getTemplate(skillName) {
|
|
133
|
+
return `---
|
|
134
|
+
name: ${skillName}
|
|
135
|
+
description: Brief one-line description of what this skill does (edit this)
|
|
136
|
+
tags: [] # Optional: Add relevant tags
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
# Getting Started
|
|
140
|
+
|
|
141
|
+
Edit this **SKILL.md** file to document your skill. Add any scripts, configurations, or other files to this folder.
|
|
142
|
+
|
|
143
|
+
## How to Create Your Skill
|
|
144
|
+
|
|
145
|
+
**Option 1: Use AI to help you**
|
|
146
|
+
- Add this skill to your AI model: https://github.com/anthropics/skills/blob/main/skills/skill-creator/SKILL.md
|
|
147
|
+
- Ask your AI to help create the skill based on your requirements
|
|
148
|
+
|
|
149
|
+
**Option 2: Create manually**
|
|
150
|
+
- Follow the guide: https://support.claude.com/en/articles/12512198-how-to-create-custom-skills
|
|
151
|
+
|
|
152
|
+
## What to Include
|
|
153
|
+
|
|
154
|
+
- Document what your skill does and how to use it
|
|
155
|
+
- Add any scripts, code, or configuration files
|
|
156
|
+
- Include examples and prerequisites if needed
|
|
157
|
+
- Structure it however works best for your use case
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
**Tip:** Delete these instructions once you've created your skill documentation.
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ../shared/dist/resources/config.js
|
|
168
|
+
var require_config2 = __commonJS({
|
|
169
|
+
"../shared/dist/resources/config.js"(exports2) {
|
|
170
|
+
"use strict";
|
|
171
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
172
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
173
|
+
};
|
|
174
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
175
|
+
exports2.VALID_RESOURCE_TYPES = exports2.RESOURCE_METADATA = exports2.FileType = exports2.ResourceSource = exports2.ResourceStorageType = exports2.ResourceType = void 0;
|
|
176
|
+
exports2.getResourceLabel = getResourceLabel;
|
|
177
|
+
exports2.getResourceSingular = getResourceSingular2;
|
|
178
|
+
exports2.getResourceStorageType = getResourceStorageType3;
|
|
179
|
+
exports2.getResourceEntryFile = getResourceEntryFile3;
|
|
180
|
+
exports2.getResourceFileExt = getResourceFileExt3;
|
|
181
|
+
exports2.getResourceTemplate = getResourceTemplate2;
|
|
182
|
+
exports2.isValidResourceType = isValidResourceType2;
|
|
183
|
+
var skillTemplate_1 = __importDefault(require_skillTemplate());
|
|
184
|
+
var ResourceType7;
|
|
185
|
+
(function(ResourceType8) {
|
|
186
|
+
ResourceType8["WORKFLOWS"] = "workflows";
|
|
187
|
+
ResourceType8["RULES"] = "rules";
|
|
188
|
+
ResourceType8["SKILLS"] = "skills";
|
|
189
|
+
ResourceType8["PROMPTS"] = "prompts";
|
|
190
|
+
})(ResourceType7 || (exports2.ResourceType = ResourceType7 = {}));
|
|
191
|
+
var ResourceStorageType3;
|
|
192
|
+
(function(ResourceStorageType4) {
|
|
193
|
+
ResourceStorageType4["FILE"] = "file";
|
|
194
|
+
ResourceStorageType4["FOLDER"] = "folder";
|
|
195
|
+
})(ResourceStorageType3 || (exports2.ResourceStorageType = ResourceStorageType3 = {}));
|
|
196
|
+
var ResourceSource2;
|
|
197
|
+
(function(ResourceSource3) {
|
|
198
|
+
ResourceSource3["DRAFT"] = "draft";
|
|
199
|
+
ResourceSource3["REGISTRY"] = "registry";
|
|
200
|
+
})(ResourceSource2 || (exports2.ResourceSource = ResourceSource2 = {}));
|
|
201
|
+
var FileType;
|
|
202
|
+
(function(FileType2) {
|
|
203
|
+
FileType2["MD"] = "md";
|
|
204
|
+
FileType2["JSON"] = "json";
|
|
205
|
+
})(FileType || (exports2.FileType = FileType = {}));
|
|
206
|
+
exports2.RESOURCE_METADATA = {
|
|
207
|
+
[ResourceType7.WORKFLOWS]: {
|
|
208
|
+
label: "Workflows",
|
|
209
|
+
singular: "workflow",
|
|
210
|
+
storageType: ResourceStorageType3.FILE,
|
|
211
|
+
defaultFileExt: FileType.MD
|
|
212
|
+
},
|
|
213
|
+
[ResourceType7.RULES]: {
|
|
214
|
+
label: "Rules",
|
|
215
|
+
singular: "rule",
|
|
216
|
+
storageType: ResourceStorageType3.FILE,
|
|
217
|
+
defaultFileExt: FileType.MD
|
|
218
|
+
},
|
|
219
|
+
[ResourceType7.SKILLS]: {
|
|
220
|
+
label: "Skills",
|
|
221
|
+
singular: "skill",
|
|
222
|
+
storageType: ResourceStorageType3.FOLDER,
|
|
223
|
+
defaultEntryFile: "SKILL.md",
|
|
224
|
+
getTemplate: skillTemplate_1.default
|
|
225
|
+
},
|
|
226
|
+
[ResourceType7.PROMPTS]: {
|
|
227
|
+
label: "Prompts",
|
|
228
|
+
singular: "prompt",
|
|
229
|
+
storageType: ResourceStorageType3.FILE,
|
|
230
|
+
defaultFileExt: FileType.MD
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
exports2.VALID_RESOURCE_TYPES = [
|
|
234
|
+
ResourceType7.WORKFLOWS,
|
|
235
|
+
ResourceType7.RULES,
|
|
236
|
+
ResourceType7.SKILLS,
|
|
237
|
+
ResourceType7.PROMPTS
|
|
238
|
+
];
|
|
239
|
+
function getResourceLabel(type) {
|
|
240
|
+
return exports2.RESOURCE_METADATA[type].label;
|
|
241
|
+
}
|
|
242
|
+
function getResourceSingular2(type) {
|
|
243
|
+
return exports2.RESOURCE_METADATA[type].singular;
|
|
244
|
+
}
|
|
245
|
+
function getResourceStorageType3(type) {
|
|
246
|
+
return exports2.RESOURCE_METADATA[type].storageType;
|
|
247
|
+
}
|
|
248
|
+
function getResourceEntryFile3(type) {
|
|
249
|
+
return exports2.RESOURCE_METADATA[type].defaultEntryFile;
|
|
250
|
+
}
|
|
251
|
+
function getResourceFileExt3(type) {
|
|
252
|
+
return exports2.RESOURCE_METADATA[type].defaultFileExt;
|
|
253
|
+
}
|
|
254
|
+
function getResourceTemplate2(type) {
|
|
255
|
+
return exports2.RESOURCE_METADATA[type].getTemplate;
|
|
256
|
+
}
|
|
257
|
+
function isValidResourceType2(type) {
|
|
258
|
+
return Object.values(ResourceType7).includes(type);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// ../shared/dist/resources/index.js
|
|
264
|
+
var require_resources = __commonJS({
|
|
265
|
+
"../shared/dist/resources/index.js"(exports2) {
|
|
266
|
+
"use strict";
|
|
267
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
268
|
+
if (k2 === void 0) k2 = k;
|
|
269
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
270
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
271
|
+
desc = { enumerable: true, get: function() {
|
|
272
|
+
return m[k];
|
|
273
|
+
} };
|
|
274
|
+
}
|
|
275
|
+
Object.defineProperty(o, k2, desc);
|
|
276
|
+
}) : (function(o, m, k, k2) {
|
|
277
|
+
if (k2 === void 0) k2 = k;
|
|
278
|
+
o[k2] = m[k];
|
|
279
|
+
}));
|
|
280
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
281
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
282
|
+
};
|
|
283
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
284
|
+
__exportStar(require_config2(), exports2);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// ../shared/dist/errors.js
|
|
289
|
+
var require_errors = __commonJS({
|
|
290
|
+
"../shared/dist/errors.js"(exports2) {
|
|
291
|
+
"use strict";
|
|
292
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
293
|
+
exports2.FileSystemError = exports2.GitHubAPIError = exports2.GitOperationError = exports2.ConflictError = exports2.NotFoundError = exports2.ForbiddenError = exports2.UnauthorizedError = exports2.RateLimitError = exports2.InvalidInputError = exports2.ValidationError = exports2.AppError = void 0;
|
|
294
|
+
var AppError2 = class extends Error {
|
|
295
|
+
message;
|
|
296
|
+
statusCode;
|
|
297
|
+
code;
|
|
298
|
+
isOperational;
|
|
299
|
+
constructor(message, statusCode = 500, code = "INTERNAL_ERROR", isOperational = true) {
|
|
300
|
+
super(message);
|
|
301
|
+
this.message = message;
|
|
302
|
+
this.statusCode = statusCode;
|
|
303
|
+
this.code = code;
|
|
304
|
+
this.isOperational = isOperational;
|
|
305
|
+
this.name = this.constructor.name;
|
|
306
|
+
Error.captureStackTrace(this, this.constructor);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
exports2.AppError = AppError2;
|
|
310
|
+
var ValidationError11 = class extends AppError2 {
|
|
311
|
+
constructor(message) {
|
|
312
|
+
super(message, 400, "VALIDATION_ERROR");
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
exports2.ValidationError = ValidationError11;
|
|
316
|
+
var InvalidInputError2 = class extends AppError2 {
|
|
317
|
+
constructor(message) {
|
|
318
|
+
super(message, 400, "INVALID_INPUT");
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
exports2.InvalidInputError = InvalidInputError2;
|
|
322
|
+
var RateLimitError = class extends AppError2 {
|
|
323
|
+
constructor(message) {
|
|
324
|
+
super(message, 429, "RATE_LIMITED");
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
exports2.RateLimitError = RateLimitError;
|
|
328
|
+
var UnauthorizedError5 = class extends AppError2 {
|
|
329
|
+
constructor(message = "Unauthorized") {
|
|
330
|
+
super(message, 401, "UNAUTHORIZED");
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
exports2.UnauthorizedError = UnauthorizedError5;
|
|
334
|
+
var ForbiddenError = class extends AppError2 {
|
|
335
|
+
constructor(message = "Forbidden") {
|
|
336
|
+
super(message, 403, "FORBIDDEN");
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
exports2.ForbiddenError = ForbiddenError;
|
|
340
|
+
var NotFoundError9 = class extends AppError2 {
|
|
341
|
+
constructor(message) {
|
|
342
|
+
super(message, 404, "NOT_FOUND");
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
exports2.NotFoundError = NotFoundError9;
|
|
346
|
+
var ConflictError6 = class extends AppError2 {
|
|
347
|
+
constructor(message) {
|
|
348
|
+
super(message, 409, "CONFLICT");
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
exports2.ConflictError = ConflictError6;
|
|
352
|
+
var GitOperationError3 = class extends AppError2 {
|
|
353
|
+
constructor(message) {
|
|
354
|
+
super(message, 500, "GIT_OPERATION_ERROR");
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
exports2.GitOperationError = GitOperationError3;
|
|
358
|
+
var GitHubAPIError3 = class extends AppError2 {
|
|
359
|
+
constructor(message, statusCode = 500) {
|
|
360
|
+
super(message, statusCode, "GITHUB_API_ERROR");
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
exports2.GitHubAPIError = GitHubAPIError3;
|
|
364
|
+
var FileSystemError3 = class extends AppError2 {
|
|
365
|
+
constructor(message) {
|
|
366
|
+
super(message, 500, "FILESYSTEM_ERROR");
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
exports2.FileSystemError = FileSystemError3;
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// ../shared/dist/index.js
|
|
374
|
+
var require_dist = __commonJS({
|
|
375
|
+
"../shared/dist/index.js"(exports2) {
|
|
376
|
+
"use strict";
|
|
377
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
378
|
+
if (k2 === void 0) k2 = k;
|
|
379
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
380
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
381
|
+
desc = { enumerable: true, get: function() {
|
|
382
|
+
return m[k];
|
|
383
|
+
} };
|
|
384
|
+
}
|
|
385
|
+
Object.defineProperty(o, k2, desc);
|
|
386
|
+
}) : (function(o, m, k, k2) {
|
|
387
|
+
if (k2 === void 0) k2 = k;
|
|
388
|
+
o[k2] = m[k];
|
|
389
|
+
}));
|
|
390
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
391
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
392
|
+
};
|
|
393
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
394
|
+
__exportStar(require_api(), exports2);
|
|
395
|
+
__exportStar(require_auth(), exports2);
|
|
396
|
+
__exportStar(require_resources(), exports2);
|
|
397
|
+
__exportStar(require_errors(), exports2);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// src/server.ts
|
|
402
|
+
var import_express9 = __toESM(require("express"));
|
|
403
|
+
var import_promises7 = __toESM(require("fs/promises"));
|
|
404
|
+
var import_path9 = __toESM(require("path"));
|
|
405
|
+
var import_os3 = __toESM(require("os"));
|
|
406
|
+
|
|
407
|
+
// src/routes/index.ts
|
|
408
|
+
var import_express8 = require("express");
|
|
409
|
+
|
|
410
|
+
// src/routes/auth.routes.ts
|
|
411
|
+
var import_express = require("express");
|
|
412
|
+
|
|
413
|
+
// src/modules/auth/auth.service.ts
|
|
414
|
+
var import_crypto = require("crypto");
|
|
415
|
+
|
|
416
|
+
// src/config/index.ts
|
|
417
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
418
|
+
var import_zod = require("zod");
|
|
419
|
+
import_dotenv.default.config();
|
|
420
|
+
var ConfigSchema = import_zod.z.object({
|
|
421
|
+
NODE_ENV: import_zod.z.enum(["development", "production", "test"]).default("development"),
|
|
422
|
+
PORT: import_zod.z.string().default("5314").transform(Number),
|
|
423
|
+
LOG_LEVEL: import_zod.z.enum(["error", "warn", "info", "debug"]).default("info"),
|
|
424
|
+
BACKEND_URL: import_zod.z.string().url().optional(),
|
|
425
|
+
CORS_ORIGINS: import_zod.z.string().optional()
|
|
426
|
+
});
|
|
427
|
+
var parsed = ConfigSchema.safeParse({
|
|
428
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
429
|
+
PORT: process.env.AISHELF_PORT || process.env.PORT,
|
|
430
|
+
LOG_LEVEL: process.env.LOG_LEVEL,
|
|
431
|
+
BACKEND_URL: process.env.BACKEND_URL,
|
|
432
|
+
CORS_ORIGINS: process.env.CORS_ORIGINS
|
|
433
|
+
});
|
|
434
|
+
if (!parsed.success) {
|
|
435
|
+
console.error("Invalid configuration:", parsed.error.format());
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
var config = parsed.data;
|
|
439
|
+
|
|
440
|
+
// src/utils/shared.ts
|
|
441
|
+
var shared_exports = {};
|
|
442
|
+
__reExport(shared_exports, __toESM(require_dist()));
|
|
443
|
+
|
|
444
|
+
// src/modules/api/api.service.ts
|
|
445
|
+
var ApiService = class {
|
|
446
|
+
async apiRequest(endpoint, token, options = {}) {
|
|
447
|
+
const { method = "GET", body, headers = {} } = options;
|
|
448
|
+
if (!config.BACKEND_URL) {
|
|
449
|
+
throw new Error("BACKEND_URL is not configured");
|
|
450
|
+
}
|
|
451
|
+
const url = `${config.BACKEND_URL}${endpoint}`;
|
|
452
|
+
const requestHeaders = {
|
|
453
|
+
...token ? { "Authorization": `Bearer ${token}` } : {},
|
|
454
|
+
"Content-Type": "application/json",
|
|
455
|
+
...headers
|
|
456
|
+
};
|
|
457
|
+
try {
|
|
458
|
+
const response = await fetch(url, {
|
|
459
|
+
method,
|
|
460
|
+
headers: requestHeaders,
|
|
461
|
+
body: body ? JSON.stringify(body) : void 0
|
|
462
|
+
});
|
|
463
|
+
const data = await response.json();
|
|
464
|
+
if (!response.ok) {
|
|
465
|
+
throw new shared_exports.GitHubAPIError(
|
|
466
|
+
data?.message || `API request failed: ${response.statusText}`,
|
|
467
|
+
response.status
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
return data;
|
|
471
|
+
} catch (error) {
|
|
472
|
+
if (error instanceof shared_exports.GitHubAPIError) {
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
throw new shared_exports.GitHubAPIError(
|
|
476
|
+
`Failed to make API request: ${error.message}`,
|
|
477
|
+
500
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
var apiService = new ApiService();
|
|
483
|
+
|
|
484
|
+
// src/modules/auth/auth.service.ts
|
|
485
|
+
var import_shared3 = __toESM(require_dist());
|
|
486
|
+
|
|
487
|
+
// src/modules/storage/storage.service.ts
|
|
488
|
+
var import_path = __toESM(require("path"));
|
|
489
|
+
var import_os = __toESM(require("os"));
|
|
490
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
491
|
+
var StorageService = class {
|
|
492
|
+
constructor() {
|
|
493
|
+
this.configFileName = "config.json";
|
|
494
|
+
}
|
|
495
|
+
getStorageRoot() {
|
|
496
|
+
return import_path.default.join(import_os.default.homedir(), ".aishelf", "storage");
|
|
497
|
+
}
|
|
498
|
+
getAuditLogPath(client) {
|
|
499
|
+
const fileName = client ? `audit-${client}.jsonl` : "audit-log.jsonl";
|
|
500
|
+
return import_path.default.join(this.getStorageRoot(), "logs", fileName);
|
|
501
|
+
}
|
|
502
|
+
getRegistriesPath() {
|
|
503
|
+
return import_path.default.join(this.getStorageRoot(), "registries");
|
|
504
|
+
}
|
|
505
|
+
getRegistryPath(owner, repo) {
|
|
506
|
+
return import_path.default.join(this.getRegistriesPath(), `${owner}-${repo}`);
|
|
507
|
+
}
|
|
508
|
+
getPackagesPath(owner, repo) {
|
|
509
|
+
return import_path.default.join(this.getRegistryPath(owner, repo), "packages");
|
|
510
|
+
}
|
|
511
|
+
getPackagePath(owner, repo, packageId) {
|
|
512
|
+
return import_path.default.join(this.getPackagesPath(owner, repo), packageId);
|
|
513
|
+
}
|
|
514
|
+
getDraftsPath() {
|
|
515
|
+
return import_path.default.join(this.getStorageRoot(), "drafts");
|
|
516
|
+
}
|
|
517
|
+
getDraftPath(owner, repo) {
|
|
518
|
+
return import_path.default.join(this.getDraftsPath(), `${owner}-${repo}`);
|
|
519
|
+
}
|
|
520
|
+
getDraftResourceDir(owner, repo, packageId, resourceType) {
|
|
521
|
+
return import_path.default.join(this.getDraftPath(owner, repo), "packages", packageId, resourceType);
|
|
522
|
+
}
|
|
523
|
+
getResourceTypeDir(owner, repo, packageId, resourceType) {
|
|
524
|
+
return import_path.default.join(this.getPackagePath(owner, repo, packageId), resourceType);
|
|
525
|
+
}
|
|
526
|
+
getResourcePath(owner, repo, packageId, resourceType, resourceName) {
|
|
527
|
+
return import_path.default.join(this.getPackagePath(owner, repo, packageId), resourceType, resourceName);
|
|
528
|
+
}
|
|
529
|
+
async loadConfig() {
|
|
530
|
+
try {
|
|
531
|
+
const configPath = this.getConfigPath();
|
|
532
|
+
const data = await import_promises.default.readFile(configPath, "utf8");
|
|
533
|
+
const config2 = JSON.parse(data);
|
|
534
|
+
return config2;
|
|
535
|
+
} catch (error) {
|
|
536
|
+
return { connectedRegistries: [], securityPreferences: {} };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async saveConfig(config2) {
|
|
540
|
+
try {
|
|
541
|
+
await this.ensureStorageExists();
|
|
542
|
+
const configPath = this.getConfigPath();
|
|
543
|
+
const data = JSON.stringify(config2, null, 2);
|
|
544
|
+
await import_promises.default.writeFile(configPath, data, {
|
|
545
|
+
encoding: "utf8",
|
|
546
|
+
mode: 384
|
|
547
|
+
});
|
|
548
|
+
await import_promises.default.chmod(configPath, 384);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
throw new shared_exports.FileSystemError("Failed to save config");
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
async getAuthToken() {
|
|
554
|
+
const config2 = await this.loadConfig();
|
|
555
|
+
return config2.token ?? null;
|
|
556
|
+
}
|
|
557
|
+
async saveAuthToken(token) {
|
|
558
|
+
const config2 = await this.loadConfig();
|
|
559
|
+
if (token) config2.token = token;
|
|
560
|
+
else delete config2.token;
|
|
561
|
+
await this.saveConfig(config2);
|
|
562
|
+
}
|
|
563
|
+
async ensureStorageExists() {
|
|
564
|
+
try {
|
|
565
|
+
await import_promises.default.mkdir(this.getStorageRoot(), { recursive: true });
|
|
566
|
+
await import_promises.default.mkdir(this.getRegistriesPath(), { recursive: true });
|
|
567
|
+
await import_promises.default.mkdir(this.getDraftsPath(), { recursive: true });
|
|
568
|
+
} catch (error) {
|
|
569
|
+
throw new shared_exports.FileSystemError("Failed to create storage directories");
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
async checkRegistryExists(owner, repo) {
|
|
573
|
+
try {
|
|
574
|
+
const registryPath = this.getRegistryPath(owner, repo);
|
|
575
|
+
await import_promises.default.stat(registryPath);
|
|
576
|
+
return true;
|
|
577
|
+
} catch {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async getConnectedRegistries() {
|
|
582
|
+
const config2 = await this.loadConfig();
|
|
583
|
+
return config2.connectedRegistries;
|
|
584
|
+
}
|
|
585
|
+
async saveConnectedRegistry(registry) {
|
|
586
|
+
const config2 = await this.loadConfig();
|
|
587
|
+
const existingIndex = config2.connectedRegistries.findIndex(
|
|
588
|
+
(r) => r.owner === registry.owner && r.repo === registry.repo
|
|
589
|
+
);
|
|
590
|
+
if (existingIndex >= 0) {
|
|
591
|
+
config2.connectedRegistries[existingIndex] = registry;
|
|
592
|
+
} else {
|
|
593
|
+
config2.connectedRegistries.push(registry);
|
|
594
|
+
}
|
|
595
|
+
await this.saveConfig(config2);
|
|
596
|
+
}
|
|
597
|
+
async updateConnectedRegistries(registries) {
|
|
598
|
+
const config2 = await this.loadConfig();
|
|
599
|
+
config2.connectedRegistries = registries;
|
|
600
|
+
await this.saveConfig(config2);
|
|
601
|
+
}
|
|
602
|
+
async shouldShowSecurityViolations(owner, repo) {
|
|
603
|
+
const config2 = await this.loadConfig();
|
|
604
|
+
const fullName = `${owner}/${repo}`;
|
|
605
|
+
return !config2.securityPreferences?.[fullName]?.dontShowViolations;
|
|
606
|
+
}
|
|
607
|
+
async setDontShowSecurityViolations(owner, repo) {
|
|
608
|
+
const config2 = await this.loadConfig();
|
|
609
|
+
const fullName = `${owner}/${repo}`;
|
|
610
|
+
if (!config2.securityPreferences) {
|
|
611
|
+
config2.securityPreferences = {};
|
|
612
|
+
}
|
|
613
|
+
config2.securityPreferences[fullName] = {
|
|
614
|
+
dontShowViolations: true
|
|
615
|
+
};
|
|
616
|
+
await this.saveConfig(config2);
|
|
617
|
+
}
|
|
618
|
+
async removeTrustPreference(owner, repo) {
|
|
619
|
+
const config2 = await this.loadConfig();
|
|
620
|
+
const fullName = `${owner}/${repo}`;
|
|
621
|
+
if (config2.securityPreferences?.[fullName]) {
|
|
622
|
+
delete config2.securityPreferences[fullName];
|
|
623
|
+
await this.saveConfig(config2);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async checkPackageExists(owner, repo, packageId) {
|
|
627
|
+
try {
|
|
628
|
+
const packagePath = this.getPackagePath(owner, repo, packageId);
|
|
629
|
+
await import_promises.default.stat(packagePath);
|
|
630
|
+
return true;
|
|
631
|
+
} catch {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async checkResourceExists(owner, repo, packageId, resourceType, resourceName, getBasePath) {
|
|
636
|
+
const draftBasePath = getBasePath(this.getDraftResourceDir(owner, repo, packageId, resourceType));
|
|
637
|
+
try {
|
|
638
|
+
await import_promises.default.stat(draftBasePath);
|
|
639
|
+
return true;
|
|
640
|
+
} catch {
|
|
641
|
+
}
|
|
642
|
+
const registryBasePath = getBasePath(this.getResourceTypeDir(owner, repo, packageId, resourceType));
|
|
643
|
+
try {
|
|
644
|
+
await import_promises.default.stat(registryBasePath);
|
|
645
|
+
return true;
|
|
646
|
+
} catch {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
async ensureDraftDirExists(owner, repo, packageId, resourceType) {
|
|
651
|
+
try {
|
|
652
|
+
await import_promises.default.mkdir(this.getDraftResourceDir(owner, repo, packageId, resourceType), { recursive: true });
|
|
653
|
+
} catch (error) {
|
|
654
|
+
throw new shared_exports.FileSystemError("Failed to create draft directories");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
getConfigPath() {
|
|
658
|
+
return import_path.default.join(this.getStorageRoot(), this.configFileName);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
var storageService = new StorageService();
|
|
662
|
+
|
|
663
|
+
// src/modules/auth/auth.service.ts
|
|
664
|
+
var CODE_TTL_MS = import_shared3.CODE_TTL_SECONDS * 1e3;
|
|
665
|
+
var AuthService = class {
|
|
666
|
+
constructor(api = apiService, storage = storageService) {
|
|
667
|
+
this.api = api;
|
|
668
|
+
this.storage = storage;
|
|
669
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
670
|
+
}
|
|
671
|
+
generatePkceSession() {
|
|
672
|
+
const codeVerifier = this.generateVerifier();
|
|
673
|
+
const codeChallenge = this.deriveChallenge(codeVerifier);
|
|
674
|
+
const sessionId = (0, import_crypto.randomUUID)();
|
|
675
|
+
this.sessions.set(sessionId, {
|
|
676
|
+
codeVerifier,
|
|
677
|
+
expiresAt: Date.now() + CODE_TTL_MS
|
|
678
|
+
});
|
|
679
|
+
return {
|
|
680
|
+
session_id: sessionId,
|
|
681
|
+
code_challenge: codeChallenge
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
async exchangeToken({ session_id: sessionId, authorization_code: authorizationCode }) {
|
|
685
|
+
const session = this.sessions.get(sessionId);
|
|
686
|
+
if (!session) {
|
|
687
|
+
throw new import_shared3.NotFoundError("Session not found");
|
|
688
|
+
}
|
|
689
|
+
const { data } = await this.api.apiRequest("/auth/token", null, {
|
|
690
|
+
method: "POST",
|
|
691
|
+
body: {
|
|
692
|
+
authorization_code: authorizationCode,
|
|
693
|
+
code_verifier: session.codeVerifier
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
this.sessions.delete(sessionId);
|
|
697
|
+
if (!data?.token) {
|
|
698
|
+
throw new import_shared3.UnauthorizedError("Token exchange with the server failed");
|
|
699
|
+
}
|
|
700
|
+
await this.storage.saveAuthToken(data.token);
|
|
701
|
+
this.api.apiRequest("/user", data.token, { method: "POST" }).catch((err) => {
|
|
702
|
+
console.warn("Failed to ensure user exists in backend:", err);
|
|
703
|
+
});
|
|
704
|
+
return this.getStatus();
|
|
705
|
+
}
|
|
706
|
+
async getStatus() {
|
|
707
|
+
const config2 = await this.storage.loadConfig();
|
|
708
|
+
const token = config2.token;
|
|
709
|
+
return { authenticated: !!token };
|
|
710
|
+
}
|
|
711
|
+
async getUser(githubUser) {
|
|
712
|
+
return {
|
|
713
|
+
user: {
|
|
714
|
+
id: githubUser.githubId,
|
|
715
|
+
username: githubUser.githubUsername,
|
|
716
|
+
email: githubUser.email,
|
|
717
|
+
avatarUrl: githubUser.avatarUrl
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
async logout() {
|
|
722
|
+
await this.storage.saveAuthToken(null);
|
|
723
|
+
const config2 = await this.storage.loadConfig();
|
|
724
|
+
return { authenticated: !config2.token };
|
|
725
|
+
}
|
|
726
|
+
generateVerifier() {
|
|
727
|
+
return (0, import_crypto.randomBytes)(32).toString("base64url");
|
|
728
|
+
}
|
|
729
|
+
deriveChallenge(verifier) {
|
|
730
|
+
return (0, import_crypto.createHash)("sha256").update(verifier).digest("base64url");
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
var authService = new AuthService();
|
|
734
|
+
|
|
735
|
+
// src/controllers/auth.controller.ts
|
|
736
|
+
var import_shared4 = __toESM(require_dist());
|
|
737
|
+
var AuthController = class {
|
|
738
|
+
constructor(auth = authService) {
|
|
739
|
+
this.auth = auth;
|
|
740
|
+
}
|
|
741
|
+
async getChallenge(req, res, next) {
|
|
742
|
+
try {
|
|
743
|
+
const session = this.auth.generatePkceSession();
|
|
744
|
+
const response = {
|
|
745
|
+
success: true,
|
|
746
|
+
data: {
|
|
747
|
+
session_id: session.session_id,
|
|
748
|
+
code_challenge: session.code_challenge
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
res.json(response);
|
|
752
|
+
} catch (error) {
|
|
753
|
+
next(error);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async exchangeToken(req, res, next) {
|
|
757
|
+
try {
|
|
758
|
+
const { authorization_code, session_id } = req.body;
|
|
759
|
+
if (!authorization_code || !session_id) {
|
|
760
|
+
throw new import_shared4.InvalidInputError("authorization_code and session_id is required");
|
|
761
|
+
}
|
|
762
|
+
const status = await this.auth.exchangeToken({ authorization_code, session_id });
|
|
763
|
+
const response = {
|
|
764
|
+
success: true,
|
|
765
|
+
data: status
|
|
766
|
+
};
|
|
767
|
+
res.json(response);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
next(error);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async getStatus(req, res, next) {
|
|
773
|
+
try {
|
|
774
|
+
const status = await this.auth.getStatus();
|
|
775
|
+
const response = {
|
|
776
|
+
success: true,
|
|
777
|
+
data: status
|
|
778
|
+
};
|
|
779
|
+
res.json(response);
|
|
780
|
+
} catch (error) {
|
|
781
|
+
next(error);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
async getUser(req, res, next) {
|
|
785
|
+
try {
|
|
786
|
+
if (!req.githubUser) {
|
|
787
|
+
throw new import_shared4.UnauthorizedError("Not authenticated");
|
|
788
|
+
}
|
|
789
|
+
const result = await this.auth.getUser(req.githubUser);
|
|
790
|
+
const response = {
|
|
791
|
+
success: true,
|
|
792
|
+
data: result
|
|
793
|
+
};
|
|
794
|
+
res.json(response);
|
|
795
|
+
} catch (error) {
|
|
796
|
+
next(error);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async logout(req, res, next) {
|
|
800
|
+
try {
|
|
801
|
+
const status = await this.auth.logout();
|
|
802
|
+
const response = {
|
|
803
|
+
success: true,
|
|
804
|
+
data: status
|
|
805
|
+
};
|
|
806
|
+
res.json(response);
|
|
807
|
+
} catch (error) {
|
|
808
|
+
next(error);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
// src/modules/auth/auth.middleware.ts
|
|
814
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
815
|
+
var import_rest = require("@octokit/rest");
|
|
816
|
+
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
817
|
+
|
|
818
|
+
// src/utils/logger.ts
|
|
819
|
+
var import_winston = __toESM(require("winston"));
|
|
820
|
+
var import_path2 = __toESM(require("path"));
|
|
821
|
+
var import_os2 = __toESM(require("os"));
|
|
822
|
+
var LOG_DIR = import_path2.default.join(import_os2.default.homedir(), ".aishelf", "logs");
|
|
823
|
+
var logger = import_winston.default.createLogger({
|
|
824
|
+
level: process.env.LOG_LEVEL || "info",
|
|
825
|
+
format: import_winston.default.format.combine(
|
|
826
|
+
import_winston.default.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
|
827
|
+
import_winston.default.format.errors({ stack: true }),
|
|
828
|
+
import_winston.default.format.json()
|
|
829
|
+
),
|
|
830
|
+
transports: [
|
|
831
|
+
new import_winston.default.transports.File({
|
|
832
|
+
filename: import_path2.default.join(LOG_DIR, "error.log"),
|
|
833
|
+
level: "error",
|
|
834
|
+
maxsize: 5242880,
|
|
835
|
+
maxFiles: 5
|
|
836
|
+
}),
|
|
837
|
+
new import_winston.default.transports.File({
|
|
838
|
+
filename: import_path2.default.join(LOG_DIR, "combined.log"),
|
|
839
|
+
maxsize: 5242880,
|
|
840
|
+
maxFiles: 5
|
|
841
|
+
})
|
|
842
|
+
]
|
|
843
|
+
});
|
|
844
|
+
if (process.env.NODE_ENV !== "production") {
|
|
845
|
+
logger.add(new import_winston.default.transports.Console({
|
|
846
|
+
format: import_winston.default.format.combine(
|
|
847
|
+
import_winston.default.format.colorize(),
|
|
848
|
+
import_winston.default.format.simple()
|
|
849
|
+
)
|
|
850
|
+
}));
|
|
851
|
+
}
|
|
852
|
+
var logger_default = logger;
|
|
853
|
+
|
|
854
|
+
// src/modules/auth/auth.middleware.ts
|
|
855
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
856
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
857
|
+
function authenticateAishelfClient(req, _res, next) {
|
|
858
|
+
try {
|
|
859
|
+
const clientToken = req.headers["x-client-token"];
|
|
860
|
+
if (!clientToken) {
|
|
861
|
+
throw new shared_exports.UnauthorizedError("Missing X-Client-Token header");
|
|
862
|
+
}
|
|
863
|
+
const clientPayload = verifyClientToken(clientToken);
|
|
864
|
+
if (!clientPayload || !clientPayload.client) {
|
|
865
|
+
throw new shared_exports.UnauthorizedError('Invalid client token: missing "client" field');
|
|
866
|
+
}
|
|
867
|
+
req.clientInfo = clientPayload;
|
|
868
|
+
next();
|
|
869
|
+
} catch (error) {
|
|
870
|
+
next(error);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
async function authenticateGithubToken(req, _res, next) {
|
|
874
|
+
try {
|
|
875
|
+
const githubToken = await storageService.getAuthToken();
|
|
876
|
+
if (!githubToken) {
|
|
877
|
+
throw new shared_exports.UnauthorizedError("Not authenticated \u2014 sign in via AIShelf");
|
|
878
|
+
}
|
|
879
|
+
const user = await verifyGitHubToken(githubToken);
|
|
880
|
+
req.githubUser = user;
|
|
881
|
+
req.githubToken = githubToken;
|
|
882
|
+
next();
|
|
883
|
+
} catch (error) {
|
|
884
|
+
next(error);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function verifyClientToken(token) {
|
|
888
|
+
try {
|
|
889
|
+
const decoded = import_jsonwebtoken.default.decode(token, { json: true });
|
|
890
|
+
if (!decoded) {
|
|
891
|
+
return null;
|
|
892
|
+
}
|
|
893
|
+
return decoded;
|
|
894
|
+
} catch {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
async function verifyGitHubToken(token) {
|
|
899
|
+
const tokenHash = import_crypto2.default.createHash("sha256").update(token).digest("hex");
|
|
900
|
+
const cached = tokenCache.get(tokenHash);
|
|
901
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
902
|
+
return cached.user;
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
const octokit = new import_rest.Octokit({ auth: token });
|
|
906
|
+
const { data: userData } = await octokit.rest.users.getAuthenticated();
|
|
907
|
+
let email = null;
|
|
908
|
+
try {
|
|
909
|
+
const { data: emails } = await octokit.rest.users.listEmailsForAuthenticatedUser();
|
|
910
|
+
const primaryEmail = emails.find((e) => e.primary && e.verified);
|
|
911
|
+
const verifiedEmail = emails.find((e) => e.verified);
|
|
912
|
+
email = primaryEmail?.email || verifiedEmail?.email || null;
|
|
913
|
+
} catch {
|
|
914
|
+
email = null;
|
|
915
|
+
}
|
|
916
|
+
const user = {
|
|
917
|
+
githubId: userData.id.toString(),
|
|
918
|
+
githubUsername: userData.login,
|
|
919
|
+
email: email || void 0,
|
|
920
|
+
avatarUrl: userData.avatar_url
|
|
921
|
+
};
|
|
922
|
+
tokenCache.set(tokenHash, { user, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
923
|
+
return user;
|
|
924
|
+
} catch (error) {
|
|
925
|
+
if (error.status === 401) {
|
|
926
|
+
throw new shared_exports.UnauthorizedError("GitHub token is invalid or expired");
|
|
927
|
+
} else if (error.status === 403) {
|
|
928
|
+
throw new shared_exports.UnauthorizedError("GitHub token does not have required permissions");
|
|
929
|
+
}
|
|
930
|
+
logger_default.error("Failed to verify GitHub token:", error);
|
|
931
|
+
throw new shared_exports.UnauthorizedError("Failed to verify GitHub token");
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/routes/auth.routes.ts
|
|
936
|
+
var router = (0, import_express.Router)();
|
|
937
|
+
var authController = new AuthController();
|
|
938
|
+
router.get("/status", authController.getStatus.bind(authController));
|
|
939
|
+
router.get("/challenge", authController.getChallenge.bind(authController));
|
|
940
|
+
router.post("/callback", authController.exchangeToken.bind(authController));
|
|
941
|
+
router.post("/logout", authController.logout.bind(authController));
|
|
942
|
+
router.get("/user", authenticateGithubToken, authController.getUser.bind(authController));
|
|
943
|
+
var auth_routes_default = router;
|
|
944
|
+
|
|
945
|
+
// src/routes/user.routes.ts
|
|
946
|
+
var import_express2 = require("express");
|
|
947
|
+
|
|
948
|
+
// src/modules/github/github.service.ts
|
|
949
|
+
var import_rest2 = require("@octokit/rest");
|
|
950
|
+
var import_shared7 = __toESM(require_dist());
|
|
951
|
+
var GitHubService = class {
|
|
952
|
+
async getUserInfo(token) {
|
|
953
|
+
try {
|
|
954
|
+
const octokit = this.getOctokit(token);
|
|
955
|
+
const { data } = await octokit.rest.users.getAuthenticated();
|
|
956
|
+
return {
|
|
957
|
+
login: data.login,
|
|
958
|
+
name: data.name,
|
|
959
|
+
email: data.email
|
|
960
|
+
};
|
|
961
|
+
} catch (error) {
|
|
962
|
+
logger_default.error("Failed to get user info:", error);
|
|
963
|
+
throw new shared_exports.GitHubAPIError(
|
|
964
|
+
`Failed to get user info: ${error.message}`,
|
|
965
|
+
error.status || 500
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
async fetchUserOrganizations(token) {
|
|
970
|
+
try {
|
|
971
|
+
const octokit = this.getOctokit(token);
|
|
972
|
+
const { data } = await octokit.rest.orgs.listForAuthenticatedUser();
|
|
973
|
+
return data;
|
|
974
|
+
} catch (error) {
|
|
975
|
+
logger_default.error("Failed to fetch organizations:", error);
|
|
976
|
+
throw new shared_exports.GitHubAPIError(
|
|
977
|
+
`Failed to fetch organizations: ${error.message}`,
|
|
978
|
+
error.status || 500
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
async fetchUserRepositories(token, affiliation = "owner", perPage = 50) {
|
|
983
|
+
try {
|
|
984
|
+
const octokit = this.getOctokit(token);
|
|
985
|
+
const { data } = await octokit.rest.repos.listForAuthenticatedUser({
|
|
986
|
+
affiliation,
|
|
987
|
+
per_page: perPage,
|
|
988
|
+
sort: "updated"
|
|
989
|
+
});
|
|
990
|
+
return data;
|
|
991
|
+
} catch (error) {
|
|
992
|
+
logger_default.error("Failed to fetch repositories:", error);
|
|
993
|
+
throw new shared_exports.GitHubAPIError(
|
|
994
|
+
`Failed to fetch repositories: ${error.message}`,
|
|
995
|
+
error.status || 500
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
async fetchOrganizationRepositories(token, org, perPage = 50) {
|
|
1000
|
+
try {
|
|
1001
|
+
const octokit = this.getOctokit(token);
|
|
1002
|
+
const { data } = await octokit.rest.repos.listForOrg({
|
|
1003
|
+
org,
|
|
1004
|
+
per_page: perPage,
|
|
1005
|
+
sort: "updated"
|
|
1006
|
+
});
|
|
1007
|
+
return data;
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
logger_default.error(`Failed to fetch repositories for org ${org}:`, error);
|
|
1010
|
+
throw new shared_exports.GitHubAPIError(
|
|
1011
|
+
`Failed to fetch repositories for organization: ${error.message}`,
|
|
1012
|
+
error.status || 500
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
async checkRepositoryAccess(token, owner, repo) {
|
|
1017
|
+
try {
|
|
1018
|
+
const octokit = this.getOctokit(token);
|
|
1019
|
+
const { data } = await octokit.rest.repos.get({ owner, repo });
|
|
1020
|
+
const permissions = data.permissions;
|
|
1021
|
+
if (permissions?.push || permissions?.admin) {
|
|
1022
|
+
return import_shared7.AccessLevel.WRITE;
|
|
1023
|
+
} else if (permissions?.pull) {
|
|
1024
|
+
return import_shared7.AccessLevel.READ_ONLY;
|
|
1025
|
+
}
|
|
1026
|
+
return import_shared7.AccessLevel.NONE;
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
if (error.status === 404 || error.status === 403) {
|
|
1029
|
+
return import_shared7.AccessLevel.NONE;
|
|
1030
|
+
}
|
|
1031
|
+
logger_default.error(`Failed to check repository access for ${owner}/${repo}:`, error);
|
|
1032
|
+
return import_shared7.AccessLevel.NONE;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async validateRegistry(fullName, token) {
|
|
1036
|
+
try {
|
|
1037
|
+
const [owner, repo] = fullName.split("/");
|
|
1038
|
+
const octokit = this.getOctokit(token);
|
|
1039
|
+
try {
|
|
1040
|
+
await octokit.rest.repos.getContent({
|
|
1041
|
+
owner,
|
|
1042
|
+
repo,
|
|
1043
|
+
path: "ai_shelf_registry.json"
|
|
1044
|
+
});
|
|
1045
|
+
return true;
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
if (error.status === 404) {
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
1050
|
+
throw error;
|
|
1051
|
+
}
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
logger_default.error(`Failed to validate registry ${fullName}:`, error);
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async createNewRegistry(token, name, description, isPrivate, organization) {
|
|
1058
|
+
try {
|
|
1059
|
+
const octokit = this.getOctokit(token);
|
|
1060
|
+
let data;
|
|
1061
|
+
if (organization) {
|
|
1062
|
+
const response = await octokit.rest.repos.createInOrg({
|
|
1063
|
+
org: organization,
|
|
1064
|
+
name,
|
|
1065
|
+
description,
|
|
1066
|
+
private: isPrivate,
|
|
1067
|
+
auto_init: true
|
|
1068
|
+
});
|
|
1069
|
+
data = response.data;
|
|
1070
|
+
} else {
|
|
1071
|
+
const response = await octokit.rest.repos.createForAuthenticatedUser({
|
|
1072
|
+
name,
|
|
1073
|
+
description,
|
|
1074
|
+
private: isPrivate,
|
|
1075
|
+
auto_init: true
|
|
1076
|
+
});
|
|
1077
|
+
data = response.data;
|
|
1078
|
+
}
|
|
1079
|
+
logger_default.info(`Created new registry: ${data.full_name}`);
|
|
1080
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1081
|
+
const registryConfig = {
|
|
1082
|
+
name: "AIShelf Registry",
|
|
1083
|
+
version: "1.0.0",
|
|
1084
|
+
description: "AIShelf registry for workflows, rules, skills, and prompts"
|
|
1085
|
+
};
|
|
1086
|
+
await octokit.rest.repos.createOrUpdateFileContents({
|
|
1087
|
+
owner: data.owner.login,
|
|
1088
|
+
repo: data.name,
|
|
1089
|
+
path: "ai_shelf_registry.json",
|
|
1090
|
+
message: "Initialize AIShelf registry",
|
|
1091
|
+
content: Buffer.from(JSON.stringify(registryConfig, null, 2)).toString("base64")
|
|
1092
|
+
});
|
|
1093
|
+
return data;
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
logger_default.error("Failed to create new registry:", error);
|
|
1096
|
+
throw new shared_exports.GitHubAPIError(
|
|
1097
|
+
`Failed to create new registry: ${error.message}`,
|
|
1098
|
+
error.status || 500
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
getOctokit(token) {
|
|
1103
|
+
return new import_rest2.Octokit({
|
|
1104
|
+
auth: token,
|
|
1105
|
+
userAgent: "AIShelf-Service"
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
var githubService = new GitHubService();
|
|
1110
|
+
|
|
1111
|
+
// src/controllers/user.controller.ts
|
|
1112
|
+
var UserController = class {
|
|
1113
|
+
async getUser(req, res, next) {
|
|
1114
|
+
try {
|
|
1115
|
+
const user = req.githubUser;
|
|
1116
|
+
const response = {
|
|
1117
|
+
success: true,
|
|
1118
|
+
data: {
|
|
1119
|
+
user: {
|
|
1120
|
+
id: user.githubId,
|
|
1121
|
+
username: user.githubUsername,
|
|
1122
|
+
email: user.email,
|
|
1123
|
+
avatarUrl: user.avatarUrl
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
res.json(response);
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
next(error);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
async getUserRepositories(req, res, next) {
|
|
1133
|
+
try {
|
|
1134
|
+
const token = req.githubToken;
|
|
1135
|
+
const affiliation = req.query.affiliation || "owner";
|
|
1136
|
+
const perPage = parseInt(req.query.perPage) || 50;
|
|
1137
|
+
const repos = await githubService.fetchUserRepositories(token, affiliation, perPage);
|
|
1138
|
+
const response = {
|
|
1139
|
+
success: true,
|
|
1140
|
+
data: {
|
|
1141
|
+
repositories: repos.map((r) => ({
|
|
1142
|
+
id: r.id,
|
|
1143
|
+
name: r.name,
|
|
1144
|
+
fullName: r.full_name,
|
|
1145
|
+
owner: r.owner.login,
|
|
1146
|
+
cloneUrl: r.clone_url,
|
|
1147
|
+
isPrivate: r.private,
|
|
1148
|
+
description: r.description || void 0,
|
|
1149
|
+
defaultBranch: void 0
|
|
1150
|
+
}))
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
res.json(response);
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
next(error);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
async getUserOrganizations(req, res, next) {
|
|
1159
|
+
try {
|
|
1160
|
+
const token = req.githubToken;
|
|
1161
|
+
const orgs = await githubService.fetchUserOrganizations(token);
|
|
1162
|
+
const response = {
|
|
1163
|
+
success: true,
|
|
1164
|
+
data: {
|
|
1165
|
+
organizations: orgs.map((o) => ({
|
|
1166
|
+
id: o.id,
|
|
1167
|
+
login: o.login,
|
|
1168
|
+
avatarUrl: o.avatar_url || "",
|
|
1169
|
+
description: o.description || void 0
|
|
1170
|
+
}))
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
res.json(response);
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
next(error);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
// src/routes/user.routes.ts
|
|
1181
|
+
var router2 = (0, import_express2.Router)();
|
|
1182
|
+
var userController = new UserController();
|
|
1183
|
+
router2.get(
|
|
1184
|
+
"/",
|
|
1185
|
+
authenticateGithubToken,
|
|
1186
|
+
userController.getUser.bind(userController)
|
|
1187
|
+
);
|
|
1188
|
+
router2.get(
|
|
1189
|
+
"/repos",
|
|
1190
|
+
authenticateGithubToken,
|
|
1191
|
+
userController.getUserRepositories.bind(userController)
|
|
1192
|
+
);
|
|
1193
|
+
router2.get(
|
|
1194
|
+
"/orgs",
|
|
1195
|
+
authenticateGithubToken,
|
|
1196
|
+
userController.getUserOrganizations.bind(userController)
|
|
1197
|
+
);
|
|
1198
|
+
var user_routes_default = router2;
|
|
1199
|
+
|
|
1200
|
+
// src/routes/audit.routes.ts
|
|
1201
|
+
var import_express3 = require("express");
|
|
1202
|
+
|
|
1203
|
+
// src/controllers/audit.controller.ts
|
|
1204
|
+
var import_fs2 = __toESM(require("fs"));
|
|
1205
|
+
|
|
1206
|
+
// src/modules/audit/audit.service.ts
|
|
1207
|
+
var import_fs = __toESM(require("fs"));
|
|
1208
|
+
var import_path3 = __toESM(require("path"));
|
|
1209
|
+
var MAX_FILE_SIZE = 500 * 1024;
|
|
1210
|
+
var AuditService = class {
|
|
1211
|
+
constructor(storage = storageService) {
|
|
1212
|
+
this.storage = storage;
|
|
1213
|
+
}
|
|
1214
|
+
getLogFilePath(client) {
|
|
1215
|
+
return this.storage.getAuditLogPath(client);
|
|
1216
|
+
}
|
|
1217
|
+
ensureLogDir() {
|
|
1218
|
+
const logsDir = import_path3.default.join(this.storage.getStorageRoot(), "logs");
|
|
1219
|
+
if (!import_fs.default.existsSync(logsDir)) {
|
|
1220
|
+
import_fs.default.mkdirSync(logsDir, { recursive: true });
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async appendLogs(client, entries) {
|
|
1224
|
+
this.ensureLogDir();
|
|
1225
|
+
const logFilePath = this.getLogFilePath(client);
|
|
1226
|
+
const lines = entries.map((entry) => JSON.stringify(entry)).join("\n") + "\n";
|
|
1227
|
+
import_fs.default.appendFileSync(logFilePath, lines, "utf8");
|
|
1228
|
+
this.rotateIfNeeded(logFilePath);
|
|
1229
|
+
logger_default.info(`[AUDIT] Appended ${entries.length} log(s) for client "${client}"`);
|
|
1230
|
+
}
|
|
1231
|
+
async getLogs(client, filters) {
|
|
1232
|
+
const logFilePath = this.getLogFilePath(client);
|
|
1233
|
+
if (!import_fs.default.existsSync(logFilePath)) {
|
|
1234
|
+
return [];
|
|
1235
|
+
}
|
|
1236
|
+
const content = import_fs.default.readFileSync(logFilePath, "utf8");
|
|
1237
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
1238
|
+
let logs = lines.map((line) => JSON.parse(line));
|
|
1239
|
+
if (filters?.startDate) {
|
|
1240
|
+
logs = logs.filter((log) => log.timestamp >= filters.startDate);
|
|
1241
|
+
}
|
|
1242
|
+
if (filters?.endDate) {
|
|
1243
|
+
logs = logs.filter((log) => log.timestamp <= filters.endDate);
|
|
1244
|
+
}
|
|
1245
|
+
if (filters?.operation) {
|
|
1246
|
+
logs = logs.filter((log) => log.operation === filters.operation);
|
|
1247
|
+
}
|
|
1248
|
+
if (filters?.repository) {
|
|
1249
|
+
logs = logs.filter((log) => log.repository === filters.repository);
|
|
1250
|
+
}
|
|
1251
|
+
return logs;
|
|
1252
|
+
}
|
|
1253
|
+
rotateIfNeeded(logFilePath) {
|
|
1254
|
+
try {
|
|
1255
|
+
const stats = import_fs.default.statSync(logFilePath);
|
|
1256
|
+
if (stats.size <= MAX_FILE_SIZE) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const content = import_fs.default.readFileSync(logFilePath, "utf8");
|
|
1260
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
1261
|
+
const entriesToRemove = Math.ceil(lines.length * 0.2);
|
|
1262
|
+
const newLines = lines.slice(entriesToRemove);
|
|
1263
|
+
import_fs.default.writeFileSync(logFilePath, newLines.join("\n") + "\n", "utf8");
|
|
1264
|
+
logger_default.info(`Audit log rotated: removed ${entriesToRemove} oldest entries (file was ${Math.round(stats.size / 1024)}KB)`);
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
logger_default.error("Failed to rotate audit log:", error);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
var auditService = new AuditService();
|
|
1271
|
+
|
|
1272
|
+
// src/modules/audit/audit.schemas.ts
|
|
1273
|
+
var import_zod2 = require("zod");
|
|
1274
|
+
var AuditOperationSchema = import_zod2.z.enum([
|
|
1275
|
+
"CONNECT_REGISTRY",
|
|
1276
|
+
"DISCONNECT_REGISTRY",
|
|
1277
|
+
"CLONE_REGISTRY",
|
|
1278
|
+
"SYNC_REGISTRY",
|
|
1279
|
+
"CREATE_REGISTRY",
|
|
1280
|
+
"READ_FILE",
|
|
1281
|
+
"CREATE_PACKAGE",
|
|
1282
|
+
"DELETE_PACKAGE",
|
|
1283
|
+
"UPLOAD_RESOURCE",
|
|
1284
|
+
"DELETE_RESOURCE",
|
|
1285
|
+
"PUSH_CHANGES"
|
|
1286
|
+
]);
|
|
1287
|
+
var AuditLogEntrySchema = import_zod2.z.object({
|
|
1288
|
+
timestamp: import_zod2.z.string(),
|
|
1289
|
+
operation: AuditOperationSchema,
|
|
1290
|
+
repository: import_zod2.z.string().optional(),
|
|
1291
|
+
details: import_zod2.z.string(),
|
|
1292
|
+
success: import_zod2.z.boolean(),
|
|
1293
|
+
error: import_zod2.z.string().optional(),
|
|
1294
|
+
userId: import_zod2.z.string().optional()
|
|
1295
|
+
});
|
|
1296
|
+
var QueryLogsRequestSchema = import_zod2.z.object({
|
|
1297
|
+
startDate: import_zod2.z.string().optional(),
|
|
1298
|
+
endDate: import_zod2.z.string().optional(),
|
|
1299
|
+
operation: AuditOperationSchema.optional(),
|
|
1300
|
+
repository: import_zod2.z.string().optional(),
|
|
1301
|
+
userId: import_zod2.z.string().optional(),
|
|
1302
|
+
success: import_zod2.z.boolean().optional()
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
// src/controllers/audit.controller.ts
|
|
1306
|
+
var AuditController = class {
|
|
1307
|
+
constructor(audit = auditService, storage = storageService) {
|
|
1308
|
+
this.audit = audit;
|
|
1309
|
+
this.storage = storage;
|
|
1310
|
+
}
|
|
1311
|
+
async getLogs(req, res, next) {
|
|
1312
|
+
try {
|
|
1313
|
+
const client = req.clientInfo.client;
|
|
1314
|
+
const filters = {
|
|
1315
|
+
startDate: req.query.startDate,
|
|
1316
|
+
endDate: req.query.endDate,
|
|
1317
|
+
operation: req.query.operation,
|
|
1318
|
+
repository: req.query.repository
|
|
1319
|
+
};
|
|
1320
|
+
const logs = await this.audit.getLogs(client, filters);
|
|
1321
|
+
const response = {
|
|
1322
|
+
success: true,
|
|
1323
|
+
data: { logs }
|
|
1324
|
+
};
|
|
1325
|
+
res.json(response);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
next(error);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
async appendLogs(req, res, next) {
|
|
1331
|
+
try {
|
|
1332
|
+
const client = req.clientInfo.client;
|
|
1333
|
+
const body = req.body;
|
|
1334
|
+
const entries = Array.isArray(body) ? body : [body];
|
|
1335
|
+
if (entries.length === 0) {
|
|
1336
|
+
throw new shared_exports.ValidationError("Request body must contain at least one log entry");
|
|
1337
|
+
}
|
|
1338
|
+
for (const entry of entries) {
|
|
1339
|
+
if (!entry.timestamp || !entry.operation || !entry.details || typeof entry.success !== "boolean") {
|
|
1340
|
+
throw new shared_exports.ValidationError("Each log entry must have: timestamp, operation, details, success");
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
await this.audit.appendLogs(client, entries);
|
|
1344
|
+
const response = {
|
|
1345
|
+
success: true,
|
|
1346
|
+
data: { appended: entries.length }
|
|
1347
|
+
};
|
|
1348
|
+
res.json(response);
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
next(error);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
async uploadToBackend(req, res, next) {
|
|
1354
|
+
try {
|
|
1355
|
+
const client = req.clientInfo.client;
|
|
1356
|
+
if (!config.BACKEND_URL) {
|
|
1357
|
+
throw new shared_exports.ValidationError("Backend URL not configured");
|
|
1358
|
+
}
|
|
1359
|
+
const logFilePath = this.storage.getAuditLogPath(client);
|
|
1360
|
+
if (!import_fs2.default.existsSync(logFilePath)) {
|
|
1361
|
+
throw new shared_exports.ValidationError("No audit logs found for this client");
|
|
1362
|
+
}
|
|
1363
|
+
const logContent = import_fs2.default.readFileSync(logFilePath);
|
|
1364
|
+
const lines = logContent.toString("utf8").split("\n").filter((l) => l.trim());
|
|
1365
|
+
if (lines.length === 0) {
|
|
1366
|
+
throw new shared_exports.ValidationError("No audit logs found for this client");
|
|
1367
|
+
}
|
|
1368
|
+
const FormData = (await import("form-data")).default;
|
|
1369
|
+
const form = new FormData();
|
|
1370
|
+
form.append("timestamp", (/* @__PURE__ */ new Date()).toISOString());
|
|
1371
|
+
form.append("logs", logContent, {
|
|
1372
|
+
filename: `audit-${client}-${Date.now()}.jsonl`,
|
|
1373
|
+
contentType: "application/x-ndjson"
|
|
1374
|
+
});
|
|
1375
|
+
const response = await fetch(`${config.BACKEND_URL}/audit-logs/upload`, {
|
|
1376
|
+
method: "POST",
|
|
1377
|
+
headers: {
|
|
1378
|
+
...form.getHeaders()
|
|
1379
|
+
},
|
|
1380
|
+
body: form
|
|
1381
|
+
});
|
|
1382
|
+
if (!response.ok) {
|
|
1383
|
+
const errorText = await response.text();
|
|
1384
|
+
throw new Error(`Backend upload failed: ${errorText}`);
|
|
1385
|
+
}
|
|
1386
|
+
const result = await response.json();
|
|
1387
|
+
const apiResponse = {
|
|
1388
|
+
success: true,
|
|
1389
|
+
data: { uploadId: result.uploadId }
|
|
1390
|
+
};
|
|
1391
|
+
res.json(apiResponse);
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
next(error);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
// src/routes/audit.routes.ts
|
|
1399
|
+
var router3 = (0, import_express3.Router)();
|
|
1400
|
+
var auditController = new AuditController();
|
|
1401
|
+
router3.get("/", auditController.getLogs.bind(auditController));
|
|
1402
|
+
router3.patch("/", auditController.appendLogs.bind(auditController));
|
|
1403
|
+
router3.post("/", auditController.uploadToBackend.bind(auditController));
|
|
1404
|
+
var audit_routes_default = router3;
|
|
1405
|
+
|
|
1406
|
+
// src/routes/org.routes.ts
|
|
1407
|
+
var import_express4 = require("express");
|
|
1408
|
+
|
|
1409
|
+
// src/controllers/org.controller.ts
|
|
1410
|
+
var OrgController = class {
|
|
1411
|
+
async getOrganizationRepositories(req, res, next) {
|
|
1412
|
+
try {
|
|
1413
|
+
const token = req.githubToken;
|
|
1414
|
+
const org = req.params.org;
|
|
1415
|
+
const perPage = parseInt(req.query.perPage) || 50;
|
|
1416
|
+
const repos = await githubService.fetchOrganizationRepositories(token, org, perPage);
|
|
1417
|
+
const response = {
|
|
1418
|
+
success: true,
|
|
1419
|
+
data: {
|
|
1420
|
+
repositories: repos.map((r) => ({
|
|
1421
|
+
id: r.id,
|
|
1422
|
+
name: r.name,
|
|
1423
|
+
fullName: r.full_name,
|
|
1424
|
+
owner: r.owner.login,
|
|
1425
|
+
cloneUrl: r.clone_url,
|
|
1426
|
+
isPrivate: r.private,
|
|
1427
|
+
description: r.description || void 0,
|
|
1428
|
+
defaultBranch: void 0
|
|
1429
|
+
}))
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
res.json(response);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
next(error);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
// src/routes/org.routes.ts
|
|
1440
|
+
var router4 = (0, import_express4.Router)();
|
|
1441
|
+
var orgController = new OrgController();
|
|
1442
|
+
router4.get("/:org/repos", authenticateGithubToken, orgController.getOrganizationRepositories.bind(orgController));
|
|
1443
|
+
var org_routes_default = router4;
|
|
1444
|
+
|
|
1445
|
+
// src/routes/registry.routes.ts
|
|
1446
|
+
var import_express7 = require("express");
|
|
1447
|
+
|
|
1448
|
+
// src/routes/package.routes.ts
|
|
1449
|
+
var import_express6 = require("express");
|
|
1450
|
+
|
|
1451
|
+
// src/modules/package/package.service.ts
|
|
1452
|
+
var import_path4 = __toESM(require("path"));
|
|
1453
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
1454
|
+
|
|
1455
|
+
// src/modules/git/git.service.ts
|
|
1456
|
+
var import_simple_git = __toESM(require("simple-git"));
|
|
1457
|
+
var GitService = class {
|
|
1458
|
+
async cloneRepository(cloneUrl, owner, repo, token) {
|
|
1459
|
+
await storageService.ensureStorageExists();
|
|
1460
|
+
const localPath = storageService.getRegistryPath(owner, repo);
|
|
1461
|
+
const cleanUrl = `https://github.com/${owner}/${repo}.git`;
|
|
1462
|
+
try {
|
|
1463
|
+
const git = await this.getConfiguredGitInstance(token);
|
|
1464
|
+
await this.performGitOperationSafely(git, owner, repo, token, async (authenticatedUrl) => {
|
|
1465
|
+
await git.clone(authenticatedUrl, localPath);
|
|
1466
|
+
});
|
|
1467
|
+
const repoGit = await this.getConfiguredGitInstance(token, localPath);
|
|
1468
|
+
await repoGit.remote(["set-url", "origin", cleanUrl]);
|
|
1469
|
+
logger_default.info(`Successfully cloned registry ${owner}/${repo} to ${localPath}`);
|
|
1470
|
+
return localPath;
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
logger_default.error("Failed to clone repository:", error);
|
|
1473
|
+
throw new shared_exports.GitOperationError(`Failed to clone repository: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
async syncRepository(owner, repo, token) {
|
|
1477
|
+
const localPath = storageService.getRegistryPath(owner, repo);
|
|
1478
|
+
try {
|
|
1479
|
+
const git = await this.getConfiguredGitInstance(token, localPath);
|
|
1480
|
+
const isRepo = await git.checkIsRepo();
|
|
1481
|
+
if (!isRepo) {
|
|
1482
|
+
throw new shared_exports.GitOperationError(`Directory ${localPath} is not a git repository`);
|
|
1483
|
+
}
|
|
1484
|
+
const beforeCommit = await git.revparse(["HEAD"]);
|
|
1485
|
+
await this.performGitOperationSafely(git, owner, repo, token, async () => {
|
|
1486
|
+
await git.pull();
|
|
1487
|
+
});
|
|
1488
|
+
const afterCommit = await git.revparse(["HEAD"]);
|
|
1489
|
+
logger_default.info(`Successfully synced registry ${owner}/${repo} (${beforeCommit.substring(0, 7)} -> ${afterCommit.substring(0, 7)})`);
|
|
1490
|
+
return { beforeCommit, afterCommit };
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
logger_default.error("Failed to sync repository:", error);
|
|
1493
|
+
throw new shared_exports.GitOperationError(`Failed to sync repository: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
async getCommitDiff(owner, repo, fromCommit, toCommit) {
|
|
1497
|
+
const localPath = storageService.getRegistryPath(owner, repo);
|
|
1498
|
+
try {
|
|
1499
|
+
const git = (0, import_simple_git.default)(localPath);
|
|
1500
|
+
const diff = await git.diff(["--name-only", fromCommit, toCommit]);
|
|
1501
|
+
return diff.split("\n").filter((line) => line.trim().length > 0);
|
|
1502
|
+
} catch (error) {
|
|
1503
|
+
logger_default.error("Failed to get changed files between commits:", error);
|
|
1504
|
+
throw new shared_exports.GitOperationError(`Failed to get changed files: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
async commitChanges(owner, repo, message, token) {
|
|
1508
|
+
const localPath = storageService.getRegistryPath(owner, repo);
|
|
1509
|
+
try {
|
|
1510
|
+
const git = await this.getConfiguredGitInstance(token, localPath);
|
|
1511
|
+
await this.syncRepository(owner, repo, token);
|
|
1512
|
+
await this.performGitOperationSafely(git, owner, repo, token, async () => {
|
|
1513
|
+
await git.add(".");
|
|
1514
|
+
await git.commit(message);
|
|
1515
|
+
await git.push();
|
|
1516
|
+
});
|
|
1517
|
+
logger_default.info(`Successfully committed and pushed changes to ${owner}/${repo}`);
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
logger_default.error("Failed to commit changes:", error);
|
|
1520
|
+
throw new shared_exports.GitOperationError(`Failed to commit changes: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
async getConfiguredGitInstance(token, path10) {
|
|
1524
|
+
const git = path10 ? (0, import_simple_git.default)(path10) : (0, import_simple_git.default)();
|
|
1525
|
+
try {
|
|
1526
|
+
const userInfo = await githubService.getUserInfo(token);
|
|
1527
|
+
await git.addConfig("user.name", userInfo.name || userInfo.login);
|
|
1528
|
+
await git.addConfig("user.email", userInfo.email || `${userInfo.login}@users.noreply.github.com`);
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
logger_default.warn("Failed to configure git user info:", error);
|
|
1531
|
+
}
|
|
1532
|
+
return git;
|
|
1533
|
+
}
|
|
1534
|
+
async performGitOperationSafely(git, owner, repo, token, operation) {
|
|
1535
|
+
const authenticatedUrl = `https://${token}@github.com/${owner}/${repo}.git`;
|
|
1536
|
+
const cleanUrl = `https://github.com/${owner}/${repo}.git`;
|
|
1537
|
+
try {
|
|
1538
|
+
await operation(authenticatedUrl);
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
if (error.message?.includes("authentication") || error.message?.includes("credentials")) {
|
|
1541
|
+
throw new shared_exports.GitOperationError("Git authentication failed. Please check your GitHub token.");
|
|
1542
|
+
}
|
|
1543
|
+
throw error;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
var gitService = new GitService();
|
|
1548
|
+
|
|
1549
|
+
// src/modules/git/git.schemas.ts
|
|
1550
|
+
var import_zod3 = require("zod");
|
|
1551
|
+
var CommitChangesRequestSchema = import_zod3.z.object({
|
|
1552
|
+
owner: import_zod3.z.string().min(1),
|
|
1553
|
+
repo: import_zod3.z.string().min(1),
|
|
1554
|
+
message: import_zod3.z.string().min(1)
|
|
1555
|
+
});
|
|
1556
|
+
var GetStatusRequestSchema = import_zod3.z.object({
|
|
1557
|
+
owner: import_zod3.z.string().min(1),
|
|
1558
|
+
repo: import_zod3.z.string().min(1)
|
|
1559
|
+
});
|
|
1560
|
+
var GetChangedFilesRequestSchema = import_zod3.z.object({
|
|
1561
|
+
owner: import_zod3.z.string().min(1),
|
|
1562
|
+
repo: import_zod3.z.string().min(1)
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
// src/modules/registry/registry.service.ts
|
|
1566
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
1567
|
+
|
|
1568
|
+
// src/modules/subscription/subscription.types.ts
|
|
1569
|
+
var SubscriptionRequiredError = class extends Error {
|
|
1570
|
+
constructor(message, actionUrl, errorType) {
|
|
1571
|
+
super(message);
|
|
1572
|
+
this.actionUrl = actionUrl;
|
|
1573
|
+
this.errorType = errorType;
|
|
1574
|
+
this.name = "SubscriptionRequiredError";
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
// src/modules/subscription/subscription.service.ts
|
|
1579
|
+
var SubscriptionService = class {
|
|
1580
|
+
async connectToRegistry(token, owner, repo) {
|
|
1581
|
+
try {
|
|
1582
|
+
await apiService.apiRequest(`/registry/${owner}/${repo}/connect`, token, { method: "POST" });
|
|
1583
|
+
} catch (error) {
|
|
1584
|
+
const errorData = error.data?.data;
|
|
1585
|
+
if (error.statusCode === 403) {
|
|
1586
|
+
if (!errorData) {
|
|
1587
|
+
throw error;
|
|
1588
|
+
}
|
|
1589
|
+
const entityName = `@${owner}`;
|
|
1590
|
+
const actionUrl = this.getSubscriptionUrl(errorData.ownerEntityId);
|
|
1591
|
+
const message = errorData.error || `Subscription required for ${entityName} repositories`;
|
|
1592
|
+
const subscriptionError = new SubscriptionRequiredError(message, actionUrl, "SUBSCRIPTION_REQUIRED");
|
|
1593
|
+
throw subscriptionError;
|
|
1594
|
+
}
|
|
1595
|
+
if (error.statusCode === 429) {
|
|
1596
|
+
if (!errorData) {
|
|
1597
|
+
throw error;
|
|
1598
|
+
}
|
|
1599
|
+
const entityName = `@${owner}`;
|
|
1600
|
+
const actionUrl = errorData.ownerEntityId ? this.getSubscriptionUrl(errorData.ownerEntityId) : this.getSubscriptionUrl();
|
|
1601
|
+
const message = errorData.error || `Seat limit reached for ${entityName}`;
|
|
1602
|
+
throw new SubscriptionRequiredError(message, actionUrl, "SEAT_LIMIT_REACHED");
|
|
1603
|
+
}
|
|
1604
|
+
throw error;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
async disconnectFromRegistry(token, owner, repo) {
|
|
1608
|
+
try {
|
|
1609
|
+
await apiService.apiRequest(`/registry/${owner}/${repo}/disconnect`, token, { method: "DELETE" });
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
logger_default.error(`Failed to disconnect from registry ${owner}/${repo} in backend:`, error);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
getSubscriptionUrl(entityId) {
|
|
1615
|
+
const params = new URLSearchParams({ source: "service" });
|
|
1616
|
+
if (entityId) {
|
|
1617
|
+
params.set("entity_id", entityId);
|
|
1618
|
+
}
|
|
1619
|
+
return `https://aishelf.dev#pricing?${params.toString()}`;
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
var subscriptionService = new SubscriptionService();
|
|
1623
|
+
|
|
1624
|
+
// src/modules/registry/registry.service.ts
|
|
1625
|
+
var import_shared11 = __toESM(require_dist());
|
|
1626
|
+
var RegistryService = class {
|
|
1627
|
+
constructor(git = gitService, github = githubService, storage = storageService, subscription = subscriptionService) {
|
|
1628
|
+
this.git = git;
|
|
1629
|
+
this.github = github;
|
|
1630
|
+
this.storage = storage;
|
|
1631
|
+
this.subscription = subscription;
|
|
1632
|
+
}
|
|
1633
|
+
async connect(owner, repo, token, options) {
|
|
1634
|
+
const repoExists = await this.checkRepositoryExists(owner, repo, token);
|
|
1635
|
+
if (!repoExists) {
|
|
1636
|
+
if (options?.createIfNotExists) {
|
|
1637
|
+
const githubUsername = options.githubUsername || (await this.github.getUserInfo(token)).login;
|
|
1638
|
+
const isOrganization = githubUsername !== owner;
|
|
1639
|
+
await this.github.createNewRegistry(
|
|
1640
|
+
token,
|
|
1641
|
+
repo,
|
|
1642
|
+
options.description || `AIShelf registry: ${repo}`,
|
|
1643
|
+
options.isPrivate ?? true,
|
|
1644
|
+
isOrganization ? owner : void 0
|
|
1645
|
+
);
|
|
1646
|
+
logger_default.info(`Created new registry ${owner}/${repo}`);
|
|
1647
|
+
} else {
|
|
1648
|
+
throw new shared_exports.NotFoundError(`Repository ${owner}/${repo} does not exist`);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
const isValid = await this.github.validateRegistry(`${owner}/${repo}`, token);
|
|
1652
|
+
if (!isValid) {
|
|
1653
|
+
throw new shared_exports.ValidationError(`Repository ${owner}/${repo} is not a valid AIShelf registry (missing ai_shelf_registry.json)`);
|
|
1654
|
+
}
|
|
1655
|
+
await this.subscription.connectToRegistry(token, owner, repo);
|
|
1656
|
+
const cloneUrl = `https://github.com/${owner}/${repo}.git`;
|
|
1657
|
+
try {
|
|
1658
|
+
await this.git.cloneRepository(cloneUrl, owner, repo, token);
|
|
1659
|
+
const registry = {
|
|
1660
|
+
owner,
|
|
1661
|
+
repo,
|
|
1662
|
+
fullName: `${owner}/${repo}`,
|
|
1663
|
+
cloneUrl,
|
|
1664
|
+
lastSynced: (/* @__PURE__ */ new Date()).toISOString()
|
|
1665
|
+
};
|
|
1666
|
+
await this.storage.saveConnectedRegistry(registry);
|
|
1667
|
+
logger_default.info(`Connected to registry ${owner}/${repo}`);
|
|
1668
|
+
return registry;
|
|
1669
|
+
} catch (error) {
|
|
1670
|
+
const registryPath = this.storage.getRegistryPath(owner, repo);
|
|
1671
|
+
try {
|
|
1672
|
+
await import_promises2.default.rm(registryPath, { recursive: true, force: true });
|
|
1673
|
+
} catch (cleanupError) {
|
|
1674
|
+
logger_default.error("Failed to clean up partial clone:", cleanupError);
|
|
1675
|
+
}
|
|
1676
|
+
throw error;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
async sync(owner, repo, token) {
|
|
1680
|
+
await this.subscription.connectToRegistry(token, owner, repo);
|
|
1681
|
+
const registries = await this.list(token);
|
|
1682
|
+
const registry = registries.find((r) => r.owner === owner && r.repo === repo);
|
|
1683
|
+
if (!registry) {
|
|
1684
|
+
throw new shared_exports.NotFoundError(`Registry ${owner}/${repo} is not connected`);
|
|
1685
|
+
}
|
|
1686
|
+
try {
|
|
1687
|
+
const { beforeCommit, afterCommit } = await this.git.syncRepository(owner, repo, token);
|
|
1688
|
+
const changedFiles = await this.git.getCommitDiff(owner, repo, beforeCommit, afterCommit);
|
|
1689
|
+
registry.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
|
|
1690
|
+
await this.storage.saveConnectedRegistry(registry);
|
|
1691
|
+
logger_default.info(`Synced registry ${owner}/${repo}, ${changedFiles.length} files changed`);
|
|
1692
|
+
return { changedFiles };
|
|
1693
|
+
} catch (error) {
|
|
1694
|
+
logger_default.error(`Failed to sync registry ${owner}/${repo}:`, error);
|
|
1695
|
+
throw error;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
async list(token) {
|
|
1699
|
+
const registries = await this.storage.getConnectedRegistries();
|
|
1700
|
+
const enriched = await Promise.all(
|
|
1701
|
+
registries.map(async (reg) => {
|
|
1702
|
+
const accessLevel = token ? await this.github.checkRepositoryAccess(token, reg.owner, reg.repo) : import_shared11.AccessLevel.NONE;
|
|
1703
|
+
const isTrusted = !await this.storage.shouldShowSecurityViolations(reg.owner, reg.repo);
|
|
1704
|
+
return {
|
|
1705
|
+
owner: reg.owner,
|
|
1706
|
+
repo: reg.repo,
|
|
1707
|
+
fullName: reg.fullName,
|
|
1708
|
+
cloneUrl: reg.cloneUrl,
|
|
1709
|
+
localPath: this.storage.getRegistryPath(reg.owner, reg.repo),
|
|
1710
|
+
accessLevel,
|
|
1711
|
+
trusted: isTrusted,
|
|
1712
|
+
enabled: true,
|
|
1713
|
+
lastSync: reg.lastSynced,
|
|
1714
|
+
connectedBy: ""
|
|
1715
|
+
// ENHANCEMENT: Future implementation
|
|
1716
|
+
};
|
|
1717
|
+
})
|
|
1718
|
+
);
|
|
1719
|
+
enriched.sort((a, b) => {
|
|
1720
|
+
const order = { [import_shared11.AccessLevel.WRITE]: 0, [import_shared11.AccessLevel.READ_ONLY]: 1, [import_shared11.AccessLevel.NONE]: 2 };
|
|
1721
|
+
return (order[a.accessLevel] ?? 3) - (order[b.accessLevel] ?? 3);
|
|
1722
|
+
});
|
|
1723
|
+
return enriched;
|
|
1724
|
+
}
|
|
1725
|
+
async disconnect(owner, repo, token) {
|
|
1726
|
+
await this.subscription.disconnectFromRegistry(token, owner, repo);
|
|
1727
|
+
try {
|
|
1728
|
+
const registryPath = this.storage.getRegistryPath(owner, repo);
|
|
1729
|
+
await import_promises2.default.rm(registryPath, { recursive: true, force: true });
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
logger_default.error("Failed to delete registry directory:", error);
|
|
1732
|
+
}
|
|
1733
|
+
const registries = await this.storage.getConnectedRegistries();
|
|
1734
|
+
const updatedRegistries = registries.filter(
|
|
1735
|
+
(r) => !(r.owner === owner && r.repo === repo)
|
|
1736
|
+
);
|
|
1737
|
+
await this.storage.updateConnectedRegistries(updatedRegistries);
|
|
1738
|
+
logger_default.info(`Disconnected from registry ${owner}/${repo}`);
|
|
1739
|
+
}
|
|
1740
|
+
async checkRepositoryExists(owner, repo, token) {
|
|
1741
|
+
try {
|
|
1742
|
+
const accessLevel = await this.github.checkRepositoryAccess(token, owner, repo);
|
|
1743
|
+
return accessLevel !== import_shared11.AccessLevel.NONE;
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
logger_default.error(`Failed to check if repository exists: ${owner}/${repo}`, error);
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
var registryService = new RegistryService();
|
|
1751
|
+
|
|
1752
|
+
// src/modules/registry/registry.middleware.ts
|
|
1753
|
+
var import_shared13 = __toESM(require_dist());
|
|
1754
|
+
async function ensureRegistryExists(req, res, next) {
|
|
1755
|
+
try {
|
|
1756
|
+
const owner = req.params.owner;
|
|
1757
|
+
const repo = req.params.repo;
|
|
1758
|
+
const exists = await storageService.checkRegistryExists(owner, repo);
|
|
1759
|
+
if (!exists) {
|
|
1760
|
+
throw new shared_exports.NotFoundError(`Registry ${owner}/${repo} not found`);
|
|
1761
|
+
}
|
|
1762
|
+
next();
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
next(error);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
async function ensureRegistryDoesNotExist(req, res, next) {
|
|
1768
|
+
try {
|
|
1769
|
+
const owner = req.params.owner;
|
|
1770
|
+
const repo = req.params.repo;
|
|
1771
|
+
const registries = await storageService.getConnectedRegistries();
|
|
1772
|
+
const existing = registries.find((r) => r.owner === owner && r.repo === repo);
|
|
1773
|
+
if (existing) {
|
|
1774
|
+
throw new shared_exports.ConflictError(`Registry ${owner}/${repo} is already connected`);
|
|
1775
|
+
}
|
|
1776
|
+
next();
|
|
1777
|
+
} catch (error) {
|
|
1778
|
+
next(error);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
async function validateRepositoryWriteAccess(req, res, next) {
|
|
1782
|
+
try {
|
|
1783
|
+
const owner = req.params.owner;
|
|
1784
|
+
const repo = req.params.repo;
|
|
1785
|
+
const token = req.githubToken;
|
|
1786
|
+
const accessLevel = await githubService.checkRepositoryAccess(token, owner, repo);
|
|
1787
|
+
if (accessLevel !== import_shared13.AccessLevel.WRITE) {
|
|
1788
|
+
throw new shared_exports.UnauthorizedError(`Write access required for ${owner}/${repo}`);
|
|
1789
|
+
}
|
|
1790
|
+
next();
|
|
1791
|
+
} catch (error) {
|
|
1792
|
+
next(error);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
async function ensureRegistrySync(req, res, next) {
|
|
1796
|
+
try {
|
|
1797
|
+
const owner = req.params.owner;
|
|
1798
|
+
const repo = req.params.repo;
|
|
1799
|
+
const token = req.githubToken;
|
|
1800
|
+
await registryService.sync(owner, repo, token);
|
|
1801
|
+
next();
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
next(error);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
function validateResourceType(req, res, next) {
|
|
1807
|
+
const resourceType = req.params.resourceType;
|
|
1808
|
+
if (!(0, import_shared13.isValidResourceType)(resourceType)) {
|
|
1809
|
+
return next(new shared_exports.ValidationError(`Invalid resource type: ${resourceType}`));
|
|
1810
|
+
}
|
|
1811
|
+
next();
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// src/modules/registry/registry.schemas.ts
|
|
1815
|
+
var import_zod4 = require("zod");
|
|
1816
|
+
var ConnectedRegistrySchema = import_zod4.z.object({
|
|
1817
|
+
owner: import_zod4.z.string().min(1),
|
|
1818
|
+
repo: import_zod4.z.string().min(1),
|
|
1819
|
+
fullName: import_zod4.z.string().min(1),
|
|
1820
|
+
cloneUrl: import_zod4.z.string().url(),
|
|
1821
|
+
lastSynced: import_zod4.z.date().optional()
|
|
1822
|
+
});
|
|
1823
|
+
var RegistryConfigSchema = import_zod4.z.object({
|
|
1824
|
+
connectedRegistries: import_zod4.z.array(ConnectedRegistrySchema),
|
|
1825
|
+
securityPreferences: import_zod4.z.record(
|
|
1826
|
+
import_zod4.z.string(),
|
|
1827
|
+
import_zod4.z.object({
|
|
1828
|
+
dontShowViolations: import_zod4.z.boolean()
|
|
1829
|
+
})
|
|
1830
|
+
).optional()
|
|
1831
|
+
});
|
|
1832
|
+
var ConnectRegistryRequestSchema = import_zod4.z.object({
|
|
1833
|
+
cloneUrl: import_zod4.z.string().url(),
|
|
1834
|
+
owner: import_zod4.z.string().min(1),
|
|
1835
|
+
repo: import_zod4.z.string().min(1)
|
|
1836
|
+
});
|
|
1837
|
+
var SyncRegistryRequestSchema = import_zod4.z.object({
|
|
1838
|
+
owner: import_zod4.z.string().min(1),
|
|
1839
|
+
repo: import_zod4.z.string().min(1)
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
// src/modules/package/package.service.ts
|
|
1843
|
+
var import_shared15 = __toESM(require_dist());
|
|
1844
|
+
var PackageService = class {
|
|
1845
|
+
constructor(git = gitService, storage = storageService, registry = registryService, subscription = subscriptionService) {
|
|
1846
|
+
this.git = git;
|
|
1847
|
+
this.storage = storage;
|
|
1848
|
+
this.registry = registry;
|
|
1849
|
+
this.subscription = subscription;
|
|
1850
|
+
}
|
|
1851
|
+
async listPackages(owner, repo) {
|
|
1852
|
+
const packagesPath = this.storage.getPackagesPath(owner, repo);
|
|
1853
|
+
try {
|
|
1854
|
+
const entries = await import_promises3.default.readdir(packagesPath, { withFileTypes: true });
|
|
1855
|
+
const packages = [];
|
|
1856
|
+
for (const entry of entries) {
|
|
1857
|
+
if (entry.isDirectory()) {
|
|
1858
|
+
const packagePath = this.storage.getPackagePath(owner, repo, entry.name);
|
|
1859
|
+
const packageInfo = await this.parsePackage(packagePath, entry.name);
|
|
1860
|
+
if (packageInfo) {
|
|
1861
|
+
packages.push(packageInfo);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return packages;
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
logger_default.error("Failed to read packages:", error);
|
|
1868
|
+
return [];
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
async getPackage(owner, repo, packageId) {
|
|
1872
|
+
const packagePath = this.storage.getPackagePath(owner, repo, packageId);
|
|
1873
|
+
const pkg = await this.parsePackage(packagePath, packageId);
|
|
1874
|
+
if (!pkg) {
|
|
1875
|
+
throw new shared_exports.NotFoundError(`Package '${packageId}' not found in ${owner}/${repo}`);
|
|
1876
|
+
}
|
|
1877
|
+
return pkg;
|
|
1878
|
+
}
|
|
1879
|
+
async createPackage(owner, repo, packageId, token) {
|
|
1880
|
+
const packagePath = this.storage.getPackagePath(owner, repo, packageId);
|
|
1881
|
+
try {
|
|
1882
|
+
for (const resourceType of import_shared15.VALID_RESOURCE_TYPES) {
|
|
1883
|
+
await import_promises3.default.mkdir(import_path4.default.join(packagePath, resourceType), { recursive: true });
|
|
1884
|
+
}
|
|
1885
|
+
const commitMessage = `feat: add package ${packageId}`;
|
|
1886
|
+
await this.git.commitChanges(owner, repo, commitMessage, token);
|
|
1887
|
+
logger_default.info(`Created package ${packageId} in ${owner}/${repo}`);
|
|
1888
|
+
return { id: packageId, path: packagePath };
|
|
1889
|
+
} catch (error) {
|
|
1890
|
+
logger_default.error(`Failed to create package ${packageId} in ${owner}/${repo}:`, error);
|
|
1891
|
+
throw error;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
async deletePackage(owner, repo, packageId, token) {
|
|
1895
|
+
const packagePath = this.storage.getPackagePath(owner, repo, packageId);
|
|
1896
|
+
try {
|
|
1897
|
+
await import_promises3.default.rm(packagePath, { recursive: true, force: true });
|
|
1898
|
+
const commitMessage = `feat: remove package ${packageId}`;
|
|
1899
|
+
await this.git.commitChanges(owner, repo, commitMessage, token);
|
|
1900
|
+
logger_default.info(`Deleted package ${packageId} from ${owner}/${repo}`);
|
|
1901
|
+
} catch (error) {
|
|
1902
|
+
logger_default.error(`Failed to delete package ${packageId} from ${owner}/${repo}:`, error);
|
|
1903
|
+
throw error;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
// ENHANCEMENT: Specific to the workflows, rules, skills and prompts, make it like an array of available resources
|
|
1907
|
+
async parsePackage(packagePath, packageId) {
|
|
1908
|
+
try {
|
|
1909
|
+
const entries = await import_promises3.default.readdir(packagePath, { withFileTypes: true });
|
|
1910
|
+
let name = packageId;
|
|
1911
|
+
let description;
|
|
1912
|
+
const packageJsonPath = import_path4.default.join(packagePath, "package.json");
|
|
1913
|
+
try {
|
|
1914
|
+
const packageJsonData = await import_promises3.default.readFile(packageJsonPath, "utf8");
|
|
1915
|
+
const packageJson = JSON.parse(packageJsonData);
|
|
1916
|
+
name = packageJson.name || packageId;
|
|
1917
|
+
description = packageJson.description;
|
|
1918
|
+
} catch {
|
|
1919
|
+
}
|
|
1920
|
+
return {
|
|
1921
|
+
id: packageId,
|
|
1922
|
+
name,
|
|
1923
|
+
description,
|
|
1924
|
+
path: packagePath
|
|
1925
|
+
};
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
logger_default.error(`Failed to parse package ${packageId}:`, error);
|
|
1928
|
+
return null;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
var packageService = new PackageService();
|
|
1933
|
+
|
|
1934
|
+
// src/modules/package/package.middleware.ts
|
|
1935
|
+
async function ensurePackageExists(req, res, next) {
|
|
1936
|
+
try {
|
|
1937
|
+
const owner = req.params.owner;
|
|
1938
|
+
const repo = req.params.repo;
|
|
1939
|
+
const packageId = req.params.packageId;
|
|
1940
|
+
const exists = await storageService.checkPackageExists(owner, repo, packageId);
|
|
1941
|
+
if (!exists) {
|
|
1942
|
+
throw new shared_exports.NotFoundError(`Package '${packageId}' not found in ${owner}/${repo}`);
|
|
1943
|
+
}
|
|
1944
|
+
next();
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
next(error);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
async function ensurePackageDoesNotExist(req, res, next) {
|
|
1950
|
+
try {
|
|
1951
|
+
const owner = req.params.owner;
|
|
1952
|
+
const repo = req.params.repo;
|
|
1953
|
+
const packageId = req.params.packageId;
|
|
1954
|
+
const exists = await storageService.checkPackageExists(owner, repo, packageId);
|
|
1955
|
+
if (exists) {
|
|
1956
|
+
throw new shared_exports.ConflictError(`Package '${packageId}' already exists in ${owner}/${repo}`);
|
|
1957
|
+
}
|
|
1958
|
+
next();
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
next(error);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// src/modules/package/package.schemas.ts
|
|
1965
|
+
var import_zod5 = require("zod");
|
|
1966
|
+
var PackageSchema = import_zod5.z.object({
|
|
1967
|
+
id: import_zod5.z.string().min(1),
|
|
1968
|
+
name: import_zod5.z.string().min(1),
|
|
1969
|
+
description: import_zod5.z.string().optional(),
|
|
1970
|
+
path: import_zod5.z.string().min(1),
|
|
1971
|
+
hasWorkflows: import_zod5.z.boolean(),
|
|
1972
|
+
hasRules: import_zod5.z.boolean(),
|
|
1973
|
+
hasSkills: import_zod5.z.boolean(),
|
|
1974
|
+
hasPrompts: import_zod5.z.boolean()
|
|
1975
|
+
});
|
|
1976
|
+
var PackageMetadataSchema = import_zod5.z.object({
|
|
1977
|
+
id: import_zod5.z.string().min(1),
|
|
1978
|
+
name: import_zod5.z.string().min(1),
|
|
1979
|
+
description: import_zod5.z.string().optional(),
|
|
1980
|
+
version: import_zod5.z.string().optional(),
|
|
1981
|
+
author: import_zod5.z.string().optional(),
|
|
1982
|
+
tags: import_zod5.z.array(import_zod5.z.string()).optional()
|
|
1983
|
+
});
|
|
1984
|
+
var GetPackagesRequestSchema = import_zod5.z.object({
|
|
1985
|
+
owner: import_zod5.z.string().min(1),
|
|
1986
|
+
repo: import_zod5.z.string().min(1)
|
|
1987
|
+
});
|
|
1988
|
+
var GetPackageResourcesRequestSchema = import_zod5.z.object({
|
|
1989
|
+
owner: import_zod5.z.string().min(1),
|
|
1990
|
+
repo: import_zod5.z.string().min(1),
|
|
1991
|
+
packageId: import_zod5.z.string().min(1),
|
|
1992
|
+
resourceType: import_zod5.z.enum(["workflows", "rules", "skills", "prompts"])
|
|
1993
|
+
});
|
|
1994
|
+
|
|
1995
|
+
// src/controllers/package.controller.ts
|
|
1996
|
+
var PackageController = class {
|
|
1997
|
+
constructor(_package = packageService) {
|
|
1998
|
+
this._package = _package;
|
|
1999
|
+
}
|
|
2000
|
+
async list(req, res, next) {
|
|
2001
|
+
try {
|
|
2002
|
+
const owner = req.params.owner;
|
|
2003
|
+
const repo = req.params.repo;
|
|
2004
|
+
if (!owner || !repo) {
|
|
2005
|
+
throw new shared_exports.ValidationError("owner and repo are required");
|
|
2006
|
+
}
|
|
2007
|
+
const packages = await this._package.listPackages(owner, repo);
|
|
2008
|
+
const response = {
|
|
2009
|
+
success: true,
|
|
2010
|
+
data: {
|
|
2011
|
+
packages: packages.map((pkg) => ({
|
|
2012
|
+
id: pkg.id,
|
|
2013
|
+
name: pkg.name,
|
|
2014
|
+
description: pkg.description,
|
|
2015
|
+
path: pkg.path
|
|
2016
|
+
}))
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
res.json(response);
|
|
2020
|
+
} catch (error) {
|
|
2021
|
+
next(error);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
async get(req, res, next) {
|
|
2025
|
+
try {
|
|
2026
|
+
const owner = req.params.owner;
|
|
2027
|
+
const repo = req.params.repo;
|
|
2028
|
+
const packageId = req.params.packageId;
|
|
2029
|
+
if (!owner || !repo) {
|
|
2030
|
+
throw new shared_exports.ValidationError("owner and repo are required");
|
|
2031
|
+
}
|
|
2032
|
+
if (!packageId) {
|
|
2033
|
+
throw new shared_exports.ValidationError("packageId is required");
|
|
2034
|
+
}
|
|
2035
|
+
const pkg = await this._package.getPackage(owner, repo, packageId);
|
|
2036
|
+
const response = {
|
|
2037
|
+
success: true,
|
|
2038
|
+
data: {
|
|
2039
|
+
id: pkg.id,
|
|
2040
|
+
name: pkg.name,
|
|
2041
|
+
description: pkg.description,
|
|
2042
|
+
path: pkg.path
|
|
2043
|
+
}
|
|
2044
|
+
};
|
|
2045
|
+
res.json(response);
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
next(error);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
async pushCreate(req, res, next) {
|
|
2051
|
+
try {
|
|
2052
|
+
const token = req.githubToken;
|
|
2053
|
+
const owner = req.params.owner;
|
|
2054
|
+
const repo = req.params.repo;
|
|
2055
|
+
const packageId = req.params.packageId;
|
|
2056
|
+
if (!owner || !repo) {
|
|
2057
|
+
throw new shared_exports.ValidationError("owner and repo are required");
|
|
2058
|
+
}
|
|
2059
|
+
if (!packageId) {
|
|
2060
|
+
throw new shared_exports.ValidationError("packageId is required");
|
|
2061
|
+
}
|
|
2062
|
+
const result = await this._package.createPackage(owner, repo, packageId, token);
|
|
2063
|
+
const response = {
|
|
2064
|
+
success: true,
|
|
2065
|
+
data: {
|
|
2066
|
+
packageId: result.id,
|
|
2067
|
+
path: result.path
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
2070
|
+
res.json(response);
|
|
2071
|
+
} catch (error) {
|
|
2072
|
+
next(error);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
async pushDelete(req, res, next) {
|
|
2076
|
+
try {
|
|
2077
|
+
const token = req.githubToken;
|
|
2078
|
+
const owner = req.params.owner;
|
|
2079
|
+
const repo = req.params.repo;
|
|
2080
|
+
const packageId = req.params.packageId;
|
|
2081
|
+
if (!owner || !repo) {
|
|
2082
|
+
throw new shared_exports.ValidationError("owner and repo are required");
|
|
2083
|
+
}
|
|
2084
|
+
if (!packageId) {
|
|
2085
|
+
throw new shared_exports.ValidationError("packageId is required");
|
|
2086
|
+
}
|
|
2087
|
+
await this._package.deletePackage(owner, repo, packageId, token);
|
|
2088
|
+
const response = {
|
|
2089
|
+
success: true,
|
|
2090
|
+
data: {}
|
|
2091
|
+
};
|
|
2092
|
+
res.json(response);
|
|
2093
|
+
} catch (error) {
|
|
2094
|
+
next(error);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
|
|
2099
|
+
// src/routes/resource.routes.ts
|
|
2100
|
+
var import_express5 = require("express");
|
|
2101
|
+
|
|
2102
|
+
// src/modules/resource/resource.service.ts
|
|
2103
|
+
var import_path8 = __toESM(require("path"));
|
|
2104
|
+
var import_promises6 = __toESM(require("fs/promises"));
|
|
2105
|
+
|
|
2106
|
+
// src/modules/draft/draft.service.ts
|
|
2107
|
+
var import_path6 = __toESM(require("path"));
|
|
2108
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
2109
|
+
var import_child_process = require("child_process");
|
|
2110
|
+
var import_util = require("util");
|
|
2111
|
+
|
|
2112
|
+
// src/config/resource.config.ts
|
|
2113
|
+
var resource_config_exports = {};
|
|
2114
|
+
__export(resource_config_exports, {
|
|
2115
|
+
ResourcePathHelper: () => ResourcePathHelper
|
|
2116
|
+
});
|
|
2117
|
+
var import_path5 = __toESM(require("path"));
|
|
2118
|
+
__reExport(resource_config_exports, __toESM(require_dist()));
|
|
2119
|
+
var import_shared18 = __toESM(require_dist());
|
|
2120
|
+
var ResourcePathHelper = class {
|
|
2121
|
+
/**
|
|
2122
|
+
* Check if a resource type is stored as a folder
|
|
2123
|
+
*/
|
|
2124
|
+
static isFolder(resourceType) {
|
|
2125
|
+
return (0, import_shared18.getResourceStorageType)(resourceType) === import_shared18.ResourceStorageType.FOLDER;
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Get the file name for a resource (e.g., "my-workflow.md" for workflows, "my-skill" for skills)
|
|
2129
|
+
*/
|
|
2130
|
+
static getFileName(resourceName, resourceType) {
|
|
2131
|
+
if (this.isFolder(resourceType)) {
|
|
2132
|
+
return resourceName;
|
|
2133
|
+
}
|
|
2134
|
+
return `${resourceName}.md`;
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Get the entry file path for a resource (SKILL.md for skills, .md file for others)
|
|
2138
|
+
*/
|
|
2139
|
+
static getEntryPath(basePath, resourceName, resourceType) {
|
|
2140
|
+
const entryFile = (0, import_shared18.getResourceEntryFile)(resourceType);
|
|
2141
|
+
if (this.isFolder(resourceType) && entryFile) {
|
|
2142
|
+
return import_path5.default.join(basePath, resourceName, entryFile);
|
|
2143
|
+
}
|
|
2144
|
+
return import_path5.default.join(basePath, `${resourceName}.md`);
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Get the base path for a resource (folder path for skills, file path for others)
|
|
2148
|
+
*/
|
|
2149
|
+
static getBasePath(basePath, resourceName, resourceType) {
|
|
2150
|
+
if (this.isFolder(resourceType)) {
|
|
2151
|
+
return import_path5.default.join(basePath, resourceName);
|
|
2152
|
+
}
|
|
2153
|
+
return import_path5.default.join(basePath, `${resourceName}.${(0, import_shared18.getResourceFileExt)(resourceType)}`);
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
|
|
2157
|
+
// src/modules/draft/draft.service.ts
|
|
2158
|
+
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
2159
|
+
var DraftService = class {
|
|
2160
|
+
constructor(storage = storageService) {
|
|
2161
|
+
this.storage = storage;
|
|
2162
|
+
}
|
|
2163
|
+
async listDraftFiles(owner, repo, packageId, resourceType) {
|
|
2164
|
+
try {
|
|
2165
|
+
const dir = this.storage.getDraftResourceDir(owner, repo, packageId, resourceType);
|
|
2166
|
+
const entries = await import_promises4.default.readdir(dir, { withFileTypes: true });
|
|
2167
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2168
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
2169
|
+
name: entry.name,
|
|
2170
|
+
path: import_path6.default.join(dir, entry.name)
|
|
2171
|
+
}));
|
|
2172
|
+
} else {
|
|
2173
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({
|
|
2174
|
+
name: entry.name,
|
|
2175
|
+
path: import_path6.default.join(dir, entry.name)
|
|
2176
|
+
}));
|
|
2177
|
+
}
|
|
2178
|
+
} catch {
|
|
2179
|
+
return [];
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
async createDraftFile(owner, repo, packageId, resourceType, fileName) {
|
|
2183
|
+
const normalizedName = fileName.endsWith(".md") ? fileName : `${fileName}.md`;
|
|
2184
|
+
await this.storage.ensureDraftDirExists(owner, repo, packageId, resourceType);
|
|
2185
|
+
const filePath = import_path6.default.join(
|
|
2186
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resourceType),
|
|
2187
|
+
normalizedName
|
|
2188
|
+
);
|
|
2189
|
+
try {
|
|
2190
|
+
await import_promises4.default.stat(filePath);
|
|
2191
|
+
} catch {
|
|
2192
|
+
await import_promises4.default.writeFile(filePath, "", "utf8");
|
|
2193
|
+
}
|
|
2194
|
+
return filePath;
|
|
2195
|
+
}
|
|
2196
|
+
async copyResourceFileToDraft(owner, repo, packageId, resourceType, fileName, registryFilePath) {
|
|
2197
|
+
const normalizedName = fileName.endsWith(".md") ? fileName : `${fileName}.md`;
|
|
2198
|
+
await this.storage.ensureDraftDirExists(owner, repo, packageId, resourceType);
|
|
2199
|
+
const draftPath = import_path6.default.join(
|
|
2200
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resourceType),
|
|
2201
|
+
normalizedName
|
|
2202
|
+
);
|
|
2203
|
+
const content = await import_promises4.default.readFile(registryFilePath);
|
|
2204
|
+
await import_promises4.default.writeFile(draftPath, content);
|
|
2205
|
+
return draftPath;
|
|
2206
|
+
}
|
|
2207
|
+
async deleteDraftFile(filePath) {
|
|
2208
|
+
try {
|
|
2209
|
+
await import_promises4.default.unlink(filePath);
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
if (error.code !== "ENOENT") {
|
|
2212
|
+
throw new shared_exports.FileSystemError(`Failed to delete draft file: ${error.message}`);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
async copyResourceFolderToDraft(owner, repo, packageId, resourceName, resourcePath) {
|
|
2217
|
+
await this.storage.ensureDraftDirExists(owner, repo, packageId, resource_config_exports.ResourceType.SKILLS);
|
|
2218
|
+
const draftResourcePath = import_path6.default.join(
|
|
2219
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resource_config_exports.ResourceType.SKILLS),
|
|
2220
|
+
resourceName
|
|
2221
|
+
);
|
|
2222
|
+
await this.copyFolderRecursive(resourcePath, draftResourcePath);
|
|
2223
|
+
return draftResourcePath;
|
|
2224
|
+
}
|
|
2225
|
+
async createResourceFolder(owner, repo, packageId, resourceType, resourceName) {
|
|
2226
|
+
await this.storage.ensureDraftDirExists(owner, repo, packageId, resourceType);
|
|
2227
|
+
const resourceFolderPath = import_path6.default.join(
|
|
2228
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resourceType),
|
|
2229
|
+
resourceName
|
|
2230
|
+
);
|
|
2231
|
+
await import_promises4.default.mkdir(resourceFolderPath, { recursive: true });
|
|
2232
|
+
const entryFile = (0, shared_exports.getResourceEntryFile)(resourceType);
|
|
2233
|
+
if (!entryFile) {
|
|
2234
|
+
throw new shared_exports.ValidationError(`Resource type ${resourceType} does not have an entry file.`);
|
|
2235
|
+
}
|
|
2236
|
+
const resourceEntryPath = import_path6.default.join(resourceFolderPath, entryFile);
|
|
2237
|
+
const template = (0, shared_exports.getResourceTemplate)(resourceType)?.(resourceName) || "";
|
|
2238
|
+
await import_promises4.default.writeFile(resourceEntryPath, template, "utf8");
|
|
2239
|
+
return resourceFolderPath;
|
|
2240
|
+
}
|
|
2241
|
+
async deleteDraftFolder(owner, repo, packageId, skillName) {
|
|
2242
|
+
const skillFolderPath = import_path6.default.join(
|
|
2243
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resource_config_exports.ResourceType.SKILLS),
|
|
2244
|
+
skillName
|
|
2245
|
+
);
|
|
2246
|
+
try {
|
|
2247
|
+
await import_promises4.default.rm(skillFolderPath, { recursive: true, force: true });
|
|
2248
|
+
} catch (error) {
|
|
2249
|
+
if (error.code !== "ENOENT") {
|
|
2250
|
+
throw new shared_exports.FileSystemError(`Failed to delete draft folder: ${error.message}`);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
async getModifiedSkills(owner, repo, packageId, registrySkillsPath) {
|
|
2255
|
+
const modifiedSkills = /* @__PURE__ */ new Set();
|
|
2256
|
+
try {
|
|
2257
|
+
const draftSkillsDir = this.storage.getDraftResourceDir(owner, repo, packageId, resource_config_exports.ResourceType.SKILLS);
|
|
2258
|
+
try {
|
|
2259
|
+
await import_promises4.default.stat(draftSkillsDir);
|
|
2260
|
+
} catch {
|
|
2261
|
+
return modifiedSkills;
|
|
2262
|
+
}
|
|
2263
|
+
const draftEntries = await import_promises4.default.readdir(draftSkillsDir, { withFileTypes: true });
|
|
2264
|
+
const draftSkillFolders = draftEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2265
|
+
for (const skillName of draftSkillFolders) {
|
|
2266
|
+
const draftPath = import_path6.default.join(draftSkillsDir, skillName);
|
|
2267
|
+
const registryPath = import_path6.default.join(registrySkillsPath, skillName);
|
|
2268
|
+
try {
|
|
2269
|
+
await import_promises4.default.stat(registryPath);
|
|
2270
|
+
} catch {
|
|
2271
|
+
modifiedSkills.add(skillName);
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
const hasChanges = await this.hasFolderChanges(draftPath, registryPath);
|
|
2275
|
+
if (hasChanges) {
|
|
2276
|
+
modifiedSkills.add(skillName);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
} catch (error) {
|
|
2280
|
+
logger_default.error("Error detecting modified skills:", error);
|
|
2281
|
+
}
|
|
2282
|
+
return modifiedSkills;
|
|
2283
|
+
}
|
|
2284
|
+
async copyFolderRecursive(source, destination) {
|
|
2285
|
+
await import_promises4.default.mkdir(destination, { recursive: true });
|
|
2286
|
+
const entries = await import_promises4.default.readdir(source, { withFileTypes: true });
|
|
2287
|
+
for (const entry of entries) {
|
|
2288
|
+
const sourcePath = import_path6.default.join(source, entry.name);
|
|
2289
|
+
const destPath = import_path6.default.join(destination, entry.name);
|
|
2290
|
+
if (entry.isFile()) {
|
|
2291
|
+
const content = await import_promises4.default.readFile(sourcePath);
|
|
2292
|
+
await import_promises4.default.writeFile(destPath, content);
|
|
2293
|
+
} else if (entry.isDirectory()) {
|
|
2294
|
+
await this.copyFolderRecursive(sourcePath, destPath);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
async getDraftContent(owner, repo, packageId, resourceType, fileName) {
|
|
2299
|
+
const filePath = import_path6.default.join(
|
|
2300
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resourceType),
|
|
2301
|
+
fileName
|
|
2302
|
+
);
|
|
2303
|
+
try {
|
|
2304
|
+
return await import_promises4.default.readFile(filePath, "utf8");
|
|
2305
|
+
} catch (error) {
|
|
2306
|
+
throw new shared_exports.NotFoundError(`Draft file not found: ${fileName}`);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
async updateDraftContent(owner, repo, packageId, resourceType, fileName, content) {
|
|
2310
|
+
const filePath = import_path6.default.join(
|
|
2311
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resourceType),
|
|
2312
|
+
fileName
|
|
2313
|
+
);
|
|
2314
|
+
await import_promises4.default.writeFile(filePath, content, "utf8");
|
|
2315
|
+
}
|
|
2316
|
+
async copyFileOrFolderFromResource(owner, repo, packageId, resourceType, resourceName) {
|
|
2317
|
+
let sourcePath = this.storage.getResourcePath(owner, repo, packageId, resourceType, resourceName);
|
|
2318
|
+
if (!ResourcePathHelper.isFolder(resourceType)) {
|
|
2319
|
+
sourcePath = `${sourcePath}.${(0, shared_exports.getResourceFileExt)(resourceType)}`;
|
|
2320
|
+
}
|
|
2321
|
+
return await this.copyFileOrFolderFromSystem(sourcePath, owner, repo, packageId, resourceType, resourceName);
|
|
2322
|
+
}
|
|
2323
|
+
async copyFileOrFolderFromSystem(sourcePath, owner, repo, packageId, resourceType, targetName) {
|
|
2324
|
+
try {
|
|
2325
|
+
await import_promises4.default.stat(sourcePath);
|
|
2326
|
+
} catch {
|
|
2327
|
+
throw new shared_exports.NotFoundError(`Source path does not exist: ${sourcePath}`);
|
|
2328
|
+
}
|
|
2329
|
+
const stats = await import_promises4.default.stat(sourcePath);
|
|
2330
|
+
await this.storage.ensureDraftDirExists(owner, repo, packageId, resourceType);
|
|
2331
|
+
if (stats.isDirectory()) {
|
|
2332
|
+
if (!ResourcePathHelper.isFolder(resourceType)) {
|
|
2333
|
+
throw new shared_exports.ValidationError(`Resource type ${resourceType} does not support folders`);
|
|
2334
|
+
}
|
|
2335
|
+
const draftFolderPath = import_path6.default.join(
|
|
2336
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resourceType),
|
|
2337
|
+
targetName
|
|
2338
|
+
);
|
|
2339
|
+
await this.copyFolderRecursive(sourcePath, draftFolderPath);
|
|
2340
|
+
return draftFolderPath;
|
|
2341
|
+
} else {
|
|
2342
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2343
|
+
throw new shared_exports.ValidationError(`Resource type ${resourceType} requires folders, not files`);
|
|
2344
|
+
}
|
|
2345
|
+
const normalizedName = targetName.endsWith(".md") ? targetName : `${targetName}.md`;
|
|
2346
|
+
const draftFilePath = import_path6.default.join(
|
|
2347
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resourceType),
|
|
2348
|
+
normalizedName
|
|
2349
|
+
);
|
|
2350
|
+
const content = await import_promises4.default.readFile(sourcePath);
|
|
2351
|
+
await import_promises4.default.writeFile(draftFilePath, content);
|
|
2352
|
+
return draftFilePath;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
async hasFolderChanges(draftPath, registryPath) {
|
|
2356
|
+
try {
|
|
2357
|
+
try {
|
|
2358
|
+
await execAsync(`git diff --no-index --quiet "${registryPath}" "${draftPath}"`);
|
|
2359
|
+
return false;
|
|
2360
|
+
} catch (error) {
|
|
2361
|
+
if (error.code === 1) {
|
|
2362
|
+
return true;
|
|
2363
|
+
}
|
|
2364
|
+
logger_default.error("Git diff error:", error);
|
|
2365
|
+
return false;
|
|
2366
|
+
}
|
|
2367
|
+
} catch (error) {
|
|
2368
|
+
logger_default.error("Error running git diff:", error);
|
|
2369
|
+
return false;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2373
|
+
var draftService = new DraftService();
|
|
2374
|
+
|
|
2375
|
+
// src/modules/draft/draft.schemas.ts
|
|
2376
|
+
var import_zod6 = require("zod");
|
|
2377
|
+
var ResourceTypeSchema = import_zod6.z.enum(["workflows", "rules", "skills", "prompts"]);
|
|
2378
|
+
var CreateDraftRequestSchema = import_zod6.z.object({
|
|
2379
|
+
owner: import_zod6.z.string().min(1),
|
|
2380
|
+
repo: import_zod6.z.string().min(1),
|
|
2381
|
+
packageId: import_zod6.z.string().min(1),
|
|
2382
|
+
resourceType: ResourceTypeSchema,
|
|
2383
|
+
fileName: import_zod6.z.string().min(1),
|
|
2384
|
+
content: import_zod6.z.string().optional()
|
|
2385
|
+
});
|
|
2386
|
+
var UpdateDraftRequestSchema = import_zod6.z.object({
|
|
2387
|
+
owner: import_zod6.z.string().min(1),
|
|
2388
|
+
repo: import_zod6.z.string().min(1),
|
|
2389
|
+
packageId: import_zod6.z.string().min(1),
|
|
2390
|
+
resourceType: ResourceTypeSchema,
|
|
2391
|
+
fileName: import_zod6.z.string().min(1),
|
|
2392
|
+
content: import_zod6.z.string()
|
|
2393
|
+
});
|
|
2394
|
+
var ListDraftsRequestSchema = import_zod6.z.object({
|
|
2395
|
+
owner: import_zod6.z.string().min(1),
|
|
2396
|
+
repo: import_zod6.z.string().min(1),
|
|
2397
|
+
packageId: import_zod6.z.string().min(1),
|
|
2398
|
+
resourceType: ResourceTypeSchema
|
|
2399
|
+
});
|
|
2400
|
+
var CopyFromRegistryRequestSchema = import_zod6.z.object({
|
|
2401
|
+
owner: import_zod6.z.string().min(1),
|
|
2402
|
+
repo: import_zod6.z.string().min(1),
|
|
2403
|
+
packageId: import_zod6.z.string().min(1),
|
|
2404
|
+
resourceType: ResourceTypeSchema,
|
|
2405
|
+
fileName: import_zod6.z.string().min(1),
|
|
2406
|
+
registryFilePath: import_zod6.z.string().min(1)
|
|
2407
|
+
});
|
|
2408
|
+
|
|
2409
|
+
// src/modules/filesystem/filesystem.service.ts
|
|
2410
|
+
var import_path7 = __toESM(require("path"));
|
|
2411
|
+
var import_promises5 = __toESM(require("fs/promises"));
|
|
2412
|
+
var FilesystemService = class {
|
|
2413
|
+
/**
|
|
2414
|
+
* Sanitizes a path to prevent directory traversal attacks
|
|
2415
|
+
*/
|
|
2416
|
+
sanitizePath(requestPath) {
|
|
2417
|
+
const normalized = import_path7.default.normalize(requestPath);
|
|
2418
|
+
if (normalized.includes("..")) {
|
|
2419
|
+
throw new shared_exports.ValidationError("Invalid path: path traversal detected");
|
|
2420
|
+
}
|
|
2421
|
+
return normalized;
|
|
2422
|
+
}
|
|
2423
|
+
/**
|
|
2424
|
+
* Read a file or list directory contents
|
|
2425
|
+
*/
|
|
2426
|
+
async read(basePath, relativePath) {
|
|
2427
|
+
const sanitizedPath = this.sanitizePath(relativePath);
|
|
2428
|
+
const fullPath = import_path7.default.join(basePath, sanitizedPath);
|
|
2429
|
+
const stat = await import_promises5.default.stat(fullPath);
|
|
2430
|
+
if (stat.isDirectory()) {
|
|
2431
|
+
const files = await import_promises5.default.readdir(fullPath, { withFileTypes: true });
|
|
2432
|
+
const contents = [];
|
|
2433
|
+
for (const f of files) {
|
|
2434
|
+
const entry = {
|
|
2435
|
+
name: f.name,
|
|
2436
|
+
type: f.isDirectory() ? "directory" : "file",
|
|
2437
|
+
path: import_path7.default.join(sanitizedPath, f.name).replace(/\\/g, "/")
|
|
2438
|
+
};
|
|
2439
|
+
if (f.isFile()) {
|
|
2440
|
+
const fileStat = await import_promises5.default.stat(import_path7.default.join(fullPath, f.name));
|
|
2441
|
+
entry.size = fileStat.size;
|
|
2442
|
+
}
|
|
2443
|
+
contents.push(entry);
|
|
2444
|
+
}
|
|
2445
|
+
return {
|
|
2446
|
+
path: sanitizedPath,
|
|
2447
|
+
type: "directory",
|
|
2448
|
+
contents
|
|
2449
|
+
};
|
|
2450
|
+
} else {
|
|
2451
|
+
const content = await import_promises5.default.readFile(fullPath, "utf-8");
|
|
2452
|
+
return {
|
|
2453
|
+
path: sanitizedPath,
|
|
2454
|
+
type: "file",
|
|
2455
|
+
content
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Write a file (creates parent directories if needed)
|
|
2461
|
+
*/
|
|
2462
|
+
async write(basePath, relativePath, content, createParentDirs = true) {
|
|
2463
|
+
const sanitizedPath = this.sanitizePath(relativePath);
|
|
2464
|
+
const fullPath = import_path7.default.join(basePath, sanitizedPath);
|
|
2465
|
+
if (createParentDirs) {
|
|
2466
|
+
await import_promises5.default.mkdir(import_path7.default.dirname(fullPath), { recursive: true });
|
|
2467
|
+
}
|
|
2468
|
+
await import_promises5.default.writeFile(fullPath, content);
|
|
2469
|
+
return { path: sanitizedPath };
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Update an existing file
|
|
2473
|
+
*/
|
|
2474
|
+
async update(basePath, relativePath, content) {
|
|
2475
|
+
const sanitizedPath = this.sanitizePath(relativePath);
|
|
2476
|
+
const fullPath = import_path7.default.join(basePath, sanitizedPath);
|
|
2477
|
+
const stat = await import_promises5.default.stat(fullPath);
|
|
2478
|
+
if (!stat.isFile()) {
|
|
2479
|
+
throw new shared_exports.ValidationError("Path is not a file");
|
|
2480
|
+
}
|
|
2481
|
+
await import_promises5.default.writeFile(fullPath, content);
|
|
2482
|
+
return { path: sanitizedPath };
|
|
2483
|
+
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Delete a file or directory
|
|
2486
|
+
*/
|
|
2487
|
+
async delete(basePath, relativePath) {
|
|
2488
|
+
const sanitizedPath = this.sanitizePath(relativePath);
|
|
2489
|
+
const fullPath = import_path7.default.join(basePath, sanitizedPath);
|
|
2490
|
+
const stat = await import_promises5.default.stat(fullPath);
|
|
2491
|
+
if (stat.isDirectory()) {
|
|
2492
|
+
await import_promises5.default.rm(fullPath, { recursive: true });
|
|
2493
|
+
} else {
|
|
2494
|
+
await import_promises5.default.unlink(fullPath);
|
|
2495
|
+
}
|
|
2496
|
+
return { path: sanitizedPath };
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Check if path exists
|
|
2500
|
+
*/
|
|
2501
|
+
async exists(basePath, relativePath) {
|
|
2502
|
+
try {
|
|
2503
|
+
const sanitizedPath = this.sanitizePath(relativePath);
|
|
2504
|
+
const fullPath = import_path7.default.join(basePath, sanitizedPath);
|
|
2505
|
+
await import_promises5.default.access(fullPath);
|
|
2506
|
+
return true;
|
|
2507
|
+
} catch {
|
|
2508
|
+
return false;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
var filesystemService = new FilesystemService();
|
|
2513
|
+
|
|
2514
|
+
// src/modules/resource/resource.service.ts
|
|
2515
|
+
var import_shared22 = __toESM(require_dist());
|
|
2516
|
+
var ResourceService = class {
|
|
2517
|
+
constructor(storage = storageService, git = gitService, draft = draftService, registry = registryService, subscription = subscriptionService, filesystem = filesystemService) {
|
|
2518
|
+
this.storage = storage;
|
|
2519
|
+
this.git = git;
|
|
2520
|
+
this.draft = draft;
|
|
2521
|
+
this.registry = registry;
|
|
2522
|
+
this.subscription = subscription;
|
|
2523
|
+
this.filesystem = filesystem;
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* List resources combining registry and drafts with state
|
|
2527
|
+
*/
|
|
2528
|
+
async listResources(owner, repo, packageId, resourceType) {
|
|
2529
|
+
const registryResources = await this.getRegistryResources(
|
|
2530
|
+
owner,
|
|
2531
|
+
repo,
|
|
2532
|
+
packageId,
|
|
2533
|
+
resourceType
|
|
2534
|
+
);
|
|
2535
|
+
const draftResources = await this.draft.listDraftFiles(
|
|
2536
|
+
owner,
|
|
2537
|
+
repo,
|
|
2538
|
+
packageId,
|
|
2539
|
+
resourceType
|
|
2540
|
+
);
|
|
2541
|
+
const draftNames = new Set(draftResources.map((d) => d.name.replace(".md", "")));
|
|
2542
|
+
const registryNames = new Set(registryResources.map((r) => r.name));
|
|
2543
|
+
const resources = [];
|
|
2544
|
+
for (const r of registryResources) {
|
|
2545
|
+
resources.push({
|
|
2546
|
+
name: r.name,
|
|
2547
|
+
type: resourceType,
|
|
2548
|
+
hasDraft: draftNames.has(r.name),
|
|
2549
|
+
inRegistry: true,
|
|
2550
|
+
registryPath: r.path,
|
|
2551
|
+
draftPath: draftResources.find((d) => d.name.replace(".md", "") === r.name)?.path
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
for (const d of draftResources) {
|
|
2555
|
+
const name = d.name.replace(".md", "");
|
|
2556
|
+
if (!registryNames.has(name)) {
|
|
2557
|
+
resources.push({
|
|
2558
|
+
name,
|
|
2559
|
+
type: resourceType,
|
|
2560
|
+
hasDraft: true,
|
|
2561
|
+
inRegistry: false,
|
|
2562
|
+
draftPath: d.path
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
return resources;
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Get resource entry content (draft-first)
|
|
2570
|
+
*/
|
|
2571
|
+
async getResourceEntry(owner, repo, packageId, resourceType, resourceName) {
|
|
2572
|
+
try {
|
|
2573
|
+
const draftBasePath = this.storage.getDraftResourceDir(owner, repo, packageId, resourceType);
|
|
2574
|
+
const draftPath = ResourcePathHelper.getEntryPath(draftBasePath, resourceName, resourceType);
|
|
2575
|
+
const content = await import_promises6.default.readFile(draftPath, "utf8");
|
|
2576
|
+
return { content, source: resource_config_exports.ResourceSource.DRAFT, path: draftPath };
|
|
2577
|
+
} catch {
|
|
2578
|
+
}
|
|
2579
|
+
try {
|
|
2580
|
+
const packagePath = this.storage.getPackagePath(owner, repo, packageId);
|
|
2581
|
+
const registryResourceDir = import_path8.default.join(packagePath, resourceType);
|
|
2582
|
+
const resourcePath = ResourcePathHelper.getEntryPath(registryResourceDir, resourceName, resourceType);
|
|
2583
|
+
const content = await import_promises6.default.readFile(resourcePath, "utf8");
|
|
2584
|
+
return { content, source: resource_config_exports.ResourceSource.REGISTRY, path: resourcePath };
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
throw new shared_exports.NotFoundError(`Resource '${resourceName}' of type '${resourceType}' not found in package '${packageId}'`);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Get filesystem access (draft-first)
|
|
2591
|
+
*/
|
|
2592
|
+
async getResourceFilesystem(owner, repo, packageId, resourceType, resourceName, fsPath = "") {
|
|
2593
|
+
const draftBasePath = import_path8.default.join(
|
|
2594
|
+
this.storage.getDraftResourceDir(owner, repo, packageId, resourceType),
|
|
2595
|
+
ResourcePathHelper.isFolder(resourceType) ? resourceName : ""
|
|
2596
|
+
);
|
|
2597
|
+
const registryBasePath = import_path8.default.join(
|
|
2598
|
+
this.storage.getResourceTypeDir(owner, repo, packageId, resourceType),
|
|
2599
|
+
ResourcePathHelper.getFileName(resourceName, resourceType)
|
|
2600
|
+
);
|
|
2601
|
+
let basePath = draftBasePath;
|
|
2602
|
+
try {
|
|
2603
|
+
await import_promises6.default.stat(draftBasePath);
|
|
2604
|
+
} catch {
|
|
2605
|
+
basePath = registryBasePath;
|
|
2606
|
+
}
|
|
2607
|
+
try {
|
|
2608
|
+
const result = await this.filesystem.read(basePath, fsPath);
|
|
2609
|
+
return result;
|
|
2610
|
+
} catch (error) {
|
|
2611
|
+
throw new shared_exports.NotFoundError(`Filesystem path '${fsPath}' not found in resource '${resourceName}'`);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
/**
|
|
2615
|
+
* Edit entry file (PUT /:name)
|
|
2616
|
+
* If no draft exists: copy from registry first, then update
|
|
2617
|
+
* If draft exists: update directly
|
|
2618
|
+
*/
|
|
2619
|
+
async editEntryDraft(owner, repo, packageId, resourceType, resourceName, content) {
|
|
2620
|
+
const draftDir = this.storage.getDraftResourceDir(owner, repo, packageId, resourceType);
|
|
2621
|
+
await this.storage.ensureDraftDirExists(owner, repo, packageId, resourceType);
|
|
2622
|
+
let draftExists = false;
|
|
2623
|
+
try {
|
|
2624
|
+
const draftPath = ResourcePathHelper.getBasePath(draftDir, resourceName, resourceType);
|
|
2625
|
+
await import_promises6.default.stat(draftPath);
|
|
2626
|
+
draftExists = true;
|
|
2627
|
+
} catch {
|
|
2628
|
+
draftExists = false;
|
|
2629
|
+
}
|
|
2630
|
+
if (!draftExists) {
|
|
2631
|
+
const registryResourceDir = this.storage.getResourceTypeDir(owner, repo, packageId, resourceType);
|
|
2632
|
+
try {
|
|
2633
|
+
const registryResourcePath = ResourcePathHelper.getBasePath(registryResourceDir, resourceName, resourceType);
|
|
2634
|
+
await import_promises6.default.stat(registryResourcePath);
|
|
2635
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2636
|
+
await this.draft.copyResourceFolderToDraft(owner, repo, packageId, resourceName, registryResourcePath);
|
|
2637
|
+
} else {
|
|
2638
|
+
await this.draft.copyResourceFileToDraft(owner, repo, packageId, resourceType, `${resourceName}.md`, registryResourcePath);
|
|
2639
|
+
}
|
|
2640
|
+
} catch (error) {
|
|
2641
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2642
|
+
await this.draft.createResourceFolder(owner, repo, packageId, resourceType, resourceName);
|
|
2643
|
+
} else {
|
|
2644
|
+
await this.draft.createDraftFile(owner, repo, packageId, resourceType, resourceName);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
const entryPath = ResourcePathHelper.getEntryPath(draftDir, resourceName, resourceType);
|
|
2649
|
+
await import_promises6.default.writeFile(entryPath, content, "utf8");
|
|
2650
|
+
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Edit specific file in folder (PUT /:name/fs/*)
|
|
2653
|
+
*/
|
|
2654
|
+
async editFileDraft(owner, repo, packageId, resourceType, resourceName, fsPath, content) {
|
|
2655
|
+
const draftDir = this.storage.getDraftResourceDir(owner, repo, packageId, resourceType);
|
|
2656
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2657
|
+
const draftPath = import_path8.default.join(draftDir, resourceName);
|
|
2658
|
+
try {
|
|
2659
|
+
await import_promises6.default.stat(draftPath);
|
|
2660
|
+
} catch {
|
|
2661
|
+
await this.draft.createResourceFolder(owner, repo, packageId, resourceType, resourceName);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
const basePath = import_path8.default.join(draftDir, ResourcePathHelper.isFolder(resourceType) ? resourceName : "");
|
|
2665
|
+
await this.filesystem.write(basePath, fsPath, content);
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Delete resource (draft if exists, else registry + commit)
|
|
2669
|
+
*/
|
|
2670
|
+
async deleteResource(owner, repo, packageId, resourceType, resourceName, token) {
|
|
2671
|
+
const draftDir = this.storage.getDraftResourceDir(owner, repo, packageId, resourceType);
|
|
2672
|
+
let draftExists = false;
|
|
2673
|
+
let draftPath = "";
|
|
2674
|
+
try {
|
|
2675
|
+
draftPath = ResourcePathHelper.getBasePath(draftDir, resourceName, resourceType);
|
|
2676
|
+
await import_promises6.default.stat(draftPath);
|
|
2677
|
+
draftExists = true;
|
|
2678
|
+
} catch {
|
|
2679
|
+
draftExists = false;
|
|
2680
|
+
}
|
|
2681
|
+
if (draftExists) {
|
|
2682
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2683
|
+
await this.draft.deleteDraftFolder(owner, repo, packageId, resourceName);
|
|
2684
|
+
} else {
|
|
2685
|
+
await this.draft.deleteDraftFile(draftPath);
|
|
2686
|
+
}
|
|
2687
|
+
} else {
|
|
2688
|
+
if (!token) {
|
|
2689
|
+
throw new shared_exports.ValidationError("GitHub token required for deleting from registry");
|
|
2690
|
+
}
|
|
2691
|
+
await this.subscription.connectToRegistry(token, owner, repo);
|
|
2692
|
+
await this.registry.sync(owner, repo, token);
|
|
2693
|
+
const resourcePath = import_path8.default.join(
|
|
2694
|
+
this.storage.getResourceTypeDir(owner, repo, packageId, resourceType),
|
|
2695
|
+
ResourcePathHelper.getFileName(resourceName, resourceType)
|
|
2696
|
+
);
|
|
2697
|
+
try {
|
|
2698
|
+
await import_promises6.default.stat(resourcePath);
|
|
2699
|
+
} catch {
|
|
2700
|
+
throw new shared_exports.NotFoundError(`Resource '${resourceName}' not found in registry`);
|
|
2701
|
+
}
|
|
2702
|
+
await import_promises6.default.rm(resourcePath, { recursive: ResourcePathHelper.isFolder(resourceType), force: true });
|
|
2703
|
+
const message = `feat: delete ${(0, import_shared22.getResourceSingular)(resourceType)} ${resourceName} from ${packageId}`;
|
|
2704
|
+
await this.git.commitChanges(owner, repo, message, token);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
/**
|
|
2708
|
+
* Commit draft to registry (sync + copy + git push + delete draft)
|
|
2709
|
+
*/
|
|
2710
|
+
async copyDraftToRegistry(owner, repo, packageId, resourceType, resourceName, token) {
|
|
2711
|
+
await this.subscription.connectToRegistry(token, owner, repo);
|
|
2712
|
+
await this.registry.sync(owner, repo, token);
|
|
2713
|
+
const draftResourceDir = this.storage.getDraftResourceDir(owner, repo, packageId, resourceType);
|
|
2714
|
+
const registryResourceDir = this.storage.getResourceTypeDir(owner, repo, packageId, resourceType);
|
|
2715
|
+
const draftPath = ResourcePathHelper.getBasePath(draftResourceDir, resourceName, resourceType);
|
|
2716
|
+
try {
|
|
2717
|
+
await import_promises6.default.stat(draftPath);
|
|
2718
|
+
} catch {
|
|
2719
|
+
throw new shared_exports.ValidationError(`No local draft found for resource '${resourceName}' to commit`);
|
|
2720
|
+
}
|
|
2721
|
+
await import_promises6.default.mkdir(registryResourceDir, { recursive: true });
|
|
2722
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2723
|
+
const draftFolderPath = import_path8.default.join(draftResourceDir, resourceName);
|
|
2724
|
+
const registryFolderPath = import_path8.default.join(registryResourceDir, resourceName);
|
|
2725
|
+
await import_promises6.default.rm(registryFolderPath, { recursive: true, force: true });
|
|
2726
|
+
await this.draft.copyFolderRecursive(draftFolderPath, registryFolderPath);
|
|
2727
|
+
} else {
|
|
2728
|
+
const draftFilePath = import_path8.default.join(draftResourceDir, `${resourceName}.md`);
|
|
2729
|
+
const registryFilePath = import_path8.default.join(registryResourceDir, `${resourceName}.md`);
|
|
2730
|
+
const content = await import_promises6.default.readFile(draftFilePath, "utf8");
|
|
2731
|
+
await import_promises6.default.writeFile(registryFilePath, content, "utf8");
|
|
2732
|
+
}
|
|
2733
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2734
|
+
await this.draft.deleteDraftFolder(owner, repo, packageId, resourceName);
|
|
2735
|
+
} else {
|
|
2736
|
+
const draftFilePath = import_path8.default.join(draftResourceDir, `${resourceName}.md`);
|
|
2737
|
+
await this.draft.deleteDraftFile(draftFilePath);
|
|
2738
|
+
}
|
|
2739
|
+
const message = `feat: update ${(0, import_shared22.getResourceSingular)(resourceType)} ${resourceName} in ${packageId}`;
|
|
2740
|
+
await this.git.commitChanges(owner, repo, message, token);
|
|
2741
|
+
}
|
|
2742
|
+
async getRegistryResources(owner, repo, packageId, resourceType) {
|
|
2743
|
+
try {
|
|
2744
|
+
const resourcePath = this.storage.getResourceTypeDir(owner, repo, packageId, resourceType);
|
|
2745
|
+
const entries = await import_promises6.default.readdir(resourcePath, { withFileTypes: true });
|
|
2746
|
+
if (ResourcePathHelper.isFolder(resourceType)) {
|
|
2747
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
2748
|
+
name: entry.name,
|
|
2749
|
+
path: import_path8.default.join(resourcePath, entry.name)
|
|
2750
|
+
}));
|
|
2751
|
+
} else {
|
|
2752
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({
|
|
2753
|
+
name: entry.name.replace(".md", ""),
|
|
2754
|
+
path: import_path8.default.join(resourcePath, entry.name)
|
|
2755
|
+
}));
|
|
2756
|
+
}
|
|
2757
|
+
} catch {
|
|
2758
|
+
return [];
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
var resourceService = new ResourceService();
|
|
2763
|
+
|
|
2764
|
+
// src/modules/resource/resource.middleware.ts
|
|
2765
|
+
async function ensureResourceExists(req, res, next) {
|
|
2766
|
+
try {
|
|
2767
|
+
const owner = req.params.owner;
|
|
2768
|
+
const repo = req.params.repo;
|
|
2769
|
+
const packageId = req.params.packageId;
|
|
2770
|
+
const resourceType = req.params.resourceType;
|
|
2771
|
+
const name = req.params.name;
|
|
2772
|
+
const exists = await storageService.checkResourceExists(
|
|
2773
|
+
owner,
|
|
2774
|
+
repo,
|
|
2775
|
+
packageId,
|
|
2776
|
+
resourceType,
|
|
2777
|
+
name,
|
|
2778
|
+
(baseDir) => ResourcePathHelper.getBasePath(baseDir, name, resourceType)
|
|
2779
|
+
);
|
|
2780
|
+
if (!exists) {
|
|
2781
|
+
throw new shared_exports.NotFoundError(
|
|
2782
|
+
`Resource '${name}' of type '${resourceType}' not found in package '${packageId}'`
|
|
2783
|
+
);
|
|
2784
|
+
}
|
|
2785
|
+
next();
|
|
2786
|
+
} catch (error) {
|
|
2787
|
+
next(error);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
async function ensureResourceDoesNotExist(req, res, next) {
|
|
2791
|
+
try {
|
|
2792
|
+
const owner = req.params.owner;
|
|
2793
|
+
const repo = req.params.repo;
|
|
2794
|
+
const packageId = req.params.packageId;
|
|
2795
|
+
const resourceType = req.params.resourceType;
|
|
2796
|
+
const name = req.params.name;
|
|
2797
|
+
const exists = await storageService.checkResourceExists(
|
|
2798
|
+
owner,
|
|
2799
|
+
repo,
|
|
2800
|
+
packageId,
|
|
2801
|
+
resourceType,
|
|
2802
|
+
name,
|
|
2803
|
+
(baseDir) => ResourcePathHelper.getBasePath(baseDir, name, resourceType)
|
|
2804
|
+
);
|
|
2805
|
+
if (exists) {
|
|
2806
|
+
throw new shared_exports.ConflictError(
|
|
2807
|
+
`Resource '${name}' of type '${resourceType}' already exists in package '${packageId}'`
|
|
2808
|
+
);
|
|
2809
|
+
}
|
|
2810
|
+
next();
|
|
2811
|
+
} catch (error) {
|
|
2812
|
+
next(error);
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/controllers/resource.controller.ts
|
|
2817
|
+
var import_shared25 = __toESM(require_dist());
|
|
2818
|
+
var ResourceController = class {
|
|
2819
|
+
constructor(resource = resourceService, draft = draftService) {
|
|
2820
|
+
this.resource = resource;
|
|
2821
|
+
this.draft = draft;
|
|
2822
|
+
}
|
|
2823
|
+
async list(req, res, next) {
|
|
2824
|
+
try {
|
|
2825
|
+
const owner = req.params.owner;
|
|
2826
|
+
const repo = req.params.repo;
|
|
2827
|
+
const packageId = req.params.packageId;
|
|
2828
|
+
const resourceType = req.params.resourceType;
|
|
2829
|
+
const resources = await this.resource.listResources(
|
|
2830
|
+
owner,
|
|
2831
|
+
repo,
|
|
2832
|
+
packageId,
|
|
2833
|
+
resourceType
|
|
2834
|
+
);
|
|
2835
|
+
const response = {
|
|
2836
|
+
success: true,
|
|
2837
|
+
data: { resources }
|
|
2838
|
+
};
|
|
2839
|
+
res.json(response);
|
|
2840
|
+
} catch (error) {
|
|
2841
|
+
next(error);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
async getEntry(req, res, next) {
|
|
2845
|
+
try {
|
|
2846
|
+
const owner = req.params.owner;
|
|
2847
|
+
const repo = req.params.repo;
|
|
2848
|
+
const packageId = req.params.packageId;
|
|
2849
|
+
const resourceType = req.params.resourceType;
|
|
2850
|
+
const name = req.params.name;
|
|
2851
|
+
const result = await this.resource.getResourceEntry(
|
|
2852
|
+
owner,
|
|
2853
|
+
repo,
|
|
2854
|
+
packageId,
|
|
2855
|
+
resourceType,
|
|
2856
|
+
name
|
|
2857
|
+
);
|
|
2858
|
+
const response = {
|
|
2859
|
+
success: true,
|
|
2860
|
+
data: {
|
|
2861
|
+
name,
|
|
2862
|
+
content: result.content,
|
|
2863
|
+
source: result.source,
|
|
2864
|
+
path: result.path
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
res.json(response);
|
|
2868
|
+
} catch (error) {
|
|
2869
|
+
next(error);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
async getFilesystem(req, res, next) {
|
|
2873
|
+
try {
|
|
2874
|
+
const owner = req.params.owner;
|
|
2875
|
+
const repo = req.params.repo;
|
|
2876
|
+
const packageId = req.params.packageId;
|
|
2877
|
+
const resourceType = req.params.resourceType;
|
|
2878
|
+
const name = req.params.name;
|
|
2879
|
+
const fsPath = req.params[0] || "";
|
|
2880
|
+
if (!ResourcePathHelper.isFolder(resourceType)) {
|
|
2881
|
+
throw new shared_exports.ValidationError(`Filesystem access is only supported for folder-based resources (skills), not ${resourceType}`);
|
|
2882
|
+
}
|
|
2883
|
+
const result = await this.resource.getResourceFilesystem(
|
|
2884
|
+
owner,
|
|
2885
|
+
repo,
|
|
2886
|
+
packageId,
|
|
2887
|
+
resourceType,
|
|
2888
|
+
name,
|
|
2889
|
+
fsPath
|
|
2890
|
+
);
|
|
2891
|
+
const response = {
|
|
2892
|
+
success: true,
|
|
2893
|
+
data: result
|
|
2894
|
+
};
|
|
2895
|
+
res.json(response);
|
|
2896
|
+
} catch (error) {
|
|
2897
|
+
next(error);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
async editEntry(req, res, next) {
|
|
2901
|
+
try {
|
|
2902
|
+
const owner = req.params.owner;
|
|
2903
|
+
const repo = req.params.repo;
|
|
2904
|
+
const packageId = req.params.packageId;
|
|
2905
|
+
const resourceType = req.params.resourceType;
|
|
2906
|
+
const name = req.params.name;
|
|
2907
|
+
const content = req.body.content;
|
|
2908
|
+
if (content === void 0) {
|
|
2909
|
+
throw new shared_exports.ValidationError("content is required in request body");
|
|
2910
|
+
}
|
|
2911
|
+
await this.resource.editEntryDraft(
|
|
2912
|
+
owner,
|
|
2913
|
+
repo,
|
|
2914
|
+
packageId,
|
|
2915
|
+
resourceType,
|
|
2916
|
+
name,
|
|
2917
|
+
content
|
|
2918
|
+
);
|
|
2919
|
+
const response = {
|
|
2920
|
+
success: true,
|
|
2921
|
+
data: {}
|
|
2922
|
+
};
|
|
2923
|
+
res.json(response);
|
|
2924
|
+
} catch (error) {
|
|
2925
|
+
next(error);
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
async editFile(req, res, next) {
|
|
2929
|
+
try {
|
|
2930
|
+
const owner = req.params.owner;
|
|
2931
|
+
const repo = req.params.repo;
|
|
2932
|
+
const packageId = req.params.packageId;
|
|
2933
|
+
const resourceType = req.params.resourceType;
|
|
2934
|
+
const name = req.params.name;
|
|
2935
|
+
const fsPath = req.params[0] || "";
|
|
2936
|
+
const content = req.body.content;
|
|
2937
|
+
if (!ResourcePathHelper.isFolder(resourceType)) {
|
|
2938
|
+
throw new shared_exports.ValidationError(`Filesystem access is only supported for folder-based resources (skills), not ${resourceType}`);
|
|
2939
|
+
}
|
|
2940
|
+
if (content === void 0) {
|
|
2941
|
+
throw new shared_exports.ValidationError("content is required in request body");
|
|
2942
|
+
}
|
|
2943
|
+
await this.resource.editFileDraft(
|
|
2944
|
+
owner,
|
|
2945
|
+
repo,
|
|
2946
|
+
packageId,
|
|
2947
|
+
resourceType,
|
|
2948
|
+
name,
|
|
2949
|
+
fsPath,
|
|
2950
|
+
content
|
|
2951
|
+
);
|
|
2952
|
+
const response = {
|
|
2953
|
+
success: true,
|
|
2954
|
+
data: {}
|
|
2955
|
+
};
|
|
2956
|
+
res.json(response);
|
|
2957
|
+
} catch (error) {
|
|
2958
|
+
next(error);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
async createDraft(req, res, next) {
|
|
2962
|
+
try {
|
|
2963
|
+
const owner = req.params.owner;
|
|
2964
|
+
const repo = req.params.repo;
|
|
2965
|
+
const packageId = req.params.packageId;
|
|
2966
|
+
const resourceType = req.params.resourceType;
|
|
2967
|
+
const name = req.params.name;
|
|
2968
|
+
let path10;
|
|
2969
|
+
const sotrageType = (0, import_shared25.getResourceStorageType)(resourceType);
|
|
2970
|
+
switch (sotrageType) {
|
|
2971
|
+
case import_shared25.ResourceStorageType.FILE:
|
|
2972
|
+
path10 = await this.draft.createDraftFile(owner, repo, packageId, resourceType, name);
|
|
2973
|
+
break;
|
|
2974
|
+
case import_shared25.ResourceStorageType.FOLDER:
|
|
2975
|
+
path10 = await this.draft.createResourceFolder(owner, repo, packageId, resourceType, name);
|
|
2976
|
+
break;
|
|
2977
|
+
}
|
|
2978
|
+
const response = {
|
|
2979
|
+
success: true,
|
|
2980
|
+
data: { path: path10 }
|
|
2981
|
+
};
|
|
2982
|
+
res.json(response);
|
|
2983
|
+
} catch (error) {
|
|
2984
|
+
next(error);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
async copyToDraft(req, res, next) {
|
|
2988
|
+
try {
|
|
2989
|
+
const owner = req.params.owner;
|
|
2990
|
+
const repo = req.params.repo;
|
|
2991
|
+
const packageId = req.params.packageId;
|
|
2992
|
+
const resourceType = req.params.resourceType;
|
|
2993
|
+
const name = req.params.name;
|
|
2994
|
+
const sourcePath = req.body.sourcePath;
|
|
2995
|
+
if (!sourcePath) {
|
|
2996
|
+
}
|
|
2997
|
+
let draftPath = null;
|
|
2998
|
+
if (sourcePath) {
|
|
2999
|
+
draftPath = await this.draft.copyFileOrFolderFromSystem(
|
|
3000
|
+
sourcePath,
|
|
3001
|
+
owner,
|
|
3002
|
+
repo,
|
|
3003
|
+
packageId,
|
|
3004
|
+
resourceType,
|
|
3005
|
+
name
|
|
3006
|
+
);
|
|
3007
|
+
} else {
|
|
3008
|
+
draftPath = await this.draft.copyFileOrFolderFromResource(
|
|
3009
|
+
owner,
|
|
3010
|
+
repo,
|
|
3011
|
+
packageId,
|
|
3012
|
+
resourceType,
|
|
3013
|
+
name
|
|
3014
|
+
);
|
|
3015
|
+
}
|
|
3016
|
+
if (!draftPath) {
|
|
3017
|
+
throw new shared_exports.ValidationError("Failed to copy from source to draft");
|
|
3018
|
+
}
|
|
3019
|
+
const response = {
|
|
3020
|
+
success: true,
|
|
3021
|
+
data: { path: draftPath }
|
|
3022
|
+
};
|
|
3023
|
+
res.json(response);
|
|
3024
|
+
} catch (error) {
|
|
3025
|
+
next(error);
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
async pushDraft(req, res, next) {
|
|
3029
|
+
try {
|
|
3030
|
+
const token = req.githubToken;
|
|
3031
|
+
const owner = req.params.owner;
|
|
3032
|
+
const repo = req.params.repo;
|
|
3033
|
+
const packageId = req.params.packageId;
|
|
3034
|
+
const resourceType = req.params.resourceType;
|
|
3035
|
+
const name = req.params.name;
|
|
3036
|
+
await this.resource.copyDraftToRegistry(
|
|
3037
|
+
owner,
|
|
3038
|
+
repo,
|
|
3039
|
+
packageId,
|
|
3040
|
+
resourceType,
|
|
3041
|
+
name,
|
|
3042
|
+
token
|
|
3043
|
+
);
|
|
3044
|
+
const response = {
|
|
3045
|
+
success: true,
|
|
3046
|
+
data: {}
|
|
3047
|
+
};
|
|
3048
|
+
res.json(response);
|
|
3049
|
+
} catch (error) {
|
|
3050
|
+
next(error);
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
async pushDelete(req, res, next) {
|
|
3054
|
+
try {
|
|
3055
|
+
const token = req.githubToken;
|
|
3056
|
+
const owner = req.params.owner;
|
|
3057
|
+
const repo = req.params.repo;
|
|
3058
|
+
const packageId = req.params.packageId;
|
|
3059
|
+
const resourceType = req.params.resourceType;
|
|
3060
|
+
const name = req.params.name;
|
|
3061
|
+
await this.resource.deleteResource(
|
|
3062
|
+
owner,
|
|
3063
|
+
repo,
|
|
3064
|
+
packageId,
|
|
3065
|
+
resourceType,
|
|
3066
|
+
name,
|
|
3067
|
+
token
|
|
3068
|
+
);
|
|
3069
|
+
const response = {
|
|
3070
|
+
success: true,
|
|
3071
|
+
data: {}
|
|
3072
|
+
};
|
|
3073
|
+
res.json(response);
|
|
3074
|
+
} catch (error) {
|
|
3075
|
+
next(error);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
};
|
|
3079
|
+
|
|
3080
|
+
// src/routes/resource.routes.ts
|
|
3081
|
+
var router5 = (0, import_express5.Router)({ mergeParams: true });
|
|
3082
|
+
var resourceController = new ResourceController();
|
|
3083
|
+
router5.get(
|
|
3084
|
+
"/",
|
|
3085
|
+
validateResourceType,
|
|
3086
|
+
resourceController.list.bind(resourceController)
|
|
3087
|
+
);
|
|
3088
|
+
router5.get(
|
|
3089
|
+
"/:name",
|
|
3090
|
+
validateResourceType,
|
|
3091
|
+
ensureResourceExists,
|
|
3092
|
+
resourceController.getEntry.bind(resourceController)
|
|
3093
|
+
);
|
|
3094
|
+
router5.get(
|
|
3095
|
+
"/:name/fs/*",
|
|
3096
|
+
validateResourceType,
|
|
3097
|
+
ensureResourceExists,
|
|
3098
|
+
resourceController.getFilesystem.bind(resourceController)
|
|
3099
|
+
);
|
|
3100
|
+
router5.put(
|
|
3101
|
+
"/:name",
|
|
3102
|
+
validateResourceType,
|
|
3103
|
+
resourceController.editEntry.bind(resourceController)
|
|
3104
|
+
);
|
|
3105
|
+
router5.put(
|
|
3106
|
+
"/:name/fs/*",
|
|
3107
|
+
validateResourceType,
|
|
3108
|
+
resourceController.editFile.bind(resourceController)
|
|
3109
|
+
);
|
|
3110
|
+
router5.put(
|
|
3111
|
+
"/:name/init",
|
|
3112
|
+
validateResourceType,
|
|
3113
|
+
ensureResourceDoesNotExist,
|
|
3114
|
+
resourceController.createDraft.bind(resourceController)
|
|
3115
|
+
);
|
|
3116
|
+
router5.put(
|
|
3117
|
+
"/:name/copy",
|
|
3118
|
+
validateResourceType,
|
|
3119
|
+
resourceController.copyToDraft.bind(resourceController)
|
|
3120
|
+
);
|
|
3121
|
+
router5.post(
|
|
3122
|
+
"/:name",
|
|
3123
|
+
validateResourceType,
|
|
3124
|
+
ensureResourceExists,
|
|
3125
|
+
resourceController.pushDraft.bind(resourceController)
|
|
3126
|
+
);
|
|
3127
|
+
router5.delete(
|
|
3128
|
+
"/:name",
|
|
3129
|
+
validateResourceType,
|
|
3130
|
+
ensureResourceExists,
|
|
3131
|
+
resourceController.pushDelete.bind(resourceController)
|
|
3132
|
+
);
|
|
3133
|
+
var resource_routes_default = router5;
|
|
3134
|
+
|
|
3135
|
+
// src/routes/package.routes.ts
|
|
3136
|
+
var router6 = (0, import_express6.Router)({ mergeParams: true });
|
|
3137
|
+
var packageController = new PackageController();
|
|
3138
|
+
router6.get(
|
|
3139
|
+
"/",
|
|
3140
|
+
ensurePackageExists,
|
|
3141
|
+
packageController.get.bind(packageController)
|
|
3142
|
+
);
|
|
3143
|
+
router6.post(
|
|
3144
|
+
"/",
|
|
3145
|
+
ensureRegistrySync,
|
|
3146
|
+
ensurePackageDoesNotExist,
|
|
3147
|
+
packageController.pushCreate.bind(packageController)
|
|
3148
|
+
);
|
|
3149
|
+
router6.delete(
|
|
3150
|
+
"/",
|
|
3151
|
+
ensureRegistrySync,
|
|
3152
|
+
ensurePackageExists,
|
|
3153
|
+
packageController.pushDelete.bind(packageController)
|
|
3154
|
+
);
|
|
3155
|
+
router6.use("/:resourceType", ensurePackageExists, resource_routes_default);
|
|
3156
|
+
var package_routes_default = router6;
|
|
3157
|
+
|
|
3158
|
+
// src/controllers/registry.controller.ts
|
|
3159
|
+
var import_shared27 = __toESM(require_dist());
|
|
3160
|
+
var RegistryController = class {
|
|
3161
|
+
constructor(registry = registryService, github = githubService, storage = storageService) {
|
|
3162
|
+
this.registry = registry;
|
|
3163
|
+
this.github = github;
|
|
3164
|
+
this.storage = storage;
|
|
3165
|
+
}
|
|
3166
|
+
async list(req, res, next) {
|
|
3167
|
+
try {
|
|
3168
|
+
const token = req.githubToken;
|
|
3169
|
+
const enriched = await this.registry.list(token);
|
|
3170
|
+
const response = {
|
|
3171
|
+
success: true,
|
|
3172
|
+
data: { registries: enriched }
|
|
3173
|
+
};
|
|
3174
|
+
res.json(response);
|
|
3175
|
+
} catch (error) {
|
|
3176
|
+
next(error);
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
// ENHANCEMENT: Validate the repository for it's safety before sync
|
|
3180
|
+
async connect(req, res, next) {
|
|
3181
|
+
try {
|
|
3182
|
+
const token = req.githubToken;
|
|
3183
|
+
const owner = req.params.owner;
|
|
3184
|
+
const repo = req.params.repo;
|
|
3185
|
+
const { createIfNotExists, description, isPrivate } = req.body || {};
|
|
3186
|
+
if (!owner || !repo) {
|
|
3187
|
+
throw new shared_exports.ValidationError("owner and repo are required");
|
|
3188
|
+
}
|
|
3189
|
+
const registry = await this.registry.connect(owner, repo, token, {
|
|
3190
|
+
createIfNotExists,
|
|
3191
|
+
description,
|
|
3192
|
+
isPrivate,
|
|
3193
|
+
githubUsername: req.githubUser?.githubUsername
|
|
3194
|
+
});
|
|
3195
|
+
const response = {
|
|
3196
|
+
success: true,
|
|
3197
|
+
data: {
|
|
3198
|
+
registry: {
|
|
3199
|
+
owner: registry.owner,
|
|
3200
|
+
repo: registry.repo,
|
|
3201
|
+
fullName: registry.fullName,
|
|
3202
|
+
cloneUrl: registry.cloneUrl,
|
|
3203
|
+
localPath: this.storage.getRegistryPath(registry.owner, registry.repo),
|
|
3204
|
+
accessLevel: import_shared27.AccessLevel.WRITE,
|
|
3205
|
+
trusted: false,
|
|
3206
|
+
enabled: true,
|
|
3207
|
+
lastSync: registry.lastSynced,
|
|
3208
|
+
connectedBy: req.githubUser?.githubUsername || ""
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
};
|
|
3212
|
+
res.json(response);
|
|
3213
|
+
} catch (error) {
|
|
3214
|
+
next(error);
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
async disconnect(req, res, next) {
|
|
3218
|
+
try {
|
|
3219
|
+
const token = req.githubToken;
|
|
3220
|
+
const owner = req.params.owner;
|
|
3221
|
+
const repo = req.params.repo;
|
|
3222
|
+
if (!owner || !repo) {
|
|
3223
|
+
throw new shared_exports.ValidationError("owner and repo are required");
|
|
3224
|
+
}
|
|
3225
|
+
await this.registry.disconnect(owner, repo, token);
|
|
3226
|
+
const response = {
|
|
3227
|
+
success: true,
|
|
3228
|
+
data: {}
|
|
3229
|
+
};
|
|
3230
|
+
res.json(response);
|
|
3231
|
+
} catch (error) {
|
|
3232
|
+
next(error);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
// ENHANCEMENT: Validate the repository for it's safety before sync
|
|
3236
|
+
async sync(req, res, next) {
|
|
3237
|
+
try {
|
|
3238
|
+
const token = req.githubToken;
|
|
3239
|
+
const owner = req.params.owner;
|
|
3240
|
+
const repo = req.params.repo;
|
|
3241
|
+
if (!owner || !repo) {
|
|
3242
|
+
throw new shared_exports.ValidationError("owner and repo are required");
|
|
3243
|
+
}
|
|
3244
|
+
const result = await this.registry.sync(owner, repo, token);
|
|
3245
|
+
const response = {
|
|
3246
|
+
success: true,
|
|
3247
|
+
data: {
|
|
3248
|
+
message: `Successfully synced ${owner}/${repo}`,
|
|
3249
|
+
changedFiles: result.changedFiles
|
|
3250
|
+
}
|
|
3251
|
+
};
|
|
3252
|
+
res.json(response);
|
|
3253
|
+
} catch (error) {
|
|
3254
|
+
next(error);
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
async updatePreferences(req, res, next) {
|
|
3258
|
+
try {
|
|
3259
|
+
const owner = req.params.owner;
|
|
3260
|
+
const repo = req.params.repo;
|
|
3261
|
+
const { trusted } = req.body;
|
|
3262
|
+
if (!owner || !repo) {
|
|
3263
|
+
throw new shared_exports.ValidationError("owner and repo are required");
|
|
3264
|
+
}
|
|
3265
|
+
if (trusted === false) {
|
|
3266
|
+
await this.storage.removeTrustPreference(owner, repo);
|
|
3267
|
+
} else if (trusted === true) {
|
|
3268
|
+
await this.storage.setDontShowSecurityViolations(owner, repo);
|
|
3269
|
+
}
|
|
3270
|
+
const response = {
|
|
3271
|
+
success: true,
|
|
3272
|
+
data: {}
|
|
3273
|
+
};
|
|
3274
|
+
res.json(response);
|
|
3275
|
+
} catch (error) {
|
|
3276
|
+
next(error);
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
|
|
3281
|
+
// src/routes/registry.routes.ts
|
|
3282
|
+
var router7 = (0, import_express7.Router)({ mergeParams: true });
|
|
3283
|
+
var registryController = new RegistryController();
|
|
3284
|
+
var packageController2 = new PackageController();
|
|
3285
|
+
router7.post(
|
|
3286
|
+
"/",
|
|
3287
|
+
validateRepositoryWriteAccess,
|
|
3288
|
+
ensureRegistryDoesNotExist,
|
|
3289
|
+
registryController.connect.bind(registryController)
|
|
3290
|
+
);
|
|
3291
|
+
router7.delete(
|
|
3292
|
+
"/",
|
|
3293
|
+
ensureRegistryExists,
|
|
3294
|
+
registryController.disconnect.bind(registryController)
|
|
3295
|
+
);
|
|
3296
|
+
router7.post(
|
|
3297
|
+
"/sync",
|
|
3298
|
+
ensureRegistryExists,
|
|
3299
|
+
registryController.sync.bind(registryController)
|
|
3300
|
+
);
|
|
3301
|
+
router7.patch(
|
|
3302
|
+
"/",
|
|
3303
|
+
ensureRegistryExists,
|
|
3304
|
+
registryController.updatePreferences.bind(registryController)
|
|
3305
|
+
);
|
|
3306
|
+
router7.get(
|
|
3307
|
+
"/packages",
|
|
3308
|
+
ensureRegistryExists,
|
|
3309
|
+
packageController2.list.bind(packageController2)
|
|
3310
|
+
);
|
|
3311
|
+
router7.use("/:packageId", ensureRegistryExists, package_routes_default);
|
|
3312
|
+
var registry_routes_default = router7;
|
|
3313
|
+
|
|
3314
|
+
// src/routes/index.ts
|
|
3315
|
+
var router8 = (0, import_express8.Router)();
|
|
3316
|
+
var registryController2 = new RegistryController();
|
|
3317
|
+
router8.use(authenticateAishelfClient);
|
|
3318
|
+
router8.use("/auth", auth_routes_default);
|
|
3319
|
+
router8.use("/audit", audit_routes_default);
|
|
3320
|
+
router8.use("/user", authenticateGithubToken, user_routes_default);
|
|
3321
|
+
router8.use("/orgs", authenticateGithubToken, org_routes_default);
|
|
3322
|
+
router8.get("/registries", authenticateGithubToken, registryController2.list.bind(registryController2));
|
|
3323
|
+
router8.use("/:owner/:repo", authenticateGithubToken, registry_routes_default);
|
|
3324
|
+
var routes_default = router8;
|
|
3325
|
+
|
|
3326
|
+
// src/middleware/error.middleware.ts
|
|
3327
|
+
function errorHandler(error, req, res, next) {
|
|
3328
|
+
if (error instanceof shared_exports.AppError) {
|
|
3329
|
+
logger_default.error({
|
|
3330
|
+
message: error.message,
|
|
3331
|
+
statusCode: error.statusCode,
|
|
3332
|
+
code: error.code,
|
|
3333
|
+
path: req.path,
|
|
3334
|
+
method: req.method,
|
|
3335
|
+
isOperational: error.isOperational
|
|
3336
|
+
});
|
|
3337
|
+
res.status(error.statusCode).json({
|
|
3338
|
+
success: false,
|
|
3339
|
+
error: {
|
|
3340
|
+
message: error.message,
|
|
3341
|
+
code: error.code
|
|
3342
|
+
}
|
|
3343
|
+
});
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
logger_default.error({
|
|
3347
|
+
message: "Unknown error",
|
|
3348
|
+
error: error.message,
|
|
3349
|
+
path: req.path,
|
|
3350
|
+
method: req.method
|
|
3351
|
+
});
|
|
3352
|
+
res.status(500).json({
|
|
3353
|
+
success: false,
|
|
3354
|
+
error: {
|
|
3355
|
+
message: "Internal server error",
|
|
3356
|
+
code: "INTERNAL_ERROR"
|
|
3357
|
+
}
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
// src/server.ts
|
|
3362
|
+
var AISHELF_ROOT = import_path9.default.join(import_os3.default.homedir(), ".aishelf");
|
|
3363
|
+
var PID_FILE = import_path9.default.join(AISHELF_ROOT, "service.pid");
|
|
3364
|
+
var PORT = 5314;
|
|
3365
|
+
async function writePid() {
|
|
3366
|
+
await import_promises7.default.mkdir(AISHELF_ROOT, { recursive: true });
|
|
3367
|
+
await import_promises7.default.writeFile(PID_FILE, process.pid.toString());
|
|
3368
|
+
}
|
|
3369
|
+
async function clearPid() {
|
|
3370
|
+
try {
|
|
3371
|
+
await import_promises7.default.unlink(PID_FILE);
|
|
3372
|
+
} catch {
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
async function ensureDirectory(dir) {
|
|
3376
|
+
await import_promises7.default.mkdir(dir, { recursive: true });
|
|
3377
|
+
}
|
|
3378
|
+
async function handleGet(req, res, pathOverride) {
|
|
3379
|
+
try {
|
|
3380
|
+
const relativePath = pathOverride || req.path.replace(/^\/raw/, "");
|
|
3381
|
+
const result = await filesystemService.read(AISHELF_ROOT, relativePath);
|
|
3382
|
+
if (result.type === "directory") {
|
|
3383
|
+
res.json(result);
|
|
3384
|
+
} else {
|
|
3385
|
+
res.setHeader("Content-Type", "text/plain");
|
|
3386
|
+
res.send(result.content);
|
|
3387
|
+
}
|
|
3388
|
+
} catch (error) {
|
|
3389
|
+
if (error.code === "ENOENT") {
|
|
3390
|
+
res.status(404).json({ error: "Not found", path: req.path });
|
|
3391
|
+
} else {
|
|
3392
|
+
res.status(500).json({ error: error.message });
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
async function startServer(port = PORT) {
|
|
3397
|
+
await ensureDirectory(AISHELF_ROOT);
|
|
3398
|
+
await ensureDirectory(import_path9.default.join(AISHELF_ROOT, "logs"));
|
|
3399
|
+
const app = (0, import_express9.default)();
|
|
3400
|
+
app.use(import_express9.default.json());
|
|
3401
|
+
app.use((req, res, next) => {
|
|
3402
|
+
res.header("Access-Control-Allow-Origin", "*");
|
|
3403
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
3404
|
+
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Client-Token");
|
|
3405
|
+
if (req.method === "OPTIONS") {
|
|
3406
|
+
res.sendStatus(200);
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
next();
|
|
3410
|
+
});
|
|
3411
|
+
app.get("/health", (req, res) => {
|
|
3412
|
+
res.json({ status: "ok" });
|
|
3413
|
+
});
|
|
3414
|
+
app.use("/api", routes_default);
|
|
3415
|
+
app.get("/raw/*", async (req, res) => {
|
|
3416
|
+
await handleGet(req, res, req.path.replace("/raw", ""));
|
|
3417
|
+
});
|
|
3418
|
+
app.use(errorHandler);
|
|
3419
|
+
return new Promise((resolve) => {
|
|
3420
|
+
const server = app.listen(port, async () => {
|
|
3421
|
+
await writePid();
|
|
3422
|
+
console.log(`AIShelf service running on http://localhost:${port}`);
|
|
3423
|
+
console.log(`Serving: ${AISHELF_ROOT}`);
|
|
3424
|
+
resolve();
|
|
3425
|
+
});
|
|
3426
|
+
const shutdown = async () => {
|
|
3427
|
+
await clearPid();
|
|
3428
|
+
server.close(() => process.exit(0));
|
|
3429
|
+
};
|
|
3430
|
+
process.on("SIGTERM", shutdown);
|
|
3431
|
+
process.on("SIGINT", shutdown);
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
// src/autostart.ts
|
|
3436
|
+
startServer().catch(console.error);
|