@agenticmail/enterprise 0.3.1 → 0.3.2

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/README.md CHANGED
@@ -96,13 +96,13 @@ Think of AgenticMail Enterprise as an HR department for AI agents.
96
96
 
97
97
  ```
98
98
  ┌──────────────────────────────────────────────────────┐
99
- │ Dashboard (Web UI)
100
- │ Single HTML · React 18 · CDN
99
+ │ Dashboard (Web UI)
100
+ │ Single HTML · React 18 · CDN
101
101
  └─────────────────────────┬────────────────────────────┘
102
102
  │ HTTP
103
103
  ┌─────────────────────────▼────────────────────────────┐
104
- │ Hono API Server
105
-
104
+ │ Hono API Server
105
+
106
106
  │ ┌─────────┐ ┌──────────┐ ┌──────────────────────┐ │
107
107
  │ │ Auth │ │ Admin │ │ Engine │ │
108
108
  │ │ Routes │ │ Routes │ │ │ │
@@ -400,10 +400,10 @@
400
400
  return h(Fragment, null,
401
401
  h('div', { className: 'page-header' }, h('h1', { className: 'page-title' }, 'Dashboard'), h('p', { className: 'page-desc' }, 'System overview and recent activity')),
402
402
  h('div', { className: 'stats-row' },
403
- h(Stat, { label: 'Agents', value: stats?.agents ?? 0, color: 'accent', sub: 'Deployed' }),
404
- h(Stat, { label: 'Users', value: stats?.users ?? 0 }),
405
- h(Stat, { label: 'Emails Today', value: stats?.emails ?? 0, color: 'success' }),
406
- h(Stat, { label: 'Events', value: stats?.events ?? 0 }),
403
+ h(Stat, { label: 'Agents', value: stats?.totalAgents ?? stats?.agents ?? 0, color: 'accent', sub: (stats?.activeAgents ?? 0) + ' active' }),
404
+ h(Stat, { label: 'Users', value: stats?.totalUsers ?? stats?.users ?? 0 }),
405
+ h(Stat, { label: 'Emails', value: stats?.totalEmails ?? stats?.emails ?? 0, color: 'success' }),
406
+ h(Stat, { label: 'Events', value: stats?.totalAuditEvents ?? stats?.events ?? 0 }),
407
407
  ),
408
408
  h('div', { className: 'card' },
409
409
  h('div', { className: 'card-header' }, h('h3', null, 'Recent Activity')),
@@ -414,7 +414,7 @@
414
414
  h('div', { className: 'activity-dot' }),
415
415
  h('div', { className: 'activity-text' },
416
416
  h('strong', null, e.action || e.type || 'Event'), ' ',
417
- e.details || e.description || JSON.stringify(e).slice(0, 80)
417
+ typeof e.details === 'string' ? e.details : e.description || JSON.stringify(e.details || e).slice(0, 80)
418
418
  ),
419
419
  h('div', { className: 'activity-time' }, e.timestamp ? new Date(e.timestamp).toLocaleString() : '')
420
420
  ))
package/live-test.mjs ADDED
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Live end-to-end test — starts server, runs all HTTP tests, shuts down.
3
+ */
4
+ import { createAdapter, createServer } from './dist/index.js';
5
+ import { randomUUID } from 'crypto';
6
+
7
+ const PORT = 3201;
8
+ const BASE = `http://localhost:${PORT}`;
9
+
10
+ async function req(path, opts = {}) {
11
+ const res = await fetch(`${BASE}${path}`, {
12
+ headers: { 'Content-Type': 'application/json', ...opts.headers },
13
+ ...opts,
14
+ });
15
+ return { status: res.status, data: await res.json().catch(() => null), headers: res.headers };
16
+ }
17
+
18
+ let pass = 0, fail = 0;
19
+ function ok(cond, name) { if (cond) { pass++; console.log(` ✅ ${name}`); } else { fail++; console.error(` ❌ ${name}`); } }
20
+
21
+ // ─── Setup ────────────────────────────────────
22
+ const db = await createAdapter({ type: 'sqlite', connectionString: ':memory:' });
23
+ await db.migrate();
24
+ const user = await db.createUser({ email: 'ope@agenticmail.io', name: 'Ope', role: 'owner', password: 'Test1234!' });
25
+ await db.updateSettings({ name: 'AgenticMail', subdomain: 'agenticmail', domain: 'agenticmail.io' });
26
+ const apiKey = (await db.createApiKey({ name: 'k', createdBy: user.id, scopes: ['read','write','admin'] })).plaintext;
27
+
28
+ const jwtSecret = randomUUID() + randomUUID();
29
+ const server = createServer({ port: PORT, db, jwtSecret, logging: false, rateLimit: 500 });
30
+ const handle = await server.start();
31
+
32
+ console.log('\n🏢 Live E2E Test — Enterprise Server on :' + PORT + '\n');
33
+
34
+ // ─── 1. Health ────────────────────────────────
35
+ console.log('─── Health & Ready ───');
36
+ const health = await req('/health');
37
+ ok(health.status === 200 && health.data?.status === 'ok', `GET /health → ${health.data?.status}`);
38
+ ok(health.data?.version === '0.3.0', `Version: ${health.data?.version}`);
39
+
40
+ const ready = await req('/ready');
41
+ ok(ready.status === 200, `GET /ready → ${ready.status}`);
42
+
43
+ // ─── 2. Auth ──────────────────────────────────
44
+ console.log('\n─── Auth ───');
45
+ const login = await req('/auth/login', { method: 'POST', body: JSON.stringify({ email: 'ope@agenticmail.io', password: 'Test1234!' }) });
46
+ ok(login.status === 200 && login.data?.token, 'Login → JWT token');
47
+ const jwt = login.data?.token;
48
+
49
+ const badLogin = await req('/auth/login', { method: 'POST', body: JSON.stringify({ email: 'ope@agenticmail.io', password: 'wrong' }) });
50
+ ok(badLogin.status === 401, 'Bad password → 401');
51
+
52
+ const noAuth = await req('/api/stats');
53
+ ok(noAuth.status === 401, 'No auth → 401');
54
+
55
+ const keyAuth = await req('/api/stats', { headers: { 'X-API-Key': apiKey } });
56
+ ok(keyAuth.status === 200, 'API key auth → 200');
57
+
58
+ const badKey = await req('/api/stats', { headers: { 'X-API-Key': 'ek_fake' } });
59
+ ok(badKey.status === 401, 'Bad API key → 401');
60
+
61
+ const auth = { Authorization: `Bearer ${jwt}` };
62
+
63
+ // ─── 3. Admin ─────────────────────────────────
64
+ console.log('\n─── Admin API ───');
65
+ const stats = await req('/api/stats', { headers: auth });
66
+ ok(stats.status === 200 && stats.data, `Stats: ${JSON.stringify(stats.data).slice(0, 80)}`);
67
+
68
+ // Create agent
69
+ const createAgent = await req('/api/agents', { method: 'POST', headers: auth, body: JSON.stringify({ name: 'support-bot', email: 'support@agenticmail.io', role: 'customer-support' }) });
70
+ ok(createAgent.status === 201, `Create agent → ${createAgent.status} (${createAgent.data?.name})`);
71
+ const agentId = createAgent.data?.id;
72
+
73
+ // List agents
74
+ const agents = await req('/api/agents', { headers: auth });
75
+ ok(agents.data?.agents?.length >= 1, `List agents → ${agents.data?.agents?.length}`);
76
+
77
+ // Get agent
78
+ const getAgent = await req(`/api/agents/${agentId}`, { headers: auth });
79
+ ok(getAgent.status === 200 && getAgent.data?.name === 'support-bot', 'Get agent by ID');
80
+
81
+ // Update agent
82
+ const updateAgent = await req(`/api/agents/${agentId}`, { method: 'PATCH', headers: auth, body: JSON.stringify({ role: 'lead-support' }) });
83
+ ok(updateAgent.status === 200 && updateAgent.data?.role === 'lead-support', 'Update agent role');
84
+
85
+ // Create second agent
86
+ const agent2 = await req('/api/agents', { method: 'POST', headers: auth, body: JSON.stringify({ name: 'research-bot', email: 'research@agenticmail.io', role: 'researcher' }) });
87
+ ok(agent2.status === 201, `Create second agent: ${agent2.data?.name}`);
88
+
89
+ // Users
90
+ const users = await req('/api/users', { headers: auth });
91
+ ok(users.status === 200, `List users → ${users.status}`);
92
+
93
+ // Audit log
94
+ const audit = await req('/api/audit', { headers: auth });
95
+ ok(audit.status === 200 && audit.data?.events?.length > 0, `Audit log → ${audit.data?.events?.length} events`);
96
+
97
+ // Settings
98
+ const settings = await req('/api/settings', { headers: auth });
99
+ ok(settings.status === 200 && settings.data?.name === 'AgenticMail', `Settings: ${settings.data?.name}`);
100
+
101
+ const patchSettings = await req('/api/settings', { method: 'PATCH', headers: auth, body: JSON.stringify({ name: 'AgenticMail Inc' }) });
102
+ ok(patchSettings.status === 200 && patchSettings.data?.name === 'AgenticMail Inc', 'Patch settings');
103
+
104
+ // Delete agent
105
+ const delAgent = await req(`/api/agents/${agent2.data?.id}`, { method: 'DELETE', headers: auth });
106
+ ok(delAgent.status === 200 || delAgent.status === 204, 'Delete agent');
107
+
108
+ // ─── 4. Engine ────────────────────────────────
109
+ console.log('\n─── Engine API ───');
110
+ const skills = await req('/api/engine/skills', { headers: auth });
111
+ ok(skills.status === 200, `Skills endpoint → ${skills.status}`);
112
+ const skillList = skills.data?.skills || skills.data || [];
113
+ ok(skillList.length >= 38, `${skillList.length} skills loaded`);
114
+
115
+ // Single skill
116
+ if (skillList.length) {
117
+ const s = await req(`/api/engine/skills/${skillList[0].id}`, { headers: auth });
118
+ ok(s.status === 200, `Get skill: ${s.data?.name || skillList[0].id}`);
119
+ }
120
+
121
+ // Presets
122
+ const presets = await req('/api/engine/profiles/presets', { headers: auth });
123
+ ok(presets.status === 200, 'Presets endpoint');
124
+ const presetList = presets.data?.presets || presets.data || [];
125
+ ok(presetList.length >= 5, `${presetList.length} presets`);
126
+
127
+ // Permission check
128
+ const permCheck = await req('/api/engine/permissions/check', { method: 'POST', headers: auth, body: JSON.stringify({ agentId: 'test', tool: 'web_search' }) });
129
+ ok(permCheck.status === 200, `Permission check → ${permCheck.status}`);
130
+
131
+ // Skills by category
132
+ const byCategory = await req('/api/engine/skills/by-category', { headers: auth });
133
+ ok(byCategory.status === 200, 'Skills by category');
134
+
135
+ // ─── 5. Dashboard ─────────────────────────────
136
+ console.log('\n─── Dashboard ───');
137
+ const dash = await fetch(`${BASE}/dashboard`);
138
+ ok(dash.status === 200, 'Dashboard serves');
139
+ const html = await dash.text();
140
+ ok(html.length > 1000 && html.includes('AgenticMail'), `Dashboard HTML: ${html.length} chars`);
141
+
142
+ const root = await fetch(`${BASE}/`, { redirect: 'manual' });
143
+ ok(root.status === 302, 'Root → /dashboard redirect');
144
+
145
+ // ─── 6. Security ──────────────────────────────
146
+ console.log('\n─── Security ───');
147
+ const secRes = await req('/health');
148
+ ok(secRes.headers.get('x-request-id'), 'X-Request-Id header');
149
+ ok(secRes.headers.get('x-content-type-options') === 'nosniff', 'nosniff header');
150
+ ok(secRes.headers.get('x-frame-options') === 'DENY', 'X-Frame-Options: DENY');
151
+
152
+ // 404 (outside /api so no auth wall)
153
+ const notFound = await req('/nonexistent-path');
154
+ ok(notFound.status === 404, '404 for unknown path');
155
+
156
+ // ─── Done ─────────────────────────────────────
157
+ handle.close();
158
+ server.healthMonitor.stop();
159
+ await db.disconnect();
160
+
161
+ console.log(`\n═══════════════════════════════════════`);
162
+ console.log(` ${pass} passed, ${fail} failed, ${pass + fail} total`);
163
+ console.log(`═══════════════════════════════════════`);
164
+ console.log(fail === 0 ? '\n✅ ALL LIVE TESTS PASSED\n' : '\n❌ SOME TESTS FAILED\n');
165
+ process.exit(fail > 0 ? 1 : 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
package/serve.mjs ADDED
@@ -0,0 +1,19 @@
1
+ import { createAdapter, createServer } from './dist/index.js';
2
+ import { randomUUID } from 'crypto';
3
+
4
+ const db = await createAdapter({ type: 'sqlite', connectionString: './enterprise-live.db' });
5
+ await db.migrate();
6
+ let user = await db.getUserByEmail('ope@agenticmail.io');
7
+ if (!user) {
8
+ user = await db.createUser({ email: 'ope@agenticmail.io', name: 'Ope Olatunji', role: 'owner', password: 'Enterprise2026!' });
9
+ await db.updateSettings({ name: 'AgenticMail', subdomain: 'agenticmail', domain: 'agenticmail.io' });
10
+ // Create some agents
11
+ await db.createAgent({ name: 'support-bot', email: 'support@agenticmail.io', role: 'customer-support', status: 'active', createdBy: user.id });
12
+ await db.createAgent({ name: 'research-bot', email: 'research@agenticmail.io', role: 'researcher', status: 'active', createdBy: user.id });
13
+ await db.createAgent({ name: 'writer-bot', email: 'writer@agenticmail.io', role: 'content-writer', status: 'active', createdBy: user.id });
14
+ console.log('✅ Seeded: admin + 3 agents');
15
+ }
16
+ const server = createServer({ port: 3200, db, jwtSecret: randomUUID()+randomUUID(), logging: true });
17
+ await server.start();
18
+ console.log('\nLogin: ope@agenticmail.io / Enterprise2026!');
19
+ setInterval(() => {}, 30000);
@@ -400,10 +400,10 @@
400
400
  return h(Fragment, null,
401
401
  h('div', { className: 'page-header' }, h('h1', { className: 'page-title' }, 'Dashboard'), h('p', { className: 'page-desc' }, 'System overview and recent activity')),
402
402
  h('div', { className: 'stats-row' },
403
- h(Stat, { label: 'Agents', value: stats?.agents ?? 0, color: 'accent', sub: 'Deployed' }),
404
- h(Stat, { label: 'Users', value: stats?.users ?? 0 }),
405
- h(Stat, { label: 'Emails Today', value: stats?.emails ?? 0, color: 'success' }),
406
- h(Stat, { label: 'Events', value: stats?.events ?? 0 }),
403
+ h(Stat, { label: 'Agents', value: stats?.totalAgents ?? stats?.agents ?? 0, color: 'accent', sub: (stats?.activeAgents ?? 0) + ' active' }),
404
+ h(Stat, { label: 'Users', value: stats?.totalUsers ?? stats?.users ?? 0 }),
405
+ h(Stat, { label: 'Emails', value: stats?.totalEmails ?? stats?.emails ?? 0, color: 'success' }),
406
+ h(Stat, { label: 'Events', value: stats?.totalAuditEvents ?? stats?.events ?? 0 }),
407
407
  ),
408
408
  h('div', { className: 'card' },
409
409
  h('div', { className: 'card-header' }, h('h3', null, 'Recent Activity')),
@@ -414,7 +414,7 @@
414
414
  h('div', { className: 'activity-dot' }),
415
415
  h('div', { className: 'activity-text' },
416
416
  h('strong', null, e.action || e.type || 'Event'), ' ',
417
- e.details || e.description || JSON.stringify(e).slice(0, 80)
417
+ typeof e.details === 'string' ? e.details : e.description || JSON.stringify(e.details || e).slice(0, 80)
418
418
  ),
419
419
  h('div', { className: 'activity-time' }, e.timestamp ? new Date(e.timestamp).toLocaleString() : '')
420
420
  ))
package/start-live.mjs ADDED
@@ -0,0 +1,39 @@
1
+ import { createAdapter, createServer } from './dist/index.js';
2
+ import { randomUUID } from 'crypto';
3
+
4
+ const db = await createAdapter({ type: 'sqlite', connectionString: './enterprise-live-test.db' });
5
+ await db.migrate();
6
+ console.log('✅ DB migrated');
7
+
8
+ // Check if admin already exists
9
+ let user = await db.getUserByEmail('ope@agenticmail.io');
10
+ if (!user) {
11
+ user = await db.createUser({
12
+ email: 'ope@agenticmail.io',
13
+ name: 'Ope Olatunji',
14
+ role: 'owner',
15
+ password: 'Enterprise2026!',
16
+ });
17
+ console.log('✅ Admin created:', user.id);
18
+ } else {
19
+ console.log('✅ Admin exists:', user.id);
20
+ }
21
+
22
+ await db.updateSettings({ name: 'AgenticMail', subdomain: 'agenticmail', domain: 'agenticmail.io' });
23
+ console.log('✅ Company: AgenticMail / agenticmail.io');
24
+
25
+ const key = await db.createApiKey({ name: 'live-key', createdBy: user.id, scopes: ['read', 'write', 'admin'] });
26
+ console.log('✅ API Key:', key.plaintext);
27
+
28
+ const jwtSecret = randomUUID() + randomUUID();
29
+ const server = createServer({ port: 3200, db, jwtSecret, corsOrigins: ['*'], rateLimit: 200 });
30
+ await server.start();
31
+
32
+ console.log('');
33
+ console.log('Login: ope@agenticmail.io / Enterprise2026!');
34
+ console.log('Key: ', key.plaintext);
35
+
36
+ // Keep process alive
37
+ process.on('SIGINT', () => { console.log('\nShutting down...'); process.exit(0); });
38
+ process.on('SIGTERM', () => { console.log('\nShutting down...'); process.exit(0); });
39
+ setInterval(() => {}, 60000);