fraim 2.0.183 → 2.0.185

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.
@@ -95,11 +95,6 @@ function asArray(value) {
95
95
  function stringValue(value) {
96
96
  return typeof value === 'string' && value.trim() ? value : null;
97
97
  }
98
- function readPackageVersion(repoRoot, result) {
99
- const relPath = 'package.json';
100
- const packageJson = readJsonObject(repoRoot, relPath, result);
101
- return packageJson ? stringValue(packageJson.version) : null;
102
- }
103
98
  function requireHttpsUrl(value, relPath, field, result) {
104
99
  const url = stringValue(value);
105
100
  if (!url) {
@@ -202,7 +197,7 @@ function validateTargetMatrix(repoRoot, result) {
202
197
  const relPath = path_1.default.join(TARGET_ROOT, 'marketplace-targets.json');
203
198
  const targetMatrix = readJsonObject(repoRoot, relPath, result);
204
199
  if (!targetMatrix) {
205
- return { remoteMcpUrl: null, oauthAuthorizationServer: null, productVersion: null, contactEmail: null };
200
+ return { remoteMcpUrl: null, oauthAuthorizationServer: null, contactEmail: null };
206
201
  }
207
202
  assertEqual(targetMatrix.schema, 'fraim-marketplace-targets-v1', relPath, 'schema', result);
208
203
  assertEqual(targetMatrix.issue, 674, relPath, 'issue', result);
@@ -214,10 +209,6 @@ function validateTargetMatrix(repoRoot, result) {
214
209
  if (!remoteMcp) {
215
210
  addIssue(result.errors, relPath, 'remoteMcp must be an object');
216
211
  }
217
- const productVersion = product ? stringValue(product.packageVersion) : null;
218
- if (!productVersion) {
219
- addIssue(result.errors, relPath, 'product.packageVersion must be set');
220
- }
221
212
  const contactEmail = product ? stringValue(product.contactEmail) : null;
222
213
  if (!contactEmail) {
223
214
  addIssue(result.errors, relPath, 'product.contactEmail must be set');
@@ -225,10 +216,6 @@ function validateTargetMatrix(repoRoot, result) {
225
216
  if (contactEmail && product) {
226
217
  assertEqual(product.support, contactEmail, relPath, 'product.support', result);
227
218
  }
228
- const packageVersion = readPackageVersion(repoRoot, result);
229
- if (productVersion && packageVersion) {
230
- assertEqual(productVersion, packageVersion, relPath, 'product.packageVersion', result);
231
- }
232
219
  const remoteMcpUrl = remoteMcp
233
220
  ? requireHttpsUrl(remoteMcp.url, relPath, 'remoteMcp.url', result)
234
221
  : null;
@@ -236,7 +223,7 @@ function validateTargetMatrix(repoRoot, result) {
236
223
  ? requireHttpsUrl(remoteMcp.oauthAuthorizationServer, relPath, 'remoteMcp.oauthAuthorizationServer', result)
237
224
  : null;
238
225
  validateTargetEntries(repoRoot, relPath, asArray(targetMatrix.targets), result);
239
- return { remoteMcpUrl, oauthAuthorizationServer, productVersion, contactEmail };
226
+ return { remoteMcpUrl, oauthAuthorizationServer, contactEmail };
240
227
  }
241
228
  function validateOpenAiAssets(repoRoot, openAiRoot, result) {
242
229
  assertFile(repoRoot, path_1.default.join(openAiRoot, 'README.md'), result);
@@ -345,11 +332,13 @@ function validateCodexManifestInterface(repoRoot, pluginRoot, manifestRelPath, m
345
332
  assertRelativeAsset(repoRoot, pluginRoot, screenshot, manifestRelPath, `interface.screenshots[${index}]`, result);
346
333
  }
347
334
  }
348
- function validateCodexManifest(repoRoot, pluginRoot, manifest, manifestRelPath, productVersion, contactEmail, result) {
335
+ function validateCodexManifest(repoRoot, pluginRoot, manifest, manifestRelPath, contactEmail, result) {
349
336
  if (!manifest)
350
337
  return;
351
338
  assertEqual(manifest.name, 'fraim', manifestRelPath, 'name', result);
352
- assertEqual(manifest.version, productVersion, manifestRelPath, 'version', result);
339
+ if (!stringValue(manifest.version)) {
340
+ addIssue(result.errors, manifestRelPath, 'version must be a non-empty string');
341
+ }
353
342
  assertEqual(asObject(manifest.author)?.email, contactEmail, manifestRelPath, 'author.email', result);
354
343
  assertEqual(manifest.skills, './skills/', manifestRelPath, 'skills', result);
355
344
  assertEqual(manifest.mcpServers, './.mcp.json', manifestRelPath, 'mcpServers', result);
@@ -409,11 +398,11 @@ function validateCodexPackage(repoRoot, result, targetDetails) {
409
398
  const manifest = readJsonObject(repoRoot, manifestRelPath, result);
410
399
  const mcpConfig = readJsonObject(repoRoot, mcpRelPath, result);
411
400
  validateCodexMarketplace(marketplace, marketplaceRelPath, result);
412
- validateCodexManifest(repoRoot, pluginRoot, manifest, manifestRelPath, targetDetails.productVersion, targetDetails.contactEmail, result);
401
+ validateCodexManifest(repoRoot, pluginRoot, manifest, manifestRelPath, targetDetails.contactEmail, result);
413
402
  validateCodexMcpConfig(mcpConfig, mcpRelPath, targetDetails.remoteMcpUrl, result);
414
403
  validateCodexSkill(repoRoot, skillRelPath, result);
415
404
  }
416
- function validateSimpleMarketplaceManifest(marketplace, marketplaceRelPath, expectedName, expectedSource, productVersion, contactEmail, result) {
405
+ function validateSimpleMarketplaceManifest(marketplace, marketplaceRelPath, expectedName, expectedSource, contactEmail, result) {
417
406
  if (!marketplace)
418
407
  return;
419
408
  assertEqual(marketplace.name, expectedName, marketplaceRelPath, 'name', result);
@@ -433,7 +422,9 @@ function validateSimpleMarketplaceManifest(marketplace, marketplaceRelPath, expe
433
422
  return;
434
423
  }
435
424
  assertEqual(fraimEntry.source, expectedSource, marketplaceRelPath, 'fraim.source', result);
436
- assertEqual(fraimEntry.version, productVersion, marketplaceRelPath, 'fraim.version', result);
425
+ if (!stringValue(fraimEntry.version)) {
426
+ addIssue(result.errors, marketplaceRelPath, 'fraim.version must be a non-empty string');
427
+ }
437
428
  const author = asObject(fraimEntry.author);
438
429
  if (author) {
439
430
  assertEqual(author.email, contactEmail, marketplaceRelPath, 'fraim.author.email', result);
@@ -453,7 +444,9 @@ function validateMcpRegistryPackage(repoRoot, result, targetDetails) {
453
444
  if (!description || description.length > 100) {
454
445
  addIssue(result.errors, serverRelPath, 'description must be a non-empty string of at most 100 characters');
455
446
  }
456
- assertEqual(serverJson.version, targetDetails.productVersion, serverRelPath, 'version', result);
447
+ if (!stringValue(serverJson.version)) {
448
+ addIssue(result.errors, serverRelPath, 'version must be a non-empty string');
449
+ }
457
450
  requireHttpsUrl(serverJson.websiteUrl, serverRelPath, 'websiteUrl', result);
458
451
  const repository = asObject(serverJson.repository);
459
452
  assertEqual(repository?.url, 'https://github.com/mathursrus/FRAIM', serverRelPath, 'repository.url', result);
@@ -484,11 +477,13 @@ function validateCursorPackage(repoRoot, result, targetDetails) {
484
477
  const mcpConfig = readJsonObject(repoRoot, mcpRelPath, result);
485
478
  assertFile(repoRoot, path_1.default.join(cursorRoot, 'README.md'), result);
486
479
  assertFile(repoRoot, path_1.default.join(pluginRoot, 'README.md'), result);
487
- validateSimpleMarketplaceManifest(rootMarketplace, ROOT_CURSOR_MARKETPLACE_PATH, 'fraim-marketplace', 'marketplaces/fraim/cursor/plugins/fraim', targetDetails.productVersion, targetDetails.contactEmail, result);
488
- validateSimpleMarketplaceManifest(targetMarketplace, targetMarketplaceRelPath, 'fraim-marketplace', 'plugins/fraim', targetDetails.productVersion, targetDetails.contactEmail, result);
480
+ validateSimpleMarketplaceManifest(rootMarketplace, ROOT_CURSOR_MARKETPLACE_PATH, 'fraim-marketplace', 'marketplaces/fraim/cursor/plugins/fraim', targetDetails.contactEmail, result);
481
+ validateSimpleMarketplaceManifest(targetMarketplace, targetMarketplaceRelPath, 'fraim-marketplace', 'plugins/fraim', targetDetails.contactEmail, result);
489
482
  if (manifest) {
490
483
  assertEqual(manifest.name, 'fraim', manifestRelPath, 'name', result);
491
- assertEqual(manifest.version, targetDetails.productVersion, manifestRelPath, 'version', result);
484
+ if (!stringValue(manifest.version)) {
485
+ addIssue(result.errors, manifestRelPath, 'version must be a non-empty string');
486
+ }
492
487
  assertEqual(asObject(manifest.author)?.email, targetDetails.contactEmail, manifestRelPath, 'author.email', result);
493
488
  assertEqual(manifest.rules, 'rules/', manifestRelPath, 'rules', result);
494
489
  assertEqual(manifest.skills, 'skills/', manifestRelPath, 'skills', result);
@@ -532,10 +527,12 @@ function validateCopilotPackage(repoRoot, result, targetDetails) {
532
527
  const mcpConfig = readJsonObject(repoRoot, mcpRelPath, result);
533
528
  assertFile(repoRoot, path_1.default.join(vscodeRoot, 'README.md'), result);
534
529
  assertFile(repoRoot, path_1.default.join(pluginRoot, 'README.md'), result);
535
- validateSimpleMarketplaceManifest(rootMarketplace, ROOT_COPILOT_MARKETPLACE_PATH, 'fraim-copilot-marketplace', 'marketplaces/fraim/vscode/plugins/fraim', targetDetails.productVersion, targetDetails.contactEmail, result);
530
+ validateSimpleMarketplaceManifest(rootMarketplace, ROOT_COPILOT_MARKETPLACE_PATH, 'fraim-copilot-marketplace', 'marketplaces/fraim/vscode/plugins/fraim', targetDetails.contactEmail, result);
536
531
  if (manifest) {
537
532
  assertEqual(manifest.name, 'fraim', manifestRelPath, 'name', result);
538
- assertEqual(manifest.version, targetDetails.productVersion, manifestRelPath, 'version', result);
533
+ if (!stringValue(manifest.version)) {
534
+ addIssue(result.errors, manifestRelPath, 'version must be a non-empty string');
535
+ }
539
536
  assertEqual(asObject(manifest.author)?.email, targetDetails.contactEmail, manifestRelPath, 'author.email', result);
540
537
  assertEqual(manifest.skills, 'skills/', manifestRelPath, 'skills', result);
541
538
  assertEqual(manifest.commands, 'commands/', manifestRelPath, 'commands', result);
@@ -559,7 +556,9 @@ function validateGeminiPackage(repoRoot, result, targetDetails) {
559
556
  if (!manifest)
560
557
  return;
561
558
  assertEqual(manifest.name, 'fraim', manifestRelPath, 'name', result);
562
- assertEqual(manifest.version, targetDetails.productVersion, manifestRelPath, 'version', result);
559
+ if (!stringValue(manifest.version)) {
560
+ addIssue(result.errors, manifestRelPath, 'version must be a non-empty string');
561
+ }
563
562
  assertEqual(manifest.contextFileName, 'GEMINI.md', manifestRelPath, 'contextFileName', result);
564
563
  const servers = asObject(manifest.mcpServers);
565
564
  const fraimServer = asObject(servers?.fraim);
@@ -74,6 +74,17 @@ function normalizeLabels(labels) {
74
74
  function escapeRegex(value) {
75
75
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
76
76
  }
77
+ /**
78
+ * Ordering for persona entitlements, applied in memory. Azure Cosmos DB (Mongo API)
79
+ * rejects an order-by on these fields without a composite index, so the sort must not
80
+ * be pushed into the query. Mirrors the former `.sort({ personaKey: 1, hireMode: 1 })`.
81
+ */
82
+ function comparePersonaEntitlements(a, b) {
83
+ const byKey = (a.personaKey || '').localeCompare(b.personaKey || '');
84
+ if (byKey !== 0)
85
+ return byKey;
86
+ return (a.hireMode || '').localeCompare(b.hireMode || '');
87
+ }
77
88
  function buildLabelMatch(label) {
78
89
  return { labels: { $regex: `^${escapeRegex(label)}$`, $options: 'i' } };
79
90
  }
@@ -728,12 +739,21 @@ class FraimDbService {
728
739
  if (activeOnly) {
729
740
  query.status = 'active';
730
741
  }
731
- return await this.personaEntitlementsCollection.find(query).sort({ personaKey: 1, hireMode: 1 }).toArray();
742
+ // Sort in memory, not in the query: Azure Cosmos DB (Mongo API) rejects an
743
+ // order-by on { personaKey, hireMode } with BadRequest 400 unless a matching
744
+ // composite index exists, and the initializeIndexes() attempt to create one
745
+ // is swallowed on Cosmos. A throw here bubbles up into getWorkspacePersonaState
746
+ // → the AI Hub silently falls back to ALL personas locked (no hired employees,
747
+ // empty manager team, empty project tree). Sorting client-side is index-free.
748
+ const rows = await this.personaEntitlementsCollection.find(query).toArray();
749
+ return rows.sort(comparePersonaEntitlements);
732
750
  }
733
751
  async listPersonaEntitlementsByStripeCustomerId(customerId) {
734
752
  if (!this.personaEntitlementsCollection)
735
753
  throw new Error('DB not connected');
736
- return await this.personaEntitlementsCollection.find({ stripeCustomerId: customerId }).sort({ updatedAt: -1 }).toArray();
754
+ // In-memory sort see getPersonaEntitlementsByWorkspaceId (Cosmos order-by).
755
+ const rows = await this.personaEntitlementsCollection.find({ stripeCustomerId: customerId }).toArray();
756
+ return rows.sort((a, b) => (b.updatedAt?.getTime() ?? 0) - (a.updatedAt?.getTime() ?? 0));
737
757
  }
738
758
  async getPersonaEntitlementsByUserId(userId, activeOnly = true) {
739
759
  if (!this.personaEntitlementsCollection)
@@ -742,7 +762,9 @@ class FraimDbService {
742
762
  if (activeOnly) {
743
763
  query.status = 'active';
744
764
  }
745
- return await this.personaEntitlementsCollection.find(query).sort({ personaKey: 1, hireMode: 1 }).toArray();
765
+ // In-memory sort see getPersonaEntitlementsByWorkspaceId (Cosmos order-by).
766
+ const rows = await this.personaEntitlementsCollection.find(query).toArray();
767
+ return rows.sort(comparePersonaEntitlements);
746
768
  }
747
769
  async upsertPersonaEntitlement(entitlement) {
748
770
  if (!this.personaEntitlementsCollection)
@@ -4,6 +4,16 @@ exports.registerOAuthRoutes = registerOAuthRoutes;
4
4
  const cookie_service_1 = require("../services/cookie-service");
5
5
  const oauth_helpers_1 = require("../services/oauth-helpers");
6
6
  const audit_log_1 = require("../services/audit-log");
7
+ const account_provisioning_1 = require("../services/account-provisioning");
8
+ const rate_limit_1 = require("../middleware/rate-limit");
9
+ function pickIntent(value) {
10
+ return value === 'signup' ? 'signup' : 'signin';
11
+ }
12
+ // Issue #691: intent=signup makes /start reachable as an account-creation path
13
+ // (see completeSignIn below), the same sensitive business flow /api/request-access
14
+ // already rate-limits. Apply the same limiter here, scoped to signup attempts only —
15
+ // the pre-existing sign-in path (no intent) is left at its original, unlimited behavior.
16
+ const signupRateLimiter = (0, rate_limit_1.sensitiveRateLimiter)();
7
17
  const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
8
18
  const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
9
19
  const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize';
@@ -75,13 +85,17 @@ function registerOAuthRoutes(app, deps) {
75
85
  NODE_ENV: process.env.NODE_ENV || '(not set)',
76
86
  });
77
87
  // Helper used by both providers to finalise authentication once we have a verified email.
78
- async function completeSignIn(req, res, verifiedEmail, provider, surface, redirectTo) {
88
+ async function completeSignIn(req, res, params) {
89
+ const { verifiedEmail, provider, surface, redirectTo, intent } = params;
79
90
  const lower = verifiedEmail.toLowerCase();
80
91
  const apiKeyData = await dbService.getApiKeyByUserId(lower, false);
81
- if (!apiKeyData) {
92
+ if (!apiKeyData && intent !== 'signup') {
82
93
  // Sign-in is NOT a sign-up. If the verified email doesn't already match a
83
94
  // FRAIM account, route the user to the sign-up flow. Account creation is
84
95
  // owned by /api/request-access, not by login. (Round-2 user feedback.)
96
+ // Issue #691: this only applies to the sign-in surface (intent !== 'signup').
97
+ // The dedicated signup surface uses intent='signup' to provision an account
98
+ // below instead of bouncing to no_account.
85
99
  await (0, audit_log_1.auditLog)('OAUTH_CALLBACK_SUCCESS', {
86
100
  userId: lower,
87
101
  provider,
@@ -92,6 +106,9 @@ function registerOAuthRoutes(app, deps) {
92
106
  (0, cookie_service_1.clearOAuthPendingCookie)(res);
93
107
  return res.redirect(`/auth/error.html?reason=no_account`);
94
108
  }
109
+ if (!apiKeyData && intent === 'signup') {
110
+ await (0, account_provisioning_1.ensureTrialApiKey)(dbService, lower);
111
+ }
95
112
  const sessionId = (0, oauth_helpers_1.generateSessionId)();
96
113
  await dbService.createAuthSession({
97
114
  sessionId,
@@ -106,13 +123,18 @@ function registerOAuthRoutes(app, deps) {
106
123
  (0, cookie_service_1.clearOAuthPendingCookie)(res);
107
124
  res.redirect(redirectTo);
108
125
  }
109
- app.get('/api/auth/oauth/:provider/start', async (req, res) => {
126
+ app.get('/api/auth/oauth/:provider/start', (req, res, next) => {
127
+ if (pickIntent(req.query.intent) === 'signup')
128
+ return signupRateLimiter(req, res, next);
129
+ next();
130
+ }, async (req, res) => {
110
131
  const providerRaw = req.params.provider;
111
132
  const provider = typeof providerRaw === 'string' ? providerRaw : '';
112
133
  if (!(0, oauth_helpers_1.isSupportedProvider)(provider)) {
113
134
  return res.status(404).json({ error: 'unsupported_provider' });
114
135
  }
115
136
  const surface = (0, oauth_helpers_1.pickSurfaceOrDefault)(req.query.surface);
137
+ const intent = pickIntent(req.query.intent);
116
138
  const redirectTo = (0, oauth_helpers_1.safeRedirectPath)(req.query.redirect_to, (0, oauth_helpers_1.defaultRedirectForSurface)(surface));
117
139
  try {
118
140
  const codeVerifier = (0, oauth_helpers_1.generatePkceVerifier)();
@@ -122,6 +144,7 @@ function registerOAuthRoutes(app, deps) {
122
144
  pendingId,
123
145
  provider,
124
146
  surface,
147
+ intent,
125
148
  codeVerifier,
126
149
  stateNonce,
127
150
  redirectTo,
@@ -228,7 +251,13 @@ function registerOAuthRoutes(app, deps) {
228
251
  await (0, audit_log_1.auditLog)('OAUTH_UNVERIFIED_EMAIL', { provider: 'google', outcome: 'failure' });
229
252
  return res.redirect('/auth/error.html?reason=unverified_email');
230
253
  }
231
- await completeSignIn(req, res, payload.email, 'google', pending.surface, pending.redirectTo);
254
+ await completeSignIn(req, res, {
255
+ verifiedEmail: payload.email,
256
+ provider: 'google',
257
+ surface: pending.surface,
258
+ redirectTo: pending.redirectTo,
259
+ intent: pickIntent(pending.intent),
260
+ });
232
261
  }
233
262
  catch (err) {
234
263
  console.error('[FRAIM AUTH] /api/auth/oauth/google/callback error:', err instanceof Error ? err.message : String(err));
@@ -315,7 +344,13 @@ function registerOAuthRoutes(app, deps) {
315
344
  await (0, audit_log_1.auditLog)('OAUTH_UNVERIFIED_EMAIL', { provider: 'github', outcome: 'failure' });
316
345
  return res.redirect('/auth/error.html?reason=unverified_email');
317
346
  }
318
- await completeSignIn(req, res, verifiedEmail, 'github', pending.surface, pending.redirectTo);
347
+ await completeSignIn(req, res, {
348
+ verifiedEmail,
349
+ provider: 'github',
350
+ surface: pending.surface,
351
+ redirectTo: pending.redirectTo,
352
+ intent: pickIntent(pending.intent),
353
+ });
319
354
  }
320
355
  catch (err) {
321
356
  console.error('[FRAIM AUTH] /api/auth/oauth/github/callback error:', err instanceof Error ? err.message : String(err));
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureTrialApiKey = ensureTrialApiKey;
4
+ const feature_flags_1 = require("../config/feature-flags");
5
+ const TRIAL_DURATION_MS = 14 * 24 * 60 * 60 * 1000;
6
+ /**
7
+ * Provision a 14-day trial FraimApiKey for `emailLower` if one does not already
8
+ * exist; otherwise return the existing key. Shared by the email+code signup
9
+ * flow (`AdminService.verifyRequestAccessCode`) and the OAuth signup-intent
10
+ * flow (`oauth-routes.ts`) so account creation has exactly one implementation.
11
+ * Issue #691.
12
+ */
13
+ async function ensureTrialApiKey(dbService, emailLower) {
14
+ const existingKey = await dbService.getApiKeyByUserId(emailLower, false);
15
+ if (existingKey)
16
+ return existingKey.key;
17
+ const orgId = 'default';
18
+ const apiKey = dbService.generateApiKey(emailLower, orgId);
19
+ const trialExpiresAt = new Date(Date.now() + TRIAL_DURATION_MS);
20
+ await dbService.createApiKey({
21
+ key: apiKey,
22
+ userId: emailLower,
23
+ orgId,
24
+ tier: 'trial',
25
+ status: 'active',
26
+ expiresAt: trialExpiresAt,
27
+ stripeCustomerId: null,
28
+ stripeSubscriptionId: null,
29
+ currentPeriodEnd: null,
30
+ cancelAt: null,
31
+ suspendedAt: null,
32
+ suspensionReason: null,
33
+ lastUsedAt: null,
34
+ apiCallCount: 0,
35
+ personaSystemActive: (0, feature_flags_1.isPersonaEntitlementsEnabled)() || undefined
36
+ });
37
+ return apiKey;
38
+ }
@@ -7,6 +7,7 @@ const email_service_1 = require("./email-service");
7
7
  const dashboard_access_1 = require("./dashboard-access");
8
8
  const feature_flags_1 = require("../config/feature-flags");
9
9
  const email_code_1 = require("./email-code");
10
+ const account_provisioning_1 = require("./account-provisioning");
10
11
  class AdminService {
11
12
  constructor(dbService) {
12
13
  this.dbService = dbService;
@@ -130,8 +131,11 @@ class AdminService {
130
131
  */
131
132
  async requestAccess(body, req) {
132
133
  const { email, name, company, useCase } = body;
133
- if (!email || !name || !company) {
134
- throw new Error('Email, name, and company are required');
134
+ // Issue #691: signup is now email + verification code only. Name/company
135
+ // are optional lead-capture fields (createWebsiteSignup below already
136
+ // tolerates missing values); the trial key itself never required them.
137
+ if (!email) {
138
+ throw new Error('Email is required');
135
139
  }
136
140
  if (!(0, request_utils_1.validateEmail)(email))
137
141
  throw new Error('Invalid email address');
@@ -140,8 +144,8 @@ class AdminService {
140
144
  const { ip: ipAddress, userAgent } = (0, request_utils_1.getRequestMeta)(req);
141
145
  await this.dbService.createWebsiteSignup({
142
146
  email: emailLower,
143
- name: name.trim(),
144
- company: company.trim(),
147
+ name: name?.trim() || '',
148
+ company: company?.trim() || '',
145
149
  useCase: useCase?.trim(),
146
150
  source: 'request-access',
147
151
  timestamp: new Date(),
@@ -184,38 +188,8 @@ class AdminService {
184
188
  if (!verification) {
185
189
  return { success: false, error: 'Invalid or expired verification code' };
186
190
  }
187
- const orgId = 'default';
188
- const existingKey = await this.dbService.getApiKeyByUserId(emailLower, false);
189
- let apiKey;
190
- if (existingKey) {
191
- apiKey = existingKey.key;
192
- console.log('[FRAIM] verify_email_code existing_key_found', { userId: emailLower });
193
- }
194
- else {
195
- apiKey = this.dbService.generateApiKey(emailLower, orgId);
196
- const trialExpiresAt = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000);
197
- await this.dbService.createApiKey({
198
- key: apiKey,
199
- userId: emailLower,
200
- orgId,
201
- tier: 'trial',
202
- status: 'active',
203
- expiresAt: trialExpiresAt,
204
- stripeCustomerId: null,
205
- stripeSubscriptionId: null,
206
- currentPeriodEnd: null,
207
- cancelAt: null,
208
- suspendedAt: null,
209
- suspensionReason: null,
210
- lastUsedAt: null,
211
- apiCallCount: 0,
212
- personaSystemActive: (0, feature_flags_1.isPersonaEntitlementsEnabled)() || undefined
213
- });
214
- console.log('[FRAIM] verify_email_code trial_key_created', {
215
- userId: emailLower,
216
- expiresAt: trialExpiresAt.toISOString()
217
- });
218
- }
191
+ const apiKey = await (0, account_provisioning_1.ensureTrialApiKey)(this.dbService, emailLower);
192
+ console.log('[FRAIM] verify_email_code key_resolved', { userId: emailLower });
219
193
  const access = await (0, dashboard_access_1.createDashboardAccessLink)(this.dbService, emailLower, apiKey);
220
194
  console.log('[FRAIM] verify_email_code verification_completed', { userId: emailLower });
221
195
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.183",
3
+ "version": "2.0.185",
4
4
  "description": "FRAIM CLI - Framework for Rigor-based AI Management (alias for fraim-framework)",
5
5
  "main": "index.js",
6
6
  "bin": {