@l4yercak3/cli 1.1.12 → 1.2.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.
@@ -0,0 +1,185 @@
1
+ /**
2
+ * ONTOLOGY SCHEMAS
3
+ *
4
+ * Universal object storage system for ALL data types.
5
+ * This replaces separate tables with a unified object model.
6
+ *
7
+ * Core Concepts:
8
+ * - objects: Universal storage for any entity (translations, events, contacts, etc.)
9
+ * - objectLinks: Relationships between objects (like graph edges)
10
+ * - objectActions: Audit trail of actions performed on objects
11
+ */
12
+
13
+ import { defineTable } from "convex/server";
14
+ import { v } from "convex/values";
15
+
16
+ /**
17
+ * OBJECTS TABLE
18
+ * Universal storage for all entity types
19
+ *
20
+ * Examples:
21
+ * - type="translation", subtype="system", name="desktop.welcome"
22
+ * - type="event", subtype="podcast", name="Episode 42"
23
+ * - type="invoice", subtype="client", name="INV-2024-001"
24
+ * - type="contact", subtype="customer", name="John Doe"
25
+ * - type="transaction", subtype="ticket_purchase", name="Product - Customer"
26
+ *
27
+ * TRANSACTION OBJECT STRUCTURE:
28
+ * Transactions (type="transaction") store purchase data in customProperties.
29
+ *
30
+ * NEW STRUCTURE (v2 - Multi-line Item Transactions):
31
+ * One transaction per checkout with multiple line items:
32
+ * {
33
+ * checkoutSessionId: Id<"objects">,
34
+ * lineItems: [
35
+ * {
36
+ * productId: Id<"objects">,
37
+ * productName: string,
38
+ * productDescription?: string,
39
+ * quantity: number,
40
+ * unitPriceInCents: number, // Net price per unit
41
+ * totalPriceInCents: number, // Net total for this line (unitPrice * quantity)
42
+ * taxRatePercent: number,
43
+ * taxAmountInCents: number,
44
+ * ticketId?: Id<"objects">, // If this is a ticket product
45
+ * eventId?: Id<"objects">,
46
+ * eventName?: string,
47
+ * },
48
+ * // ... more line items
49
+ * ],
50
+ * subtotalInCents: number, // Sum of all line item totals (net)
51
+ * taxAmountInCents: number, // Sum of all line item taxes
52
+ * totalInCents: number, // Grand total (subtotal + tax)
53
+ * currency: string,
54
+ * customerName: string,
55
+ * customerEmail: string,
56
+ * payerType: "individual" | "organization",
57
+ * paymentMethod: string,
58
+ * paymentStatus: string,
59
+ * invoicingStatus: "pending" | "on_draft_invoice" | "invoiced",
60
+ * // ... other customer/payer fields
61
+ * }
62
+ *
63
+ * LEGACY STRUCTURE (v1 - Single Product Per Transaction):
64
+ * DEPRECATED: Old approach created one transaction per product.
65
+ * Kept for backward compatibility with existing data.
66
+ * {
67
+ * productId: Id<"objects">,
68
+ * productName: string,
69
+ * quantity: number,
70
+ * amountInCents: number, // Total for this ONE product
71
+ * currency: string,
72
+ * taxRatePercent: number,
73
+ * // ... single product fields
74
+ * }
75
+ */
76
+ export const objects = defineTable({
77
+ // Multi-tenancy
78
+ organizationId: v.id("organizations"),
79
+
80
+ // Object Identity
81
+ type: v.string(), // "translation", "event", "invoice", "contact", etc.
82
+ subtype: v.optional(v.string()), // "system", "app", "content", etc.
83
+
84
+ // Universal Properties (ALL objects have these)
85
+ name: v.string(), // Human-readable identifier
86
+ description: v.optional(v.string()),
87
+ status: v.string(), // Object-specific statuses
88
+
89
+ // Translation-Specific Fields (only used when type="translation")
90
+ locale: v.optional(v.string()), // "en", "de", "pl"
91
+ value: v.optional(v.string()), // Translation text
92
+
93
+ // Gravel Road - Per-Org/Per-Type Customizations
94
+ // This allows each organization to add their own fields without schema changes
95
+ customProperties: v.optional(v.record(v.string(), v.any())),
96
+
97
+ // Metadata
98
+ // createdBy can be either platform user (staff) or frontend_user (customer)
99
+ // - Platform user (Id<"users">): When staff creates records administratively
100
+ // - Frontend user (Id<"objects">): When customers create records (guest registration, checkout)
101
+ createdBy: v.optional(v.union(v.id("users"), v.id("objects"))),
102
+ createdAt: v.number(),
103
+ updatedAt: v.number(),
104
+ })
105
+ // Core indexes for fast queries
106
+ .index("by_org", ["organizationId"])
107
+ .index("by_org_type", ["organizationId", "type"])
108
+ .index("by_org_type_subtype", ["organizationId", "type", "subtype"])
109
+ .index("by_type", ["type"])
110
+ .index("by_type_subtype", ["type", "subtype"])
111
+ .index("by_status", ["status"])
112
+
113
+ // Translation-specific indexes
114
+ .index("by_org_type_locale", ["organizationId", "type", "locale"])
115
+ .index("by_org_type_locale_name", ["organizationId", "type", "locale", "name"])
116
+ .index("by_type_locale", ["type", "locale"])
117
+
118
+ // Search indexes
119
+ .searchIndex("search_by_name", { searchField: "name" })
120
+ .searchIndex("search_by_value", { searchField: "value" });
121
+
122
+ /**
123
+ * OBJECT LINKS TABLE
124
+ * Relationships between objects (graph edges)
125
+ *
126
+ * Examples:
127
+ * - fromObjectId=translation1, toObjectId=event1, linkType="translates"
128
+ * - fromObjectId=user1, toObjectId=org1, linkType="member_of"
129
+ * - fromObjectId=org1, toObjectId=address1, linkType="has_address"
130
+ * - fromObjectId=contact1, toObjectId=event1, linkType="registers_for"
131
+ */
132
+ export const objectLinks = defineTable({
133
+ organizationId: v.id("organizations"),
134
+
135
+ // Link Endpoints
136
+ fromObjectId: v.id("objects"),
137
+ toObjectId: v.id("objects"),
138
+
139
+ // Link Type (the "verb" in the relationship)
140
+ linkType: v.string(), // "translates", "member_of", "has_address", "registers_for", etc.
141
+
142
+ // Link-Specific Data
143
+ // Store additional metadata about the relationship
144
+ properties: v.optional(v.record(v.string(), v.any())),
145
+
146
+ // Metadata
147
+ // createdBy can be platform user or frontend_user (same as objects table)
148
+ createdBy: v.optional(v.union(v.id("users"), v.id("objects"))),
149
+ createdAt: v.number(),
150
+ })
151
+ .index("by_from_object", ["fromObjectId"])
152
+ .index("by_to_object", ["toObjectId"])
153
+ .index("by_org_link_type", ["organizationId", "linkType"])
154
+ .index("by_from_link_type", ["fromObjectId", "linkType"])
155
+ .index("by_to_link_type", ["toObjectId", "linkType"]);
156
+
157
+ /**
158
+ * OBJECT ACTIONS TABLE
159
+ * Audit trail of actions performed on objects
160
+ *
161
+ * Examples:
162
+ * - objectId=translation1, actionType="approve", performedBy=user1
163
+ * - objectId=event1, actionType="publish", performedBy=user2
164
+ * - objectId=invoice1, actionType="send", performedBy=user3
165
+ */
166
+ export const objectActions = defineTable({
167
+ organizationId: v.id("organizations"),
168
+ objectId: v.id("objects"),
169
+
170
+ // Action Identity
171
+ actionType: v.string(), // "approve", "translate", "publish", "view", "edit", etc.
172
+
173
+ // Action Data
174
+ // Store details about what changed
175
+ actionData: v.optional(v.record(v.string(), v.any())),
176
+
177
+ // Who & When
178
+ // performedBy can be platform user or frontend_user (same as objects table)
179
+ performedBy: v.optional(v.union(v.id("users"), v.id("objects"))),
180
+ performedAt: v.number(),
181
+ })
182
+ .index("by_object", ["objectId"])
183
+ .index("by_org_action_type", ["organizationId", "actionType"])
184
+ .index("by_performer", ["performedBy"])
185
+ .index("by_performed_at", ["performedAt"]);
@@ -0,0 +1,250 @@
1
+ /**
2
+ * ORGANIZED SCHEMA STRUCTURE
3
+ *
4
+ * This schema is composed from modular definitions in the schemas/ directory.
5
+ * See schemas/README.md for documentation on adding new apps.
6
+ *
7
+ * Structure:
8
+ * 1. Core platform (users, organizations, memberships)
9
+ * 2. App Store (apps registry, installations, purchases)
10
+ * 3. Individual Apps (l4yercak3pod, etc. - each is self-contained)
11
+ * 4. Utilities (audit logs)
12
+ */
13
+
14
+ import { defineSchema } from "convex/server";
15
+
16
+ // Import modular schema definitions
17
+ import {
18
+ users,
19
+ organizations,
20
+ organizationMembers,
21
+ userPasswords,
22
+ sessions,
23
+ frontendSessions,
24
+ passkeys,
25
+ passkeysChallenges,
26
+ apiKeys,
27
+ roles,
28
+ permissions,
29
+ rolePermissions,
30
+ userPreferences,
31
+ organizationMedia,
32
+ oauthConnections,
33
+ oauthStates,
34
+ cliSessions,
35
+ cliLoginStates,
36
+ oauthSignupStates,
37
+ webhookSubscriptions
38
+ } from "./schemas/coreSchemas";
39
+ // NOTE: apiKeyDomains table removed - now using unified domain configurations in objects table
40
+ import { apps, appInstallations, snapshots, snapshotLoads, purchases, appAvailabilities } from "./schemas/appStoreSchemas";
41
+ // import { app_podcasting } from "./schemas/appDataSchemas"; // Not yet used
42
+ import { auditLogs, workflowExecutionLogs } from "./schemas/utilitySchemas";
43
+
44
+ // ✅ NEW ONTOLOGY SCHEMAS
45
+ import { objects, objectLinks, objectActions } from "./schemas/ontologySchemas";
46
+
47
+ // 🤖 AI INTEGRATION SCHEMAS
48
+ import {
49
+ aiConversations,
50
+ aiMessages,
51
+ aiToolExecutions,
52
+ organizationAiSettings,
53
+ aiAgentTasks,
54
+ aiAgentMemory,
55
+ aiModels,
56
+ aiWorkItems
57
+ } from "./schemas/aiSchemas";
58
+
59
+ // 💳 AI BILLING SCHEMAS v3.1 (VAT-inclusive pricing, EUR only)
60
+ import {
61
+ aiUsage,
62
+ aiSubscriptions,
63
+ aiTokenBalance,
64
+ aiTokenPurchases,
65
+ aiBudgetAlerts,
66
+ aiBillingEvents
67
+ } from "./schemas/aiBillingSchemas";
68
+
69
+ // 👤 USER QUOTA SCHEMAS (Phase 1: Foundation for per-user limits)
70
+ import { userAIQuotas } from "./schemas/userQuotaSchemas";
71
+
72
+ // 💾 STORAGE TRACKING SCHEMAS (Organization + per-user storage)
73
+ import { organizationStorage, userStorageQuotas } from "./schemas/storageSchemas";
74
+
75
+ // 📧 CONTACT SYNC & BULK EMAIL SCHEMAS (AI-powered external contact integration)
76
+ import { contactSyncs, emailCampaigns } from "./schemas/contactSyncSchemas";
77
+
78
+ // 🔐 OAUTH 2.0 SCHEMAS (OAuth authentication for third-party integrations)
79
+ import {
80
+ oauthApplications,
81
+ oauthAuthorizationCodes,
82
+ oauthRefreshTokens,
83
+ oauthRevokedTokens,
84
+ oauthTokenUsage
85
+ } from "./schemas/oauthSchemas";
86
+
87
+ // 🚦 RATE LIMITING SCHEMAS (Token bucket rate limiting for API abuse prevention)
88
+ import { rateLimitSchemas } from "./schemas/rateLimitSchemas";
89
+
90
+ // 🛡️ SECURITY SCHEMAS (Anomaly detection and security event monitoring)
91
+ import { securitySchemas } from "./schemas/securitySchemas";
92
+
93
+ // 📊 GROWTH TRACKING SCHEMAS (Launch metrics and KPI tracking)
94
+ import {
95
+ dailyGrowthMetrics,
96
+ signupEvents,
97
+ weeklyScorecard,
98
+ salesNotifications,
99
+ celebrationMilestones
100
+ } from "./schemas/growthTrackingSchemas";
101
+
102
+ // 📧 EMAIL QUEUE SCHEMA (Email delivery tracking)
103
+ import { emailQueue } from "./schemas/emailQueueSchemas";
104
+
105
+ // 🎁 BENEFITS PLATFORM SCHEMAS (Benefits & Commissions tracking)
106
+ import {
107
+ benefitClaims,
108
+ commissionPayouts,
109
+ memberWallets,
110
+ platformFees
111
+ } from "./schemas/benefitsSchemas";
112
+
113
+ /**
114
+ * MAIN SCHEMA EXPORT
115
+ *
116
+ * All tables are defined in their respective schema modules.
117
+ * This file simply composes them together.
118
+ */
119
+ export default defineSchema({
120
+ // 👥 CORE: Platform foundation
121
+ users,
122
+ organizations,
123
+ organizationMembers,
124
+ userPasswords,
125
+ sessions,
126
+ frontendSessions, // Customer user sessions (separate from platform staff)
127
+ passkeys,
128
+ passkeysChallenges,
129
+ apiKeys,
130
+ // Domain configurations (including API access, email, branding) stored in objects table
131
+ userPreferences,
132
+ organizationMedia,
133
+ oauthConnections,
134
+ oauthStates,
135
+ cliSessions,
136
+ cliLoginStates,
137
+ oauthSignupStates,
138
+ webhookSubscriptions,
139
+
140
+ // 🔐 RBAC: Role-Based Access Control
141
+ roles,
142
+ permissions,
143
+ rolePermissions,
144
+
145
+ // 🏪 APP STORE: Marketplace functionality
146
+ apps,
147
+ appInstallations,
148
+ appAvailabilities,
149
+ snapshots,
150
+ snapshotLoads,
151
+ purchases,
152
+
153
+ // 📱 APPS: Individual app data tables
154
+ // Each app is self-contained with its own table
155
+ // All apps follow the appSchemaBase pattern (see schemas/appSchemaBase.ts)
156
+ // NAMING: Always prefix with "app_"
157
+ // app_podcasting, // Podcasting App
158
+ // Add more apps here as they're created:
159
+ // app_analytics,
160
+ // app_subscribers,
161
+ // app_calendar,
162
+
163
+ // 🛠️ UTILITIES: Supporting functionality
164
+ auditLogs,
165
+ workflowExecutionLogs,
166
+
167
+ // 🥷 ONTOLOGY: Universal object system
168
+ objects, // Universal storage for all entity types
169
+ objectLinks, // Relationships between objects
170
+ objectActions, // Audit trail of actions
171
+
172
+ // 🤖 AI INTEGRATION: General AI Assistant + Email AI Specialist
173
+ aiConversations, // Chat history for general AI assistant
174
+ aiMessages, // Individual messages in conversations
175
+ aiToolExecutions, // Audit trail of tool executions
176
+ // NOTE: Tool drafts use objects table with status="draft" + AI metadata in customProperties
177
+ organizationAiSettings, // AI configuration per organization (LLM + embeddings)
178
+ aiModels, // AI model discovery cache (auto-refreshed daily)
179
+ aiWorkItems, // Work items for human-in-the-loop approval workflow
180
+ aiAgentTasks, // Email AI tasks with approval workflow
181
+ aiAgentMemory, // Email templates and preferences with vector search
182
+
183
+ // 💳 AI BILLING v3.1: Three-tier system (€49 or €2,500-€12,000/mo, VAT incl.)
184
+ aiUsage, // Track AI API usage for billing and monitoring (with privacy audit)
185
+ aiSubscriptions, // Stripe subscriptions for AI features (tier-based + sub-tiers)
186
+ aiTokenBalance, // Purchased token balance (Standard/Privacy-Enhanced only)
187
+ aiTokenPurchases, // Token pack purchase history (with VAT breakdown)
188
+ aiBudgetAlerts, // Budget alert history and acknowledgments
189
+ aiBillingEvents, // Audit log for billing operations
190
+
191
+ // 👤 USER QUOTAS: Per-user limits foundation (Phase 1: tracking only, Phase 4: enforcement)
192
+ userAIQuotas, // Per-user monthly AI token limits
193
+
194
+ // 💾 STORAGE TRACKING: Organization + per-user storage metrics
195
+ organizationStorage, // Aggregated storage per organization
196
+ userStorageQuotas, // Per-user storage limits (Phase 1: tracking only)
197
+
198
+ // 📧 CONTACT SYNC & BULK EMAIL: AI-powered external contact integration
199
+ contactSyncs, // Audit trail for contact synchronization (Microsoft/Google → CRM)
200
+ emailCampaigns, // Bulk email campaigns to CRM contacts/organizations
201
+
202
+ // 🔐 OAUTH 2.0: Third-party authentication and authorization
203
+ oauthApplications, // OAuth apps registered by organizations (Zapier, Make, etc.)
204
+ oauthAuthorizationCodes, // Temporary authorization codes (10 min lifetime)
205
+ oauthRefreshTokens, // Long-lived refresh tokens (30 days)
206
+ oauthRevokedTokens, // Revocation list for access tokens
207
+ oauthTokenUsage, // Token usage analytics (optional, for monitoring)
208
+
209
+ // 🚦 RATE LIMITING: Token bucket rate limiting for API abuse prevention
210
+ ...rateLimitSchemas, // rateLimitBuckets, rateLimitViolations
211
+
212
+ // 🛡️ SECURITY: Anomaly detection and security event monitoring
213
+ ...securitySchemas, // securityEvents, usageMetadata, failedAuthAttempts
214
+
215
+ // 📊 GROWTH TRACKING: Launch metrics and KPI tracking
216
+ dailyGrowthMetrics, // Daily metrics (automated + manual)
217
+ signupEvents, // Signup event tracking
218
+ weeklyScorecard, // Weekly scorecard snapshots
219
+ salesNotifications, // Sales team notifications
220
+ celebrationMilestones, // Milestone achievements
221
+
222
+ // 📧 EMAIL QUEUE: Email delivery tracking
223
+ emailQueue, // Outbound email queue
224
+
225
+ // 🎁 BENEFITS PLATFORM: Benefits & Commissions tracking
226
+ benefitClaims, // Benefit claim workflow tracking
227
+ commissionPayouts, // Commission payout workflow tracking
228
+ memberWallets, // Crypto wallet links for members
229
+ platformFees, // Platform fee tracking for billing
230
+
231
+ // ❌ OLD TRANSLATIONS - Replaced by ontology
232
+ // systemTranslations,
233
+ // appTranslations,
234
+ // contentTranslations,
235
+ // translationNamespaces,
236
+ // translationKeys,
237
+ // supportedLocales,
238
+ });
239
+
240
+ /**
241
+ * ADDING A NEW APP?
242
+ *
243
+ * 1. Define your table in schemas/appDataSchemas.ts
244
+ * 2. Import it above: import { yourapp } from "./schemas/appDataSchemas";
245
+ * 3. Add it to the schema export under "APPS" section
246
+ * 4. Register it in convex/apps.ts DEFAULT_APPS
247
+ * 5. Create convex/yourapp.ts for queries/mutations
248
+ *
249
+ * See schemas/README.md for complete guide
250
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@l4yercak3/cli",
3
- "version": "1.1.12",
3
+ "version": "1.2.0",
4
4
  "description": "Icing on the L4yercak3 - The sweet finishing touch for your Layer Cake integration",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -37,11 +37,14 @@
37
37
  "cli",
38
38
  "backend",
39
39
  "platform",
40
- "integration"
40
+ "integration",
41
+ "mcp",
42
+ "claude-code"
41
43
  ],
42
44
  "author": "",
43
45
  "license": "MIT",
44
46
  "dependencies": {
47
+ "@modelcontextprotocol/sdk": "^1.0.0",
45
48
  "chalk": "^4.1.2",
46
49
  "commander": "^14.0.2",
47
50
  "figlet": "^1.7.0",
@@ -197,12 +197,6 @@ async function handleLogin() {
197
197
  configManager.saveSession(session);
198
198
 
199
199
  // Validate session with backend
200
- // Add delay to allow Convex to propagate the session (debugging timing issues)
201
- if (process.env.L4YERCAK3_DEBUG) {
202
- console.log('[DEBUG] Waiting 2s for session propagation...');
203
- }
204
- await new Promise(resolve => setTimeout(resolve, 2000));
205
-
206
200
  try {
207
201
  const userInfo = await backendClient.validateSession();
208
202
  if (userInfo) {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * MCP Server Command
3
+ *
4
+ * Starts the L4YERCAK3 MCP server for Claude Code integration.
5
+ * The server exposes L4YERCAK3 capabilities as MCP tools.
6
+ *
7
+ * Usage:
8
+ * l4yercak3 mcp-server
9
+ *
10
+ * To add to Claude Code:
11
+ * claude mcp add l4yercak3 -- npx l4yercak3 mcp-server
12
+ *
13
+ * @module commands/mcp-server
14
+ */
15
+
16
+ const { startServer } = require('../mcp/server');
17
+
18
+ module.exports = {
19
+ command: 'mcp-server',
20
+ description: 'Start the MCP server for Claude Code integration',
21
+ handler: async () => {
22
+ // Note: All output goes to stderr because stdout is used for MCP protocol
23
+ // console.error is used intentionally here
24
+
25
+ try {
26
+ await startServer();
27
+ } catch (error) {
28
+ console.error(`[L4YERCAK3 MCP] Failed to start server: ${error.message}`);
29
+ process.exit(1);
30
+ }
31
+ },
32
+ };
@@ -230,8 +230,61 @@ async function handleSpread() {
230
230
  console.log(chalk.gray(' • Delete an existing key at https://app.l4yercak3.com?openWindow=integrations&panel=api-keys'));
231
231
  console.log(chalk.gray(' • Upgrade your plan at https://app.l4yercak3.com?openWindow=store\n'));
232
232
  process.exit(0);
233
+ } else if (existingKeys && existingKeys.keys && existingKeys.keys.length > 0) {
234
+ // Has existing keys - offer to reuse or generate new
235
+ const activeKeys = existingKeys.keys.filter(k => k.status === 'active');
236
+
237
+ if (activeKeys.length > 0) {
238
+ console.log(chalk.gray(` Found ${activeKeys.length} active API key(s)\n`));
239
+
240
+ const keyChoices = activeKeys.map(key => ({
241
+ name: `${key.name} (${key.keyPreview})`,
242
+ value: key.id,
243
+ }));
244
+ keyChoices.push({ name: '➕ Generate a new API key', value: '__generate__' });
245
+
246
+ const { keyChoice } = await inquirer.prompt([
247
+ {
248
+ type: 'list',
249
+ name: 'keyChoice',
250
+ message: 'Which API key would you like to use?',
251
+ choices: keyChoices,
252
+ },
253
+ ]);
254
+
255
+ if (keyChoice === '__generate__') {
256
+ apiKey = await generateNewApiKey(organizationId);
257
+ } else {
258
+ // User selected existing key - we only have the preview, not the full key
259
+ // They need to use the key they have stored or get it from dashboard
260
+ const selectedKey = activeKeys.find(k => k.id === keyChoice);
261
+ console.log(chalk.yellow(`\n ⚠️ For security, we can't retrieve the full API key.`));
262
+ console.log(chalk.gray(` You selected: ${selectedKey.name} (${selectedKey.keyPreview})`));
263
+ console.log(chalk.gray(` If you have this key stored, enter it below.`));
264
+ console.log(chalk.gray(` Otherwise, generate a new key or find it at:`));
265
+ console.log(chalk.gray(` https://app.l4yercak3.com?openWindow=integrations&panel=api-keys\n`));
266
+
267
+ const { existingKey } = await inquirer.prompt([
268
+ {
269
+ type: 'input',
270
+ name: 'existingKey',
271
+ message: 'Enter your API key (or press Enter to generate new):',
272
+ },
273
+ ]);
274
+
275
+ if (existingKey.trim()) {
276
+ apiKey = existingKey.trim();
277
+ console.log(chalk.green(` ✅ Using existing API key\n`));
278
+ } else {
279
+ apiKey = await generateNewApiKey(organizationId);
280
+ }
281
+ }
282
+ } else {
283
+ // Only revoked keys exist - generate new
284
+ apiKey = await generateNewApiKey(organizationId);
285
+ }
233
286
  } else {
234
- // Generate new key (either no keys exist or can create more)
287
+ // No existing keys - generate one
235
288
  apiKey = await generateNewApiKey(organizationId);
236
289
  }
237
290
  } catch (error) {
@@ -0,0 +1,127 @@
1
+ /**
2
+ * MCP Server Authentication Layer
3
+ *
4
+ * Reads CLI session from ~/.l4yercak3/config.json and validates with backend.
5
+ * Provides auth context for tool execution.
6
+ *
7
+ * @module mcp/auth
8
+ */
9
+
10
+ const configManager = require('../config/config-manager');
11
+ const backendClient = require('../api/backend-client');
12
+
13
+ /**
14
+ * @typedef {Object} AuthContext
15
+ * @property {string} userId - The authenticated user's ID
16
+ * @property {string} organizationId - Current organization ID
17
+ * @property {string} organizationName - Current organization name
18
+ * @property {string} sessionToken - The CLI session token
19
+ * @property {string[]} permissions - User's permissions in the organization
20
+ */
21
+
22
+ /**
23
+ * Get authentication context from CLI session
24
+ * Returns null if not authenticated or session expired
25
+ *
26
+ * @returns {Promise<AuthContext|null>}
27
+ */
28
+ async function getAuthContext() {
29
+ // Check if session exists locally
30
+ if (!configManager.isLoggedIn()) {
31
+ return null;
32
+ }
33
+
34
+ const session = configManager.getSession();
35
+ if (!session || !session.token) {
36
+ return null;
37
+ }
38
+
39
+ // Check if session is expired locally
40
+ if (session.expiresAt && session.expiresAt < Date.now()) {
41
+ return null;
42
+ }
43
+
44
+ try {
45
+ // Validate session with backend
46
+ const validation = await backendClient.validateSession();
47
+
48
+ if (!validation || !validation.valid) {
49
+ return null;
50
+ }
51
+
52
+ // Build auth context
53
+ return {
54
+ userId: validation.userId || session.userId,
55
+ organizationId: validation.organizationId || session.organizationId,
56
+ organizationName: validation.organizationName || session.organizationName || 'Unknown',
57
+ sessionToken: session.token,
58
+ email: validation.email || session.email,
59
+ permissions: validation.permissions || [],
60
+ };
61
+ } catch (error) {
62
+ // Log error to stderr (stdout is for MCP protocol)
63
+ console.error('[L4YERCAK3 MCP] Auth validation error:', error.message);
64
+ return null;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Require authentication for a tool
70
+ * Throws if not authenticated
71
+ *
72
+ * @param {AuthContext|null} authContext
73
+ * @returns {AuthContext}
74
+ * @throws {Error} If not authenticated
75
+ */
76
+ function requireAuth(authContext) {
77
+ if (!authContext) {
78
+ throw new Error(
79
+ 'Not authenticated with L4YERCAK3. Please run "l4yercak3 login" first.'
80
+ );
81
+ }
82
+ return authContext;
83
+ }
84
+
85
+ /**
86
+ * Check if user has a specific permission
87
+ *
88
+ * @param {AuthContext} authContext
89
+ * @param {string} permission
90
+ * @returns {boolean}
91
+ */
92
+ function hasPermission(authContext, permission) {
93
+ if (!authContext || !authContext.permissions) {
94
+ return false;
95
+ }
96
+
97
+ // Check for wildcard permission
98
+ if (authContext.permissions.includes('*')) {
99
+ return true;
100
+ }
101
+
102
+ return authContext.permissions.includes(permission);
103
+ }
104
+
105
+ /**
106
+ * Require a specific permission
107
+ *
108
+ * @param {AuthContext} authContext
109
+ * @param {string} permission
110
+ * @throws {Error} If permission not granted
111
+ */
112
+ function requirePermission(authContext, permission) {
113
+ requireAuth(authContext);
114
+
115
+ if (!hasPermission(authContext, permission)) {
116
+ throw new Error(
117
+ `Permission denied: ${permission} required. Contact your organization admin.`
118
+ );
119
+ }
120
+ }
121
+
122
+ module.exports = {
123
+ getAuthContext,
124
+ requireAuth,
125
+ hasPermission,
126
+ requirePermission,
127
+ };