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