@vasperacapital/vaspera-shared 0.1.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/index.mjs ADDED
@@ -0,0 +1,421 @@
1
+ // src/types/index.ts
2
+ var QUOTA_LIMITS = {
3
+ free: 5,
4
+ starter: 100,
5
+ pro: 500,
6
+ enterprise: 999999
7
+ // Effectively unlimited
8
+ };
9
+ var RATE_LIMITS = {
10
+ free: { perMinute: 10, perDay: 100 },
11
+ starter: { perMinute: 30, perDay: 1e3 },
12
+ pro: { perMinute: 60, perDay: 5e3 },
13
+ enterprise: { perMinute: 120, perDay: 999999 }
14
+ };
15
+
16
+ // src/errors/codes.ts
17
+ var ErrorCodes = {
18
+ // Authentication Errors (VPM-AUTH-XXX)
19
+ AUTH: {
20
+ REQUIRED: {
21
+ code: "VPM-AUTH-001",
22
+ message: "Authentication required",
23
+ httpStatus: 401
24
+ },
25
+ INVALID_TOKEN: {
26
+ code: "VPM-AUTH-002",
27
+ message: "Invalid or expired authentication token",
28
+ httpStatus: 401
29
+ },
30
+ INSUFFICIENT_PERMISSIONS: {
31
+ code: "VPM-AUTH-003",
32
+ message: "Insufficient permissions for this action",
33
+ httpStatus: 403
34
+ },
35
+ SESSION_EXPIRED: {
36
+ code: "VPM-AUTH-004",
37
+ message: "Session has expired, please login again",
38
+ httpStatus: 401
39
+ },
40
+ MFA_REQUIRED: {
41
+ code: "VPM-AUTH-005",
42
+ message: "Multi-factor authentication required",
43
+ httpStatus: 403
44
+ }
45
+ },
46
+ // API Key Errors (VPM-API-KEY-XXX)
47
+ API_KEY: {
48
+ REQUIRED: {
49
+ code: "VPM-API-KEY-001",
50
+ message: "API key is required",
51
+ httpStatus: 401
52
+ },
53
+ INVALID: {
54
+ code: "VPM-API-KEY-002",
55
+ message: "Invalid API key",
56
+ httpStatus: 401
57
+ },
58
+ REVOKED: {
59
+ code: "VPM-API-KEY-003",
60
+ message: "API key has been revoked",
61
+ httpStatus: 401
62
+ },
63
+ EXPIRED: {
64
+ code: "VPM-API-KEY-004",
65
+ message: "API key has expired",
66
+ httpStatus: 401
67
+ },
68
+ RATE_LIMITED: {
69
+ code: "VPM-API-KEY-005",
70
+ message: "API key rate limit exceeded",
71
+ httpStatus: 429
72
+ },
73
+ QUOTA_EXCEEDED: {
74
+ code: "VPM-API-KEY-006",
75
+ message: "Monthly usage quota exceeded",
76
+ httpStatus: 429
77
+ }
78
+ },
79
+ // MCP Tool Errors (VPM-MCP-XXX)
80
+ MCP: {
81
+ TOOL_NOT_FOUND: {
82
+ code: "VPM-MCP-001",
83
+ message: "Requested tool not found",
84
+ httpStatus: 404
85
+ },
86
+ INVALID_ARGUMENTS: {
87
+ code: "VPM-MCP-002",
88
+ message: "Invalid tool arguments provided",
89
+ httpStatus: 400
90
+ },
91
+ EXECUTION_FAILED: {
92
+ code: "VPM-MCP-003",
93
+ message: "Tool execution failed",
94
+ httpStatus: 500
95
+ },
96
+ TIMEOUT: {
97
+ code: "VPM-MCP-004",
98
+ message: "Tool execution timed out",
99
+ httpStatus: 504
100
+ },
101
+ LLM_ERROR: {
102
+ code: "VPM-MCP-005",
103
+ message: "AI model returned an error",
104
+ httpStatus: 502
105
+ },
106
+ CONTEXT_TOO_LARGE: {
107
+ code: "VPM-MCP-006",
108
+ message: "Input context exceeds maximum size",
109
+ httpStatus: 413
110
+ }
111
+ },
112
+ // Billing Errors (VPM-BILLING-XXX)
113
+ BILLING: {
114
+ NO_SUBSCRIPTION: {
115
+ code: "VPM-BILLING-001",
116
+ message: "No active subscription found",
117
+ httpStatus: 402
118
+ },
119
+ SUBSCRIPTION_EXPIRED: {
120
+ code: "VPM-BILLING-002",
121
+ message: "Subscription has expired",
122
+ httpStatus: 402
123
+ },
124
+ PAYMENT_FAILED: {
125
+ code: "VPM-BILLING-003",
126
+ message: "Payment processing failed",
127
+ httpStatus: 402
128
+ },
129
+ FEATURE_NOT_INCLUDED: {
130
+ code: "VPM-BILLING-004",
131
+ message: "Feature not included in current plan",
132
+ httpStatus: 403
133
+ },
134
+ UPGRADE_REQUIRED: {
135
+ code: "VPM-BILLING-005",
136
+ message: "Plan upgrade required for this action",
137
+ httpStatus: 403
138
+ }
139
+ },
140
+ // Integration Errors (VPM-INT-XXX)
141
+ INTEGRATION: {
142
+ NOT_CONNECTED: {
143
+ code: "VPM-INT-001",
144
+ message: "Integration not connected",
145
+ httpStatus: 400
146
+ },
147
+ TOKEN_EXPIRED: {
148
+ code: "VPM-INT-002",
149
+ message: "Integration token expired, reconnection required",
150
+ httpStatus: 401
151
+ },
152
+ REFRESH_FAILED: {
153
+ code: "VPM-INT-003",
154
+ message: "Failed to refresh integration token",
155
+ httpStatus: 502
156
+ },
157
+ API_ERROR: {
158
+ code: "VPM-INT-004",
159
+ message: "External integration API error",
160
+ httpStatus: 502
161
+ },
162
+ RATE_LIMITED: {
163
+ code: "VPM-INT-005",
164
+ message: "Integration rate limit exceeded",
165
+ httpStatus: 429
166
+ }
167
+ },
168
+ // Validation Errors (VPM-VAL-XXX)
169
+ VALIDATION: {
170
+ REQUIRED_FIELD: {
171
+ code: "VPM-VAL-001",
172
+ message: "Required field missing",
173
+ httpStatus: 400
174
+ },
175
+ INVALID_FORMAT: {
176
+ code: "VPM-VAL-002",
177
+ message: "Invalid field format",
178
+ httpStatus: 400
179
+ },
180
+ OUT_OF_RANGE: {
181
+ code: "VPM-VAL-003",
182
+ message: "Value out of allowed range",
183
+ httpStatus: 400
184
+ },
185
+ INVALID_JSON: {
186
+ code: "VPM-VAL-004",
187
+ message: "Invalid JSON in request body",
188
+ httpStatus: 400
189
+ }
190
+ },
191
+ // System Errors (VPM-SYS-XXX)
192
+ SYSTEM: {
193
+ INTERNAL_ERROR: {
194
+ code: "VPM-SYS-001",
195
+ message: "Internal server error",
196
+ httpStatus: 500
197
+ },
198
+ DATABASE_ERROR: {
199
+ code: "VPM-SYS-002",
200
+ message: "Database operation failed",
201
+ httpStatus: 500
202
+ },
203
+ SERVICE_UNAVAILABLE: {
204
+ code: "VPM-SYS-003",
205
+ message: "Service temporarily unavailable",
206
+ httpStatus: 503
207
+ },
208
+ MAINTENANCE_MODE: {
209
+ code: "VPM-SYS-004",
210
+ message: "System is under maintenance",
211
+ httpStatus: 503
212
+ }
213
+ }
214
+ };
215
+
216
+ // src/errors/factory.ts
217
+ function generateRequestId() {
218
+ return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
219
+ }
220
+ function createError(errorDef, details, requestId) {
221
+ return {
222
+ code: errorDef.code,
223
+ message: errorDef.message,
224
+ details,
225
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
226
+ requestId: requestId || generateRequestId(),
227
+ docUrl: `https://docs.vaspera.pm/errors/${errorDef.code}`
228
+ };
229
+ }
230
+ function createErrorResponse(errorDef, details, requestId) {
231
+ return {
232
+ response: {
233
+ success: false,
234
+ error: createError(errorDef, details, requestId)
235
+ },
236
+ status: errorDef.httpStatus
237
+ };
238
+ }
239
+ function getNextResetDate() {
240
+ const now = /* @__PURE__ */ new Date();
241
+ const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
242
+ return nextMonth.toISOString();
243
+ }
244
+ var Errors = {
245
+ authRequired: (requestId) => createErrorResponse(ErrorCodes.AUTH.REQUIRED, void 0, requestId),
246
+ invalidApiKey: (requestId) => createErrorResponse(ErrorCodes.API_KEY.INVALID, void 0, requestId),
247
+ apiKeyRevoked: (requestId) => createErrorResponse(ErrorCodes.API_KEY.REVOKED, void 0, requestId),
248
+ apiKeyExpired: (requestId) => createErrorResponse(ErrorCodes.API_KEY.EXPIRED, void 0, requestId),
249
+ rateLimited: (retryAfter, requestId) => createErrorResponse(
250
+ ErrorCodes.API_KEY.RATE_LIMITED,
251
+ { retryAfterSeconds: retryAfter },
252
+ requestId
253
+ ),
254
+ quotaExceeded: (currentUsage, limit, requestId) => createErrorResponse(
255
+ ErrorCodes.API_KEY.QUOTA_EXCEEDED,
256
+ { currentUsage, limit, resetDate: getNextResetDate() },
257
+ requestId
258
+ ),
259
+ toolNotFound: (toolName, requestId) => createErrorResponse(
260
+ ErrorCodes.MCP.TOOL_NOT_FOUND,
261
+ { tool: toolName },
262
+ requestId
263
+ ),
264
+ toolExecutionFailed: (toolName, reason, requestId) => createErrorResponse(
265
+ ErrorCodes.MCP.EXECUTION_FAILED,
266
+ { tool: toolName, reason },
267
+ requestId
268
+ ),
269
+ toolTimeout: (toolName, timeoutMs, requestId) => createErrorResponse(
270
+ ErrorCodes.MCP.TIMEOUT,
271
+ { tool: toolName, timeoutMs },
272
+ requestId
273
+ ),
274
+ validationFailed: (field, reason, requestId) => createErrorResponse(
275
+ ErrorCodes.VALIDATION.REQUIRED_FIELD,
276
+ { field, reason },
277
+ requestId
278
+ ),
279
+ invalidFormat: (field, expected, requestId) => createErrorResponse(
280
+ ErrorCodes.VALIDATION.INVALID_FORMAT,
281
+ { field, expected },
282
+ requestId
283
+ ),
284
+ internalError: (message, requestId) => createErrorResponse(
285
+ ErrorCodes.SYSTEM.INTERNAL_ERROR,
286
+ message ? { message } : void 0,
287
+ requestId
288
+ ),
289
+ databaseError: (operation, requestId) => createErrorResponse(
290
+ ErrorCodes.SYSTEM.DATABASE_ERROR,
291
+ { operation },
292
+ requestId
293
+ ),
294
+ integrationNotConnected: (provider, requestId) => createErrorResponse(
295
+ ErrorCodes.INTEGRATION.NOT_CONNECTED,
296
+ { provider },
297
+ requestId
298
+ ),
299
+ subscriptionRequired: (feature, requiredTier, requestId) => createErrorResponse(
300
+ ErrorCodes.BILLING.FEATURE_NOT_INCLUDED,
301
+ { feature, requiredTier },
302
+ requestId
303
+ )
304
+ };
305
+ var VasperaApiError = class extends Error {
306
+ errorDef;
307
+ details;
308
+ constructor(errorDef, details) {
309
+ super(errorDef.message);
310
+ this.name = "VasperaApiError";
311
+ this.errorDef = errorDef;
312
+ this.details = details;
313
+ }
314
+ toResponse(requestId) {
315
+ return createErrorResponse(this.errorDef, this.details, requestId);
316
+ }
317
+ };
318
+
319
+ // src/utils/api-key.ts
320
+ import { randomBytes, createHash } from "crypto";
321
+ var API_KEY_PREFIX_LIVE = "vpm_live_";
322
+ var API_KEY_PREFIX_TEST = "vpm_test_";
323
+ var API_KEY_RANDOM_BYTES = 24;
324
+ function generateApiKey(environment = "live") {
325
+ const prefix = environment === "live" ? API_KEY_PREFIX_LIVE : API_KEY_PREFIX_TEST;
326
+ const randomPart = randomBytes(API_KEY_RANDOM_BYTES).toString("base64url").slice(0, 32);
327
+ const key = `${prefix}${randomPart}`;
328
+ const keyPrefix = key.slice(0, 16);
329
+ const keyHash = hashApiKey(key);
330
+ return {
331
+ key,
332
+ keyPrefix,
333
+ keyHash
334
+ };
335
+ }
336
+ function hashApiKey(key) {
337
+ return createHash("sha256").update(key).digest("hex");
338
+ }
339
+ function isValidApiKeyFormat(key) {
340
+ const livePattern = /^vpm_live_[a-zA-Z0-9_-]{32}$/;
341
+ const testPattern = /^vpm_test_[a-zA-Z0-9_-]{32}$/;
342
+ return livePattern.test(key) || testPattern.test(key);
343
+ }
344
+ function extractKeyPrefix(key) {
345
+ return key.slice(0, 16);
346
+ }
347
+ function isTestKey(key) {
348
+ return key.startsWith(API_KEY_PREFIX_TEST);
349
+ }
350
+ function maskApiKey(key) {
351
+ if (key.length < 24) return key;
352
+ return `${key.slice(0, 16)}...${key.slice(-4)}`;
353
+ }
354
+
355
+ // src/utils/encryption.ts
356
+ import {
357
+ createCipheriv,
358
+ createDecipheriv,
359
+ randomBytes as randomBytes2,
360
+ scrypt
361
+ } from "crypto";
362
+ import { promisify } from "util";
363
+ var scryptAsync = promisify(scrypt);
364
+ var ALGORITHM = "aes-256-gcm";
365
+ var IV_LENGTH = 16;
366
+ var SALT_LENGTH = 32;
367
+ var KEY_LENGTH = 32;
368
+ async function encrypt(text, secret) {
369
+ const salt = randomBytes2(SALT_LENGTH);
370
+ const iv = randomBytes2(IV_LENGTH);
371
+ const key = await scryptAsync(secret, salt, KEY_LENGTH);
372
+ const cipher = createCipheriv(ALGORITHM, key, iv);
373
+ const encrypted = Buffer.concat([
374
+ cipher.update(text, "utf8"),
375
+ cipher.final()
376
+ ]);
377
+ const tag = cipher.getAuthTag();
378
+ return [
379
+ salt.toString("base64"),
380
+ iv.toString("base64"),
381
+ tag.toString("base64"),
382
+ encrypted.toString("base64")
383
+ ].join(":");
384
+ }
385
+ async function decrypt(encryptedText, secret) {
386
+ const parts = encryptedText.split(":");
387
+ if (parts.length !== 4) {
388
+ throw new Error("Invalid encrypted text format");
389
+ }
390
+ const [saltB64, ivB64, tagB64, encryptedB64] = parts;
391
+ const salt = Buffer.from(saltB64, "base64");
392
+ const iv = Buffer.from(ivB64, "base64");
393
+ const tag = Buffer.from(tagB64, "base64");
394
+ const encrypted = Buffer.from(encryptedB64, "base64");
395
+ const key = await scryptAsync(secret, salt, KEY_LENGTH);
396
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
397
+ decipher.setAuthTag(tag);
398
+ return decipher.update(encrypted) + decipher.final("utf8");
399
+ }
400
+ function generateEncryptionSecret() {
401
+ return randomBytes2(32).toString("hex");
402
+ }
403
+ export {
404
+ ErrorCodes,
405
+ Errors,
406
+ QUOTA_LIMITS,
407
+ RATE_LIMITS,
408
+ VasperaApiError,
409
+ createError,
410
+ createErrorResponse,
411
+ decrypt,
412
+ encrypt,
413
+ extractKeyPrefix,
414
+ generateApiKey,
415
+ generateEncryptionSecret,
416
+ hashApiKey,
417
+ isTestKey,
418
+ isValidApiKeyFormat,
419
+ maskApiKey
420
+ };
421
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types/index.ts","../src/errors/codes.ts","../src/errors/factory.ts","../src/utils/api-key.ts","../src/utils/encryption.ts"],"sourcesContent":["// Subscription Types\nexport type SubscriptionTier = 'free' | 'starter' | 'pro' | 'enterprise';\nexport type SubscriptionStatus = 'active' | 'canceled' | 'past_due' | 'trialing' | 'incomplete';\n\n// User Profile\nexport interface UserProfile {\n id: string;\n email: string;\n fullName: string | null;\n avatarUrl: string | null;\n subscriptionTier: SubscriptionTier;\n subscriptionStatus: SubscriptionStatus;\n stripeCustomerId: string | null;\n stripeSubscriptionId: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n// API Key\nexport interface ApiKey {\n id: string;\n userId: string;\n name: string;\n keyPrefix: string;\n lastUsedAt: string | null;\n expiresAt: string | null;\n revokedAt: string | null;\n createdAt: string;\n}\n\nexport interface ApiKeyWithSecret extends Omit<ApiKey, 'revokedAt'> {\n key: string; // Full key (only shown once)\n}\n\n// Usage\nexport interface UsageEvent {\n id: string;\n userId: string;\n apiKeyId: string | null;\n toolName: string;\n tokensUsed: number;\n latencyMs: number | null;\n success: boolean;\n errorCode: string | null;\n requestId: string | null;\n metadata: Record<string, unknown>;\n createdAt: string;\n}\n\nexport interface UsageSummary {\n period: {\n start: string;\n end: string;\n };\n summary: {\n totalToolCalls: number;\n totalTokensUsed: number;\n quotaUsed: number;\n quotaLimit: number;\n quotaPercentage: number;\n };\n byTool: Array<{\n tool: string;\n calls: number;\n tokensUsed: number;\n avgLatencyMs: number;\n }>;\n}\n\n// Quota Limits by Tier\nexport const QUOTA_LIMITS: Record<SubscriptionTier, number> = {\n free: 5,\n starter: 100,\n pro: 500,\n enterprise: 999999, // Effectively unlimited\n};\n\nexport const RATE_LIMITS: Record<SubscriptionTier, { perMinute: number; perDay: number }> = {\n free: { perMinute: 10, perDay: 100 },\n starter: { perMinute: 30, perDay: 1000 },\n pro: { perMinute: 60, perDay: 5000 },\n enterprise: { perMinute: 120, perDay: 999999 },\n};\n\n// Integration Types\nexport type IntegrationProvider = 'jira' | 'linear' | 'github' | 'gitlab' | 'asana';\n\nexport interface IntegrationToken {\n id: string;\n userId: string;\n provider: IntegrationProvider;\n tokenType: string;\n expiresAt: string | null;\n scopes: string[] | null;\n providerUserId: string | null;\n providerEmail: string | null;\n metadata: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface IntegrationStatus {\n provider: IntegrationProvider;\n connected: boolean;\n connectedAt?: string;\n providerEmail?: string;\n scopes?: string[];\n metadata?: Record<string, unknown>;\n}\n\n// MCP Tool Types\nexport type McpToolName =\n | 'synthesize_requirements'\n | 'review_prd'\n | 'explode_backlog'\n | 'generate_architecture'\n | 'sync_to_tracker'\n | 'infer_prd_from_code'\n | 'reverse_engineer_user_flows'\n | 'generate_test_specs'\n | 'explain_codebase'\n | 'validate_implementation'\n | 'suggest_refactors'\n | 'generate_api_docs'\n | 'dependency_audit'\n | 'estimate_migration';\n\nexport interface McpToolResult<T = unknown> {\n content: Array<{\n type: 'text' | 'image' | 'resource';\n text?: string;\n data?: string;\n mimeType?: string;\n }>;\n data?: T;\n tokensUsed?: number;\n isError?: boolean;\n}\n\n// API Response Types\nexport interface ApiSuccessResponse<T> {\n success: true;\n data: T;\n meta?: {\n requestId: string;\n timestamp: string;\n };\n}\n\nexport interface ApiErrorResponse {\n success: false;\n error: {\n code: string;\n message: string;\n details?: Record<string, unknown>;\n docUrl?: string;\n };\n meta?: {\n requestId: string;\n timestamp: string;\n };\n}\n\nexport type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;\n","export interface ErrorDefinition {\n code: string;\n message: string;\n httpStatus: number;\n}\n\nexport const ErrorCodes = {\n // Authentication Errors (VPM-AUTH-XXX)\n AUTH: {\n REQUIRED: {\n code: 'VPM-AUTH-001',\n message: 'Authentication required',\n httpStatus: 401,\n },\n INVALID_TOKEN: {\n code: 'VPM-AUTH-002',\n message: 'Invalid or expired authentication token',\n httpStatus: 401,\n },\n INSUFFICIENT_PERMISSIONS: {\n code: 'VPM-AUTH-003',\n message: 'Insufficient permissions for this action',\n httpStatus: 403,\n },\n SESSION_EXPIRED: {\n code: 'VPM-AUTH-004',\n message: 'Session has expired, please login again',\n httpStatus: 401,\n },\n MFA_REQUIRED: {\n code: 'VPM-AUTH-005',\n message: 'Multi-factor authentication required',\n httpStatus: 403,\n },\n },\n\n // API Key Errors (VPM-API-KEY-XXX)\n API_KEY: {\n REQUIRED: {\n code: 'VPM-API-KEY-001',\n message: 'API key is required',\n httpStatus: 401,\n },\n INVALID: {\n code: 'VPM-API-KEY-002',\n message: 'Invalid API key',\n httpStatus: 401,\n },\n REVOKED: {\n code: 'VPM-API-KEY-003',\n message: 'API key has been revoked',\n httpStatus: 401,\n },\n EXPIRED: {\n code: 'VPM-API-KEY-004',\n message: 'API key has expired',\n httpStatus: 401,\n },\n RATE_LIMITED: {\n code: 'VPM-API-KEY-005',\n message: 'API key rate limit exceeded',\n httpStatus: 429,\n },\n QUOTA_EXCEEDED: {\n code: 'VPM-API-KEY-006',\n message: 'Monthly usage quota exceeded',\n httpStatus: 429,\n },\n },\n\n // MCP Tool Errors (VPM-MCP-XXX)\n MCP: {\n TOOL_NOT_FOUND: {\n code: 'VPM-MCP-001',\n message: 'Requested tool not found',\n httpStatus: 404,\n },\n INVALID_ARGUMENTS: {\n code: 'VPM-MCP-002',\n message: 'Invalid tool arguments provided',\n httpStatus: 400,\n },\n EXECUTION_FAILED: {\n code: 'VPM-MCP-003',\n message: 'Tool execution failed',\n httpStatus: 500,\n },\n TIMEOUT: {\n code: 'VPM-MCP-004',\n message: 'Tool execution timed out',\n httpStatus: 504,\n },\n LLM_ERROR: {\n code: 'VPM-MCP-005',\n message: 'AI model returned an error',\n httpStatus: 502,\n },\n CONTEXT_TOO_LARGE: {\n code: 'VPM-MCP-006',\n message: 'Input context exceeds maximum size',\n httpStatus: 413,\n },\n },\n\n // Billing Errors (VPM-BILLING-XXX)\n BILLING: {\n NO_SUBSCRIPTION: {\n code: 'VPM-BILLING-001',\n message: 'No active subscription found',\n httpStatus: 402,\n },\n SUBSCRIPTION_EXPIRED: {\n code: 'VPM-BILLING-002',\n message: 'Subscription has expired',\n httpStatus: 402,\n },\n PAYMENT_FAILED: {\n code: 'VPM-BILLING-003',\n message: 'Payment processing failed',\n httpStatus: 402,\n },\n FEATURE_NOT_INCLUDED: {\n code: 'VPM-BILLING-004',\n message: 'Feature not included in current plan',\n httpStatus: 403,\n },\n UPGRADE_REQUIRED: {\n code: 'VPM-BILLING-005',\n message: 'Plan upgrade required for this action',\n httpStatus: 403,\n },\n },\n\n // Integration Errors (VPM-INT-XXX)\n INTEGRATION: {\n NOT_CONNECTED: {\n code: 'VPM-INT-001',\n message: 'Integration not connected',\n httpStatus: 400,\n },\n TOKEN_EXPIRED: {\n code: 'VPM-INT-002',\n message: 'Integration token expired, reconnection required',\n httpStatus: 401,\n },\n REFRESH_FAILED: {\n code: 'VPM-INT-003',\n message: 'Failed to refresh integration token',\n httpStatus: 502,\n },\n API_ERROR: {\n code: 'VPM-INT-004',\n message: 'External integration API error',\n httpStatus: 502,\n },\n RATE_LIMITED: {\n code: 'VPM-INT-005',\n message: 'Integration rate limit exceeded',\n httpStatus: 429,\n },\n },\n\n // Validation Errors (VPM-VAL-XXX)\n VALIDATION: {\n REQUIRED_FIELD: {\n code: 'VPM-VAL-001',\n message: 'Required field missing',\n httpStatus: 400,\n },\n INVALID_FORMAT: {\n code: 'VPM-VAL-002',\n message: 'Invalid field format',\n httpStatus: 400,\n },\n OUT_OF_RANGE: {\n code: 'VPM-VAL-003',\n message: 'Value out of allowed range',\n httpStatus: 400,\n },\n INVALID_JSON: {\n code: 'VPM-VAL-004',\n message: 'Invalid JSON in request body',\n httpStatus: 400,\n },\n },\n\n // System Errors (VPM-SYS-XXX)\n SYSTEM: {\n INTERNAL_ERROR: {\n code: 'VPM-SYS-001',\n message: 'Internal server error',\n httpStatus: 500,\n },\n DATABASE_ERROR: {\n code: 'VPM-SYS-002',\n message: 'Database operation failed',\n httpStatus: 500,\n },\n SERVICE_UNAVAILABLE: {\n code: 'VPM-SYS-003',\n message: 'Service temporarily unavailable',\n httpStatus: 503,\n },\n MAINTENANCE_MODE: {\n code: 'VPM-SYS-004',\n message: 'System is under maintenance',\n httpStatus: 503,\n },\n },\n} as const;\n\nexport type ErrorCodeType =\n (typeof ErrorCodes)[keyof typeof ErrorCodes][keyof (typeof ErrorCodes)[keyof typeof ErrorCodes]];\n","import type { ErrorDefinition } from './codes.js';\nimport { ErrorCodes } from './codes.js';\n\nexport interface VasperaError {\n code: string;\n message: string;\n details?: Record<string, unknown>;\n timestamp: string;\n requestId: string;\n docUrl?: string;\n}\n\nexport interface ErrorResponse {\n success: false;\n error: VasperaError;\n}\n\nfunction generateRequestId(): string {\n return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;\n}\n\nexport function createError(\n errorDef: ErrorDefinition,\n details?: Record<string, unknown>,\n requestId?: string\n): VasperaError {\n return {\n code: errorDef.code,\n message: errorDef.message,\n details,\n timestamp: new Date().toISOString(),\n requestId: requestId || generateRequestId(),\n docUrl: `https://docs.vaspera.pm/errors/${errorDef.code}`,\n };\n}\n\nexport function createErrorResponse(\n errorDef: ErrorDefinition,\n details?: Record<string, unknown>,\n requestId?: string\n): { response: ErrorResponse; status: number } {\n return {\n response: {\n success: false,\n error: createError(errorDef, details, requestId),\n },\n status: errorDef.httpStatus,\n };\n}\n\nfunction getNextResetDate(): string {\n const now = new Date();\n const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);\n return nextMonth.toISOString();\n}\n\n// Pre-built error creators for common cases\nexport const Errors = {\n authRequired: (requestId?: string) =>\n createErrorResponse(ErrorCodes.AUTH.REQUIRED, undefined, requestId),\n\n invalidApiKey: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.INVALID, undefined, requestId),\n\n apiKeyRevoked: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.REVOKED, undefined, requestId),\n\n apiKeyExpired: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.EXPIRED, undefined, requestId),\n\n rateLimited: (retryAfter: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.API_KEY.RATE_LIMITED,\n { retryAfterSeconds: retryAfter },\n requestId\n ),\n\n quotaExceeded: (currentUsage: number, limit: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.API_KEY.QUOTA_EXCEEDED,\n { currentUsage, limit, resetDate: getNextResetDate() },\n requestId\n ),\n\n toolNotFound: (toolName: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.TOOL_NOT_FOUND,\n { tool: toolName },\n requestId\n ),\n\n toolExecutionFailed: (toolName: string, reason: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.EXECUTION_FAILED,\n { tool: toolName, reason },\n requestId\n ),\n\n toolTimeout: (toolName: string, timeoutMs: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.TIMEOUT,\n { tool: toolName, timeoutMs },\n requestId\n ),\n\n validationFailed: (field: string, reason: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.VALIDATION.REQUIRED_FIELD,\n { field, reason },\n requestId\n ),\n\n invalidFormat: (field: string, expected: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.VALIDATION.INVALID_FORMAT,\n { field, expected },\n requestId\n ),\n\n internalError: (message?: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.SYSTEM.INTERNAL_ERROR,\n message ? { message } : undefined,\n requestId\n ),\n\n databaseError: (operation: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.SYSTEM.DATABASE_ERROR,\n { operation },\n requestId\n ),\n\n integrationNotConnected: (provider: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.INTEGRATION.NOT_CONNECTED,\n { provider },\n requestId\n ),\n\n subscriptionRequired: (feature: string, requiredTier: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.BILLING.FEATURE_NOT_INCLUDED,\n { feature, requiredTier },\n requestId\n ),\n};\n\n// Custom error class for throwing in API routes\nexport class VasperaApiError extends Error {\n public readonly errorDef: ErrorDefinition;\n public readonly details?: Record<string, unknown>;\n\n constructor(errorDef: ErrorDefinition, details?: Record<string, unknown>) {\n super(errorDef.message);\n this.name = 'VasperaApiError';\n this.errorDef = errorDef;\n this.details = details;\n }\n\n toResponse(requestId?: string): { response: ErrorResponse; status: number } {\n return createErrorResponse(this.errorDef, this.details, requestId);\n }\n}\n","import { randomBytes, createHash } from 'crypto';\n\nconst API_KEY_PREFIX_LIVE = 'vpm_live_';\nconst API_KEY_PREFIX_TEST = 'vpm_test_';\nconst API_KEY_RANDOM_BYTES = 24; // 32 chars in base64url\n\nexport interface GeneratedApiKey {\n key: string; // Full key to show user once\n keyPrefix: string; // First 16 chars for identification\n keyHash: string; // Hash for storage and validation\n}\n\n/**\n * Generate a new API key\n * @param environment - 'live' or 'test'\n * @returns Generated key with prefix and hash\n */\nexport function generateApiKey(environment: 'live' | 'test' = 'live'): GeneratedApiKey {\n const prefix = environment === 'live' ? API_KEY_PREFIX_LIVE : API_KEY_PREFIX_TEST;\n const randomPart = randomBytes(API_KEY_RANDOM_BYTES)\n .toString('base64url')\n .slice(0, 32);\n\n const key = `${prefix}${randomPart}`;\n const keyPrefix = key.slice(0, 16);\n const keyHash = hashApiKey(key);\n\n return {\n key,\n keyPrefix,\n keyHash,\n };\n}\n\n/**\n * Hash an API key for storage\n * Using SHA-256 since we need to look up by hash\n */\nexport function hashApiKey(key: string): string {\n return createHash('sha256').update(key).digest('hex');\n}\n\n/**\n * Validate API key format\n */\nexport function isValidApiKeyFormat(key: string): boolean {\n const livePattern = /^vpm_live_[a-zA-Z0-9_-]{32}$/;\n const testPattern = /^vpm_test_[a-zA-Z0-9_-]{32}$/;\n return livePattern.test(key) || testPattern.test(key);\n}\n\n/**\n * Extract prefix from API key\n */\nexport function extractKeyPrefix(key: string): string {\n return key.slice(0, 16);\n}\n\n/**\n * Check if key is a test key\n */\nexport function isTestKey(key: string): boolean {\n return key.startsWith(API_KEY_PREFIX_TEST);\n}\n\n/**\n * Mask an API key for display (show first 16 and last 4 chars)\n */\nexport function maskApiKey(key: string): string {\n if (key.length < 24) return key;\n return `${key.slice(0, 16)}...${key.slice(-4)}`;\n}\n","import {\n createCipheriv,\n createDecipheriv,\n randomBytes,\n scrypt,\n} from 'crypto';\nimport { promisify } from 'util';\n\nconst scryptAsync = promisify(scrypt);\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 16;\nconst SALT_LENGTH = 32;\nconst TAG_LENGTH = 16;\nconst KEY_LENGTH = 32;\n\n/**\n * Encrypt text using AES-256-GCM\n * @param text - Plain text to encrypt\n * @param secret - Encryption secret\n * @returns Encrypted string in format: salt:iv:tag:ciphertext (all base64)\n */\nexport async function encrypt(text: string, secret: string): Promise<string> {\n const salt = randomBytes(SALT_LENGTH);\n const iv = randomBytes(IV_LENGTH);\n const key = (await scryptAsync(secret, salt, KEY_LENGTH)) as Buffer;\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([\n cipher.update(text, 'utf8'),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n\n // Format: salt:iv:tag:encrypted (all base64)\n return [\n salt.toString('base64'),\n iv.toString('base64'),\n tag.toString('base64'),\n encrypted.toString('base64'),\n ].join(':');\n}\n\n/**\n * Decrypt text that was encrypted with encrypt()\n * @param encryptedText - Encrypted string in format: salt:iv:tag:ciphertext\n * @param secret - Encryption secret (must match encryption)\n * @returns Decrypted plain text\n */\nexport async function decrypt(\n encryptedText: string,\n secret: string\n): Promise<string> {\n const parts = encryptedText.split(':');\n if (parts.length !== 4) {\n throw new Error('Invalid encrypted text format');\n }\n\n const [saltB64, ivB64, tagB64, encryptedB64] = parts;\n\n const salt = Buffer.from(saltB64!, 'base64');\n const iv = Buffer.from(ivB64!, 'base64');\n const tag = Buffer.from(tagB64!, 'base64');\n const encrypted = Buffer.from(encryptedB64!, 'base64');\n\n const key = (await scryptAsync(secret, salt, KEY_LENGTH)) as Buffer;\n\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(tag);\n\n return decipher.update(encrypted) + decipher.final('utf8');\n}\n\n/**\n * Generate a random encryption secret\n * @returns 32-byte hex string suitable for use as encryption secret\n */\nexport function generateEncryptionSecret(): string {\n return randomBytes(32).toString('hex');\n}\n"],"mappings":"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","names":["randomBytes","randomBytes"]}
@@ -0,0 +1,122 @@
1
+ type SubscriptionTier = 'free' | 'starter' | 'pro' | 'enterprise';
2
+ type SubscriptionStatus = 'active' | 'canceled' | 'past_due' | 'trialing' | 'incomplete';
3
+ interface UserProfile {
4
+ id: string;
5
+ email: string;
6
+ fullName: string | null;
7
+ avatarUrl: string | null;
8
+ subscriptionTier: SubscriptionTier;
9
+ subscriptionStatus: SubscriptionStatus;
10
+ stripeCustomerId: string | null;
11
+ stripeSubscriptionId: string | null;
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ }
15
+ interface ApiKey {
16
+ id: string;
17
+ userId: string;
18
+ name: string;
19
+ keyPrefix: string;
20
+ lastUsedAt: string | null;
21
+ expiresAt: string | null;
22
+ revokedAt: string | null;
23
+ createdAt: string;
24
+ }
25
+ interface ApiKeyWithSecret extends Omit<ApiKey, 'revokedAt'> {
26
+ key: string;
27
+ }
28
+ interface UsageEvent {
29
+ id: string;
30
+ userId: string;
31
+ apiKeyId: string | null;
32
+ toolName: string;
33
+ tokensUsed: number;
34
+ latencyMs: number | null;
35
+ success: boolean;
36
+ errorCode: string | null;
37
+ requestId: string | null;
38
+ metadata: Record<string, unknown>;
39
+ createdAt: string;
40
+ }
41
+ interface UsageSummary {
42
+ period: {
43
+ start: string;
44
+ end: string;
45
+ };
46
+ summary: {
47
+ totalToolCalls: number;
48
+ totalTokensUsed: number;
49
+ quotaUsed: number;
50
+ quotaLimit: number;
51
+ quotaPercentage: number;
52
+ };
53
+ byTool: Array<{
54
+ tool: string;
55
+ calls: number;
56
+ tokensUsed: number;
57
+ avgLatencyMs: number;
58
+ }>;
59
+ }
60
+ declare const QUOTA_LIMITS: Record<SubscriptionTier, number>;
61
+ declare const RATE_LIMITS: Record<SubscriptionTier, {
62
+ perMinute: number;
63
+ perDay: number;
64
+ }>;
65
+ type IntegrationProvider = 'jira' | 'linear' | 'github' | 'gitlab' | 'asana';
66
+ interface IntegrationToken {
67
+ id: string;
68
+ userId: string;
69
+ provider: IntegrationProvider;
70
+ tokenType: string;
71
+ expiresAt: string | null;
72
+ scopes: string[] | null;
73
+ providerUserId: string | null;
74
+ providerEmail: string | null;
75
+ metadata: Record<string, unknown>;
76
+ createdAt: string;
77
+ updatedAt: string;
78
+ }
79
+ interface IntegrationStatus {
80
+ provider: IntegrationProvider;
81
+ connected: boolean;
82
+ connectedAt?: string;
83
+ providerEmail?: string;
84
+ scopes?: string[];
85
+ metadata?: Record<string, unknown>;
86
+ }
87
+ type McpToolName = 'synthesize_requirements' | 'review_prd' | 'explode_backlog' | 'generate_architecture' | 'sync_to_tracker' | 'infer_prd_from_code' | 'reverse_engineer_user_flows' | 'generate_test_specs' | 'explain_codebase' | 'validate_implementation' | 'suggest_refactors' | 'generate_api_docs' | 'dependency_audit' | 'estimate_migration';
88
+ interface McpToolResult<T = unknown> {
89
+ content: Array<{
90
+ type: 'text' | 'image' | 'resource';
91
+ text?: string;
92
+ data?: string;
93
+ mimeType?: string;
94
+ }>;
95
+ data?: T;
96
+ tokensUsed?: number;
97
+ isError?: boolean;
98
+ }
99
+ interface ApiSuccessResponse<T> {
100
+ success: true;
101
+ data: T;
102
+ meta?: {
103
+ requestId: string;
104
+ timestamp: string;
105
+ };
106
+ }
107
+ interface ApiErrorResponse {
108
+ success: false;
109
+ error: {
110
+ code: string;
111
+ message: string;
112
+ details?: Record<string, unknown>;
113
+ docUrl?: string;
114
+ };
115
+ meta?: {
116
+ requestId: string;
117
+ timestamp: string;
118
+ };
119
+ }
120
+ type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
121
+
122
+ export { type ApiErrorResponse, type ApiKey, type ApiKeyWithSecret, type ApiResponse, type ApiSuccessResponse, type IntegrationProvider, type IntegrationStatus, type IntegrationToken, type McpToolName, type McpToolResult, QUOTA_LIMITS, RATE_LIMITS, type SubscriptionStatus, type SubscriptionTier, type UsageEvent, type UsageSummary, type UserProfile };