@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 +4 -4
- package/dist/dashboard/index.html +5 -5
- package/live-test.mjs +165 -0
- package/package.json +1 -1
- package/serve.mjs +19 -0
- package/src/dashboard/index.html +5 -5
- package/start-live.mjs +39 -0
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: '
|
|
404
|
-
h(Stat, { label: 'Users', value: stats?.users ?? 0 }),
|
|
405
|
-
h(Stat, { label: 'Emails
|
|
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
|
|
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
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);
|
package/src/dashboard/index.html
CHANGED
|
@@ -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: '
|
|
404
|
-
h(Stat, { label: 'Users', value: stats?.users ?? 0 }),
|
|
405
|
-
h(Stat, { label: 'Emails
|
|
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
|
|
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);
|