@agenticmail/enterprise 0.5.13 → 0.5.15

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 CHANGED
@@ -48,7 +48,7 @@ Skill Development:
48
48
  break;
49
49
  case "setup":
50
50
  default:
51
- import("./setup-SZPCU3TC.js").then((m) => m.runSetupWizard()).catch(fatal);
51
+ import("./setup-UH5AXHZX.js").then((m) => m.runSetupWizard()).catch(fatal);
52
52
  break;
53
53
  }
54
54
  function fatal(err) {
@@ -48,6 +48,10 @@ export function SettingsPage() {
48
48
  var newProvider = _newProvider[0]; var setNewProvider = _newProvider[1];
49
49
  var _discoverResults = useState({});
50
50
  var discoverResults = _discoverResults[0]; var setDiscoverResults = _discoverResults[1];
51
+ var _apiKeyModal = useState(null); // { providerId, providerName, isUpdate }
52
+ var apiKeyModal = _apiKeyModal[0]; var setApiKeyModal = _apiKeyModal[1];
53
+ var _apiKeyInput = useState('');
54
+ var apiKeyInput = _apiKeyInput[0]; var setApiKeyInput = _apiKeyInput[1];
51
55
 
52
56
  useEffect(() => {
53
57
  apiCall('/settings').then(d => { const s = d.settings || d || {}; setSettings(s); if (s.primaryColor) applyBrandColor(s.primaryColor); }).catch(() => {});
@@ -558,6 +562,10 @@ export function SettingsPage() {
558
562
  setNewProvider: setNewProvider,
559
563
  discoverResults: discoverResults,
560
564
  setDiscoverResults: setDiscoverResults,
565
+ apiKeyModal: apiKeyModal,
566
+ setApiKeyModal: setApiKeyModal,
567
+ apiKeyInput: apiKeyInput,
568
+ setApiKeyInput: setApiKeyInput,
561
569
  onSave: function() {
562
570
  setPricingSaving(true);
563
571
  apiCall('/settings/model-pricing', { method: 'PUT', body: JSON.stringify(pricing) }).then(function(d) {
@@ -1310,6 +1318,22 @@ function ProvidersSection(props) {
1310
1318
  }, isConfigured ? 'Connected' : 'Not Configured')
1311
1319
  ),
1312
1320
  isLocal && p.baseUrl && h('div', { style: { fontSize: 12, color: '#6b7280', marginBottom: 8, wordBreak: 'break-all' } }, p.baseUrl),
1321
+ !isLocal && !p.isCustom && p.requiresApiKey && h('div', { style: { marginTop: 8 } },
1322
+ isConfigured
1323
+ ? h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 } },
1324
+ h('span', { style: { color: 'var(--success, #16a34a)' } }, 'āœ“ API key configured via environment'),
1325
+ h('button', { className: 'btn btn-sm btn-ghost', style: { padding: '2px 8px', fontSize: 11 }, onClick: function() {
1326
+ setApiKeyInput('');
1327
+ setApiKeyModal({ providerId: p.id, providerName: p.name, isUpdate: true });
1328
+ }}, 'Update Key')
1329
+ )
1330
+ : h('div', null,
1331
+ h('button', { className: 'btn btn-sm btn-primary', onClick: function() {
1332
+ setApiKeyInput('');
1333
+ setApiKeyModal({ providerId: p.id, providerName: p.name, isUpdate: false });
1334
+ }}, 'šŸ”‘ Add API Key')
1335
+ )
1336
+ ),
1313
1337
  h('div', { style: { display: 'flex', gap: 6, marginTop: 8 } },
1314
1338
  isLocal && h('button', {
1315
1339
  className: 'btn btn-sm',
@@ -1377,6 +1401,52 @@ function ProvidersSection(props) {
1377
1401
  h('button', { className: 'btn', onClick: function() { props.setShowAddProvider(false); } }, 'Cancel'),
1378
1402
  h('button', { className: 'btn btn-primary', onClick: handleAddProvider }, 'Add Provider')
1379
1403
  )
1404
+ ),
1405
+
1406
+ // API Key Modal
1407
+ props.apiKeyModal && h(Modal, {
1408
+ title: (props.apiKeyModal.isUpdate ? 'Update' : 'Add') + ' API Key — ' + props.apiKeyModal.providerName,
1409
+ onClose: function() { props.setApiKeyModal(null); props.setApiKeyInput(''); },
1410
+ },
1411
+ h('div', { style: { marginBottom: 16 } },
1412
+ h('p', { style: { color: 'var(--text-secondary)', fontSize: 13, margin: '0 0 16px 0', lineHeight: 1.5 } },
1413
+ props.apiKeyModal.isUpdate
1414
+ ? 'Enter a new API key to replace the current one for ' + props.apiKeyModal.providerName + '.'
1415
+ : 'Enter your ' + props.apiKeyModal.providerName + ' API key to enable this provider.'
1416
+ ),
1417
+ h('label', { className: 'form-label' }, 'API Key'),
1418
+ h('input', {
1419
+ className: 'input',
1420
+ type: 'password',
1421
+ placeholder: 'sk-...',
1422
+ value: props.apiKeyInput,
1423
+ autoFocus: true,
1424
+ onChange: function(e) { props.setApiKeyInput(e.target.value); },
1425
+ onKeyDown: function(e) {
1426
+ if (e.key === 'Enter' && props.apiKeyInput.trim()) {
1427
+ var m = props.apiKeyModal;
1428
+ apiCall('/providers/' + m.providerId + '/api-key', { method: 'POST', body: JSON.stringify({ apiKey: props.apiKeyInput.trim() }) })
1429
+ .then(function() { toast((m.isUpdate ? 'API key updated' : 'API key saved') + ' for ' + m.providerName + '!', 'success'); props.setApiKeyModal(null); props.setApiKeyInput(''); window.location.reload(); })
1430
+ .catch(function(e) { toast(e.message || 'Failed to save', 'error'); });
1431
+ }
1432
+ },
1433
+ style: { fontSize: 14, fontFamily: 'var(--font-mono, monospace)' },
1434
+ }),
1435
+ h('p', { className: 'form-help', style: { marginTop: 6 } }, 'Your key is stored encrypted and never exposed in the dashboard.')
1436
+ ),
1437
+ h('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } },
1438
+ h('button', { className: 'btn', onClick: function() { props.setApiKeyModal(null); props.setApiKeyInput(''); } }, 'Cancel'),
1439
+ h('button', {
1440
+ className: 'btn btn-primary',
1441
+ disabled: !props.apiKeyInput.trim(),
1442
+ onClick: function() {
1443
+ var m = props.apiKeyModal;
1444
+ apiCall('/providers/' + m.providerId + '/api-key', { method: 'POST', body: JSON.stringify({ apiKey: props.apiKeyInput.trim() }) })
1445
+ .then(function() { toast((m.isUpdate ? 'API key updated' : 'API key saved') + ' for ' + m.providerName + '!', 'success'); props.setApiKeyModal(null); props.setApiKeyInput(''); window.location.reload(); })
1446
+ .catch(function(e) { toast(e.message || 'Failed to save', 'error'); });
1447
+ }
1448
+ }, props.apiKeyModal.isUpdate ? 'Update Key' : 'Save Key')
1449
+ )
1380
1450
  )
1381
1451
  );
1382
1452
  }
@@ -1410,6 +1480,10 @@ function ModelPricingTab(props) {
1410
1480
  discoverResults: props.discoverResults,
1411
1481
  setDiscoverResults: props.setDiscoverResults,
1412
1482
  toast: props.toast,
1483
+ apiKeyModal: props.apiKeyModal,
1484
+ setApiKeyModal: props.setApiKeyModal,
1485
+ apiKeyInput: props.apiKeyInput,
1486
+ setApiKeyInput: props.setApiKeyInput,
1413
1487
  }),
1414
1488
 
1415
1489
  // Divider
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  requireRole,
27
27
  securityHeaders,
28
28
  validate
29
- } from "./chunk-AXLDCUI7.js";
29
+ } from "./chunk-LNWVYHWX.js";
30
30
  import {
31
31
  PROVIDER_REGISTRY,
32
32
  listAllProviders,
@@ -36,7 +36,7 @@ import {
36
36
  import {
37
37
  provision,
38
38
  runSetupWizard
39
- } from "./chunk-WOJ47LRQ.js";
39
+ } from "./chunk-VBKWGYN3.js";
40
40
  import {
41
41
  ENGINE_TABLES,
42
42
  ENGINE_TABLES_POSTGRES,
@@ -0,0 +1,11 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-LNWVYHWX.js";
4
+ import "./chunk-ZNR5DDTA.js";
5
+ import "./chunk-RO537U6H.js";
6
+ import "./chunk-DRXMYYKN.js";
7
+ import "./chunk-JLSQOQ5L.js";
8
+ import "./chunk-KFQGP6VL.js";
9
+ export {
10
+ createServer
11
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-VBKWGYN3.js";
10
+ import "./chunk-6TDW6ZNI.js";
11
+ import "./chunk-KFQGP6VL.js";
12
+ export {
13
+ promptCompanyInfo,
14
+ promptDatabase,
15
+ promptDeployment,
16
+ promptDomain,
17
+ promptRegistration,
18
+ provision,
19
+ runSetupWizard
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -629,6 +629,31 @@ export function createAdminRoutes(db: DatabaseAdapter) {
629
629
  return c.json({ ok: true, provider: body });
630
630
  });
631
631
 
632
+ // ─── Provider API Key Management ────────────────────────
633
+ api.post('/providers/:id/api-key', requireRole('admin'), async (c) => {
634
+ var id = c.req.param('id');
635
+ var provider = PROVIDER_REGISTRY[id];
636
+ if (!provider) {
637
+ return c.json({ error: 'Unknown provider' }, 404);
638
+ }
639
+ var body = await c.req.json();
640
+ if (!body.apiKey || typeof body.apiKey !== 'string' || body.apiKey.trim().length < 5) {
641
+ return c.json({ error: 'Valid API key required' }, 400);
642
+ }
643
+
644
+ // Store API key in company settings (encrypted field)
645
+ var settings = await db.getSettings();
646
+ var config = (settings as any)?.modelPricingConfig || { models: [], currency: 'USD' };
647
+ config.providerApiKeys = config.providerApiKeys || {};
648
+ config.providerApiKeys[id] = body.apiKey.trim();
649
+ await db.updateSettings({ modelPricingConfig: config } as any);
650
+
651
+ // Also set in process.env so it takes effect immediately
652
+ process.env[provider.envKey] = body.apiKey.trim();
653
+
654
+ return c.json({ ok: true, message: 'API key saved for ' + provider.name });
655
+ });
656
+
632
657
  api.put('/providers/:id', requireRole('admin'), async (c) => {
633
658
  var id = c.req.param('id');
634
659
  if (PROVIDER_REGISTRY[id]) {
@@ -48,6 +48,10 @@ export function SettingsPage() {
48
48
  var newProvider = _newProvider[0]; var setNewProvider = _newProvider[1];
49
49
  var _discoverResults = useState({});
50
50
  var discoverResults = _discoverResults[0]; var setDiscoverResults = _discoverResults[1];
51
+ var _apiKeyModal = useState(null); // { providerId, providerName, isUpdate }
52
+ var apiKeyModal = _apiKeyModal[0]; var setApiKeyModal = _apiKeyModal[1];
53
+ var _apiKeyInput = useState('');
54
+ var apiKeyInput = _apiKeyInput[0]; var setApiKeyInput = _apiKeyInput[1];
51
55
 
52
56
  useEffect(() => {
53
57
  apiCall('/settings').then(d => { const s = d.settings || d || {}; setSettings(s); if (s.primaryColor) applyBrandColor(s.primaryColor); }).catch(() => {});
@@ -558,6 +562,10 @@ export function SettingsPage() {
558
562
  setNewProvider: setNewProvider,
559
563
  discoverResults: discoverResults,
560
564
  setDiscoverResults: setDiscoverResults,
565
+ apiKeyModal: apiKeyModal,
566
+ setApiKeyModal: setApiKeyModal,
567
+ apiKeyInput: apiKeyInput,
568
+ setApiKeyInput: setApiKeyInput,
561
569
  onSave: function() {
562
570
  setPricingSaving(true);
563
571
  apiCall('/settings/model-pricing', { method: 'PUT', body: JSON.stringify(pricing) }).then(function(d) {
@@ -1310,6 +1318,22 @@ function ProvidersSection(props) {
1310
1318
  }, isConfigured ? 'Connected' : 'Not Configured')
1311
1319
  ),
1312
1320
  isLocal && p.baseUrl && h('div', { style: { fontSize: 12, color: '#6b7280', marginBottom: 8, wordBreak: 'break-all' } }, p.baseUrl),
1321
+ !isLocal && !p.isCustom && p.requiresApiKey && h('div', { style: { marginTop: 8 } },
1322
+ isConfigured
1323
+ ? h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 } },
1324
+ h('span', { style: { color: 'var(--success, #16a34a)' } }, 'āœ“ API key configured via environment'),
1325
+ h('button', { className: 'btn btn-sm btn-ghost', style: { padding: '2px 8px', fontSize: 11 }, onClick: function() {
1326
+ setApiKeyInput('');
1327
+ setApiKeyModal({ providerId: p.id, providerName: p.name, isUpdate: true });
1328
+ }}, 'Update Key')
1329
+ )
1330
+ : h('div', null,
1331
+ h('button', { className: 'btn btn-sm btn-primary', onClick: function() {
1332
+ setApiKeyInput('');
1333
+ setApiKeyModal({ providerId: p.id, providerName: p.name, isUpdate: false });
1334
+ }}, 'šŸ”‘ Add API Key')
1335
+ )
1336
+ ),
1313
1337
  h('div', { style: { display: 'flex', gap: 6, marginTop: 8 } },
1314
1338
  isLocal && h('button', {
1315
1339
  className: 'btn btn-sm',
@@ -1377,6 +1401,52 @@ function ProvidersSection(props) {
1377
1401
  h('button', { className: 'btn', onClick: function() { props.setShowAddProvider(false); } }, 'Cancel'),
1378
1402
  h('button', { className: 'btn btn-primary', onClick: handleAddProvider }, 'Add Provider')
1379
1403
  )
1404
+ ),
1405
+
1406
+ // API Key Modal
1407
+ props.apiKeyModal && h(Modal, {
1408
+ title: (props.apiKeyModal.isUpdate ? 'Update' : 'Add') + ' API Key — ' + props.apiKeyModal.providerName,
1409
+ onClose: function() { props.setApiKeyModal(null); props.setApiKeyInput(''); },
1410
+ },
1411
+ h('div', { style: { marginBottom: 16 } },
1412
+ h('p', { style: { color: 'var(--text-secondary)', fontSize: 13, margin: '0 0 16px 0', lineHeight: 1.5 } },
1413
+ props.apiKeyModal.isUpdate
1414
+ ? 'Enter a new API key to replace the current one for ' + props.apiKeyModal.providerName + '.'
1415
+ : 'Enter your ' + props.apiKeyModal.providerName + ' API key to enable this provider.'
1416
+ ),
1417
+ h('label', { className: 'form-label' }, 'API Key'),
1418
+ h('input', {
1419
+ className: 'input',
1420
+ type: 'password',
1421
+ placeholder: 'sk-...',
1422
+ value: props.apiKeyInput,
1423
+ autoFocus: true,
1424
+ onChange: function(e) { props.setApiKeyInput(e.target.value); },
1425
+ onKeyDown: function(e) {
1426
+ if (e.key === 'Enter' && props.apiKeyInput.trim()) {
1427
+ var m = props.apiKeyModal;
1428
+ apiCall('/providers/' + m.providerId + '/api-key', { method: 'POST', body: JSON.stringify({ apiKey: props.apiKeyInput.trim() }) })
1429
+ .then(function() { toast((m.isUpdate ? 'API key updated' : 'API key saved') + ' for ' + m.providerName + '!', 'success'); props.setApiKeyModal(null); props.setApiKeyInput(''); window.location.reload(); })
1430
+ .catch(function(e) { toast(e.message || 'Failed to save', 'error'); });
1431
+ }
1432
+ },
1433
+ style: { fontSize: 14, fontFamily: 'var(--font-mono, monospace)' },
1434
+ }),
1435
+ h('p', { className: 'form-help', style: { marginTop: 6 } }, 'Your key is stored encrypted and never exposed in the dashboard.')
1436
+ ),
1437
+ h('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } },
1438
+ h('button', { className: 'btn', onClick: function() { props.setApiKeyModal(null); props.setApiKeyInput(''); } }, 'Cancel'),
1439
+ h('button', {
1440
+ className: 'btn btn-primary',
1441
+ disabled: !props.apiKeyInput.trim(),
1442
+ onClick: function() {
1443
+ var m = props.apiKeyModal;
1444
+ apiCall('/providers/' + m.providerId + '/api-key', { method: 'POST', body: JSON.stringify({ apiKey: props.apiKeyInput.trim() }) })
1445
+ .then(function() { toast((m.isUpdate ? 'API key updated' : 'API key saved') + ' for ' + m.providerName + '!', 'success'); props.setApiKeyModal(null); props.setApiKeyInput(''); window.location.reload(); })
1446
+ .catch(function(e) { toast(e.message || 'Failed to save', 'error'); });
1447
+ }
1448
+ }, props.apiKeyModal.isUpdate ? 'Update Key' : 'Save Key')
1449
+ )
1380
1450
  )
1381
1451
  );
1382
1452
  }
@@ -1410,6 +1480,10 @@ function ModelPricingTab(props) {
1410
1480
  discoverResults: props.discoverResults,
1411
1481
  setDiscoverResults: props.setDiscoverResults,
1412
1482
  toast: props.toast,
1483
+ apiKeyModal: props.apiKeyModal,
1484
+ setApiKeyModal: props.setApiKeyModal,
1485
+ apiKeyInput: props.apiKeyInput,
1486
+ setApiKeyInput: props.setApiKeyInput,
1413
1487
  }),
1414
1488
 
1415
1489
  // Divider
package/src/server.ts CHANGED
@@ -391,6 +391,29 @@ export function createServer(config: ServerConfig): ServerInstance {
391
391
  // Start health monitoring
392
392
  healthMonitor.start();
393
393
 
394
+ // Load saved provider API keys from DB into process.env
395
+ config.db.getSettings().then((settings: any) => {
396
+ const keys = settings?.modelPricingConfig?.providerApiKeys;
397
+ if (keys && typeof keys === 'object') {
398
+ // Map of provider IDs to their env var names
399
+ const envMap: Record<string, string> = {
400
+ anthropic: 'ANTHROPIC_API_KEY', openai: 'OPENAI_API_KEY',
401
+ google: 'GOOGLE_API_KEY', deepseek: 'DEEPSEEK_API_KEY',
402
+ xai: 'XAI_API_KEY', mistral: 'MISTRAL_API_KEY',
403
+ groq: 'GROQ_API_KEY', together: 'TOGETHER_API_KEY',
404
+ fireworks: 'FIREWORKS_API_KEY', perplexity: 'PERPLEXITY_API_KEY',
405
+ cohere: 'COHERE_API_KEY',
406
+ };
407
+ for (const [providerId, apiKey] of Object.entries(keys)) {
408
+ const envVar = envMap[providerId];
409
+ if (envVar && apiKey && !process.env[envVar]) {
410
+ process.env[envVar] = apiKey as string;
411
+ console.log(` šŸ”‘ Loaded API key for ${providerId}`);
412
+ }
413
+ }
414
+ }
415
+ }).catch(() => {});
416
+
394
417
  // Graceful shutdown
395
418
  const shutdown = () => {
396
419
  console.log('\nā³ Shutting down gracefully...');