@agenticmail/enterprise 0.3.0 → 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/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ope Olatunji (https://github.com/ope-olatunji)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ AgenticMail Enterprise - AI agent deployment platform for organizations.
26
+ Part of the AgenticMail project: https://github.com/agenticmail
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 │ │ │ │
@@ -924,6 +924,16 @@ packages/enterprise/src/
924
924
 
925
925
  ---
926
926
 
927
+ ## Author
928
+
929
+ Created by **[Ope Olatunji](https://github.com/ope-olatunji)**.
930
+
931
+ Part of the [AgenticMail](https://github.com/agenticmail/agenticmail) project — the first platform to give AI agents real email addresses and phone numbers.
932
+
933
+ - GitHub: [@ope-olatunji](https://github.com/ope-olatunji)
934
+ - Website: [agenticmail.io](https://agenticmail.io)
935
+ - Twitter: [@agenticmail](https://x.com/agenticmail)
936
+
927
937
  ## License
928
938
 
929
939
  MIT — see [LICENSE](./LICENSE)
@@ -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,10 +1,10 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.3.0",
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": {
7
- "agenticmail-enterprise": "./dist/cli.js"
7
+ "agenticmail-enterprise": "dist/cli.js"
8
8
  },
9
9
  "exports": {
10
10
  ".": {
@@ -27,6 +27,15 @@
27
27
  "scim",
28
28
  "identity"
29
29
  ],
30
+ "author": "Ope Olatunji (https://github.com/ope-olatunji)",
31
+ "homepage": "https://github.com/agenticmail/enterprise",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/agenticmail/enterprise.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/agenticmail/enterprise/issues"
38
+ },
30
39
  "license": "MIT",
31
40
  "peerDependencies": {
32
41
  "openclaw": ">=0.1.0",
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);