@agenticmail/enterprise 0.2.2 → 0.3.0

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/src/cli.ts CHANGED
@@ -1,260 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * AgenticMail Enterprise CLI
4
- *
4
+ *
5
5
  * Interactive setup wizard that provisions a cloud-hosted
6
6
  * enterprise dashboard for managing AI agent identities.
7
- *
7
+ *
8
8
  * Usage: npx @agenticmail/enterprise
9
+ *
10
+ * The wizard is split into modular steps under setup/:
11
+ * 1. Company info → setup/company.ts
12
+ * 2. Database → setup/database.ts
13
+ * 3. Deployment → setup/deployment.ts
14
+ * 4. Custom domain → setup/domain.ts
15
+ * → Provisioning → setup/provision.ts
16
+ * → Orchestrator → setup/index.ts
9
17
  */
10
18
 
11
- import { randomUUID } from 'crypto';
12
- import { createAdapter, getSupportedDatabases } from './db/factory.js';
13
- import { createServer } from './server.js';
14
- import { deployToCloud, generateDockerCompose, generateFlyToml } from './deploy/managed.js';
19
+ import { runSetupWizard } from './setup/index.js';
15
20
 
16
- async function main() {
17
- // Dynamic imports for CLI deps
18
- const { default: inquirer } = await import('inquirer');
19
- const { default: ora } = await import('ora');
20
- const { default: chalk } = await import('chalk');
21
-
22
- console.log('');
23
- console.log(chalk.bold('🏢 AgenticMail Enterprise'));
24
- console.log(chalk.dim(' AI Agent Identity & Email for Organizations'));
25
- console.log('');
26
-
27
- // ─── Step 1: Company Info ───────────────────────────────
28
-
29
- const { companyName, adminEmail, adminPassword } = await inquirer.prompt([
30
- {
31
- type: 'input',
32
- name: 'companyName',
33
- message: 'Company name:',
34
- validate: (v: string) => v.length > 0 || 'Required',
35
- },
36
- {
37
- type: 'input',
38
- name: 'adminEmail',
39
- message: 'Admin email:',
40
- validate: (v: string) => v.includes('@') || 'Must be a valid email',
41
- },
42
- {
43
- type: 'password',
44
- name: 'adminPassword',
45
- message: 'Admin password:',
46
- mask: '*',
47
- validate: (v: string) => v.length >= 8 || 'Must be at least 8 characters',
48
- },
49
- ]);
50
-
51
- // ─── Step 2: Database ───────────────────────────────────
52
-
53
- const databases = getSupportedDatabases();
54
- const { dbType } = await inquirer.prompt([
55
- {
56
- type: 'list',
57
- name: 'dbType',
58
- message: 'Database:',
59
- choices: databases.map(d => ({
60
- name: `${d.label} ${chalk.dim(`(${d.group})`)}`,
61
- value: d.type,
62
- })),
63
- },
64
- ]);
65
-
66
- let dbConfig: any = { type: dbType };
67
-
68
- if (dbType === 'sqlite') {
69
- const { dbPath } = await inquirer.prompt([{
70
- type: 'input',
71
- name: 'dbPath',
72
- message: 'Database file path:',
73
- default: './agenticmail-enterprise.db',
74
- }]);
75
- dbConfig.connectionString = dbPath;
76
- } else if (dbType === 'dynamodb') {
77
- const { region, accessKeyId, secretAccessKey } = await inquirer.prompt([
78
- { type: 'input', name: 'region', message: 'AWS Region:', default: 'us-east-1' },
79
- { type: 'input', name: 'accessKeyId', message: 'AWS Access Key ID:' },
80
- { type: 'password', name: 'secretAccessKey', message: 'AWS Secret Access Key:', mask: '*' },
81
- ]);
82
- dbConfig = { ...dbConfig, region, accessKeyId, secretAccessKey };
83
- } else if (dbType === 'turso') {
84
- const { connectionString, authToken } = await inquirer.prompt([
85
- { type: 'input', name: 'connectionString', message: 'Turso database URL:', placeholder: 'libsql://...' },
86
- { type: 'password', name: 'authToken', message: 'Turso auth token:', mask: '*' },
87
- ]);
88
- dbConfig = { ...dbConfig, connectionString, authToken };
89
- } else {
90
- // All other SQL/NoSQL databases use a connection string
91
- const hints: Record<string, string> = {
92
- postgres: 'postgresql://user:pass@host:5432/dbname',
93
- mysql: 'mysql://user:pass@host:3306/dbname',
94
- mongodb: 'mongodb+srv://user:pass@cluster.mongodb.net/dbname',
95
- supabase: 'postgresql://postgres:pass@db.xxxx.supabase.co:5432/postgres',
96
- neon: 'postgresql://user:pass@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require',
97
- planetscale: 'mysql://user:pass@aws.connect.psdb.cloud/dbname?ssl={"rejectUnauthorized":true}',
98
- cockroachdb: 'postgresql://user:pass@cluster.cockroachlabs.cloud:26257/dbname?sslmode=verify-full',
99
- };
100
- const { connectionString } = await inquirer.prompt([{
101
- type: 'input',
102
- name: 'connectionString',
103
- message: 'Connection string:',
104
- suffix: chalk.dim(` (e.g. ${hints[dbType] || ''})`),
105
- }]);
106
- dbConfig.connectionString = connectionString;
107
- }
108
-
109
- // ─── Step 3: Deployment ─────────────────────────────────
110
-
111
- const { deployTarget } = await inquirer.prompt([{
112
- type: 'list',
113
- name: 'deployTarget',
114
- message: 'Deploy to:',
115
- choices: [
116
- { name: `AgenticMail Cloud ${chalk.dim('(managed, instant URL)')}`, value: 'cloud' },
117
- { name: `Fly.io ${chalk.dim('(your account)')}`, value: 'fly' },
118
- { name: `Railway ${chalk.dim('(your account)')}`, value: 'railway' },
119
- { name: `Docker ${chalk.dim('(self-hosted)')}`, value: 'docker' },
120
- { name: `Local ${chalk.dim('(dev/testing, runs here)')}`, value: 'local' },
121
- ],
122
- }]);
123
-
124
- // Generate subdomain from company name
125
- const subdomain = companyName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
126
-
127
- // ─── Step 4: Custom Domain (optional) ──────────────────
128
-
129
- let customDomain: string | undefined;
130
- if (deployTarget !== 'local') {
131
- const { wantsDomain } = await inquirer.prompt([{
132
- type: 'confirm',
133
- name: 'wantsDomain',
134
- message: 'Add a custom domain? (can do later)',
135
- default: false,
136
- }]);
137
- if (wantsDomain) {
138
- const { domain } = await inquirer.prompt([{
139
- type: 'input',
140
- name: 'domain',
141
- message: 'Custom domain:',
142
- suffix: chalk.dim(' (e.g. agents.acme.com)'),
143
- }]);
144
- customDomain = domain;
145
- }
146
- }
147
-
148
- // ─── Provisioning ──────────────────────────────────────
149
-
150
- console.log('');
151
- const spinner = ora('Connecting to database...').start();
152
-
153
- try {
154
- // Connect to database
155
- const db = await createAdapter(dbConfig);
156
- spinner.text = 'Running migrations...';
157
- await db.migrate();
158
- spinner.succeed('Database ready');
159
-
160
- // Create company settings
161
- spinner.start('Creating company...');
162
- // Insert initial company settings
163
- // (Using raw SQL since we don't have a createSettings method)
164
- const settings = await db.updateSettings({
165
- name: companyName,
166
- subdomain,
167
- domain: customDomain,
168
- });
169
- spinner.succeed('Company created');
170
-
171
- // Create admin user
172
- spinner.start('Creating admin account...');
173
- const admin = await db.createUser({
174
- email: adminEmail,
175
- name: 'Admin',
176
- role: 'owner',
177
- password: adminPassword,
178
- });
179
- await db.logEvent({
180
- actor: admin.id, actorType: 'system', action: 'setup.complete',
181
- resource: `company:${subdomain}`,
182
- details: { dbType, deployTarget, companyName },
183
- });
184
- spinner.succeed('Admin account created');
185
-
186
- // Generate JWT secret
187
- const jwtSecret = randomUUID() + randomUUID();
188
-
189
- // Deploy
190
- if (deployTarget === 'cloud') {
191
- spinner.start('Deploying to AgenticMail Cloud...');
192
- const result = await deployToCloud({ subdomain, plan: 'free' });
193
- spinner.succeed(`Deployed to ${result.url}`);
194
-
195
- console.log('');
196
- console.log(chalk.green.bold('🎉 Your dashboard is live!'));
197
- console.log('');
198
- console.log(` ${chalk.bold('URL:')} ${result.url}`);
199
- console.log(` ${chalk.bold('Admin:')} ${adminEmail}`);
200
- console.log(` ${chalk.bold('Password:')} (the one you just set)`);
201
- if (customDomain) {
202
- console.log('');
203
- console.log(chalk.dim(` To use ${customDomain}:`));
204
- console.log(chalk.dim(` Add CNAME: ${customDomain} → ${subdomain}.agenticmail.cloud`));
205
- }
206
-
207
- } else if (deployTarget === 'docker') {
208
- const compose = generateDockerCompose({
209
- dbType, dbConnectionString: dbConfig.connectionString || '',
210
- port: 3000, jwtSecret,
211
- });
212
- const { writeFileSync } = await import('fs');
213
- writeFileSync('docker-compose.yml', compose);
214
- spinner.succeed('docker-compose.yml generated');
215
-
216
- console.log('');
217
- console.log(chalk.green.bold('🐳 Docker deployment ready!'));
218
- console.log('');
219
- console.log(' Run: docker compose up -d');
220
- console.log(' Dashboard: http://localhost:3000');
221
-
222
- } else if (deployTarget === 'fly') {
223
- const flyToml = generateFlyToml(`am-${subdomain}`, 'iad');
224
- const { writeFileSync } = await import('fs');
225
- writeFileSync('fly.toml', flyToml);
226
- spinner.succeed('fly.toml generated');
227
-
228
- console.log('');
229
- console.log(chalk.green.bold('🪰 Fly.io deployment ready!'));
230
- console.log('');
231
- console.log(' Run: fly launch --copy-config');
232
- console.log(` Then: fly secrets set DATABASE_URL="${dbConfig.connectionString}" JWT_SECRET="${jwtSecret}"`);
233
-
234
- } else if (deployTarget === 'local') {
235
- spinner.start('Starting local server...');
236
- const server = createServer({ port: 3000, db, jwtSecret });
237
- server.start();
238
- spinner.succeed('Server running');
239
-
240
- console.log('');
241
- console.log(chalk.green.bold('🎉 AgenticMail Enterprise is running!'));
242
- console.log('');
243
- console.log(` ${chalk.bold('Dashboard:')} http://localhost:3000`);
244
- console.log(` ${chalk.bold('API:')} http://localhost:3000/api`);
245
- console.log(` ${chalk.bold('Admin:')} ${adminEmail}`);
246
- console.log('');
247
- console.log(chalk.dim(' Press Ctrl+C to stop'));
248
- }
249
-
250
- console.log('');
251
-
252
- } catch (err: any) {
253
- spinner.fail(`Setup failed: ${err.message}`);
254
- console.error('');
255
- console.error(chalk.dim(err.stack));
256
- process.exit(1);
257
- }
258
- }
259
-
260
- main().catch(console.error);
21
+ runSetupWizard().catch((err) => {
22
+ console.error('Fatal error:', err.message);
23
+ process.exit(1);
24
+ });