@agenticmail/enterprise 0.2.2 → 0.3.1

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
@@ -1,58 +1,77 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- createAdapter,
4
- createServer,
5
- deployToCloud,
6
- generateDockerCompose,
7
- generateFlyToml,
8
3
  getSupportedDatabases
9
- } from "./chunk-YFDSE4BW.js";
4
+ } from "./chunk-BS2WCSHO.js";
10
5
  import "./chunk-PNKVD2UK.js";
11
6
 
12
- // src/cli.ts
13
- import { randomUUID } from "crypto";
14
- async function main() {
15
- const { default: inquirer } = await import("inquirer");
16
- const { default: ora } = await import("ora");
17
- const { default: chalk } = await import("chalk");
18
- console.log("");
19
- console.log(chalk.bold("\u{1F3E2} AgenticMail Enterprise"));
20
- console.log(chalk.dim(" AI Agent Identity & Email for Organizations"));
21
- console.log("");
7
+ // src/setup/company.ts
8
+ async function promptCompanyInfo(inquirer, chalk) {
9
+ console.log(chalk.bold.cyan(" Step 1 of 4: Company Info"));
10
+ console.log(chalk.dim(" Tell us about your organization.\n"));
22
11
  const { companyName, adminEmail, adminPassword } = await inquirer.prompt([
23
12
  {
24
13
  type: "input",
25
14
  name: "companyName",
26
15
  message: "Company name:",
27
- validate: (v) => v.length > 0 || "Required"
16
+ validate: (v) => {
17
+ if (!v.trim()) return "Company name is required";
18
+ if (v.length > 100) return "Company name must be under 100 characters";
19
+ return true;
20
+ }
28
21
  },
29
22
  {
30
23
  type: "input",
31
24
  name: "adminEmail",
32
25
  message: "Admin email:",
33
- validate: (v) => v.includes("@") || "Must be a valid email"
26
+ validate: (v) => {
27
+ if (!v.includes("@") || !v.includes(".")) return "Enter a valid email address";
28
+ return true;
29
+ }
34
30
  },
35
31
  {
36
32
  type: "password",
37
33
  name: "adminPassword",
38
34
  message: "Admin password:",
39
35
  mask: "*",
40
- validate: (v) => v.length >= 8 || "Must be at least 8 characters"
36
+ validate: (v) => {
37
+ if (v.length < 8) return "Password must be at least 8 characters";
38
+ if (!/[A-Z]/.test(v) && !/[0-9]/.test(v)) {
39
+ return "Password should contain at least one uppercase letter or number";
40
+ }
41
+ return true;
42
+ }
41
43
  }
42
44
  ]);
45
+ const subdomain = companyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 63);
46
+ return { companyName, adminEmail, adminPassword, subdomain };
47
+ }
48
+
49
+ // src/setup/database.ts
50
+ var CONNECTION_HINTS = {
51
+ postgres: "postgresql://user:pass@host:5432/dbname",
52
+ mysql: "mysql://user:pass@host:3306/dbname",
53
+ mongodb: "mongodb+srv://user:pass@cluster.mongodb.net/dbname",
54
+ supabase: "postgresql://postgres:pass@db.xxxx.supabase.co:5432/postgres",
55
+ neon: "postgresql://user:pass@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require",
56
+ planetscale: 'mysql://user:pass@aws.connect.psdb.cloud/dbname?ssl={"rejectUnauthorized":true}',
57
+ cockroachdb: "postgresql://user:pass@cluster.cockroachlabs.cloud:26257/dbname?sslmode=verify-full"
58
+ };
59
+ async function promptDatabase(inquirer, chalk) {
60
+ console.log("");
61
+ console.log(chalk.bold.cyan(" Step 2 of 4: Database"));
62
+ console.log(chalk.dim(" Where should your data live?\n"));
43
63
  const databases = getSupportedDatabases();
44
64
  const { dbType } = await inquirer.prompt([
45
65
  {
46
66
  type: "list",
47
67
  name: "dbType",
48
- message: "Database:",
68
+ message: "Database backend:",
49
69
  choices: databases.map((d) => ({
50
70
  name: `${d.label} ${chalk.dim(`(${d.group})`)}`,
51
71
  value: d.type
52
72
  }))
53
73
  }
54
74
  ]);
55
- let dbConfig = { type: dbType };
56
75
  if (dbType === "sqlite") {
57
76
  const { dbPath } = await inquirer.prompt([{
58
77
  type: "input",
@@ -60,159 +79,305 @@ async function main() {
60
79
  message: "Database file path:",
61
80
  default: "./agenticmail-enterprise.db"
62
81
  }]);
63
- dbConfig.connectionString = dbPath;
64
- } else if (dbType === "dynamodb") {
65
- const { region, accessKeyId, secretAccessKey } = await inquirer.prompt([
66
- { type: "input", name: "region", message: "AWS Region:", default: "us-east-1" },
67
- { type: "input", name: "accessKeyId", message: "AWS Access Key ID:" },
68
- { type: "password", name: "secretAccessKey", message: "AWS Secret Access Key:", mask: "*" }
82
+ return { type: dbType, connectionString: dbPath };
83
+ }
84
+ if (dbType === "dynamodb") {
85
+ const answers = await inquirer.prompt([
86
+ {
87
+ type: "input",
88
+ name: "region",
89
+ message: "AWS Region:",
90
+ default: "us-east-1"
91
+ },
92
+ {
93
+ type: "input",
94
+ name: "accessKeyId",
95
+ message: "AWS Access Key ID:",
96
+ validate: (v) => v.length > 0 || "Required"
97
+ },
98
+ {
99
+ type: "password",
100
+ name: "secretAccessKey",
101
+ message: "AWS Secret Access Key:",
102
+ mask: "*",
103
+ validate: (v) => v.length > 0 || "Required"
104
+ }
69
105
  ]);
70
- dbConfig = { ...dbConfig, region, accessKeyId, secretAccessKey };
71
- } else if (dbType === "turso") {
72
- const { connectionString, authToken } = await inquirer.prompt([
73
- { type: "input", name: "connectionString", message: "Turso database URL:", placeholder: "libsql://..." },
74
- { type: "password", name: "authToken", message: "Turso auth token:", mask: "*" }
106
+ return { type: dbType, ...answers };
107
+ }
108
+ if (dbType === "turso") {
109
+ const answers = await inquirer.prompt([
110
+ {
111
+ type: "input",
112
+ name: "connectionString",
113
+ message: "Turso database URL:",
114
+ suffix: chalk.dim(" (e.g. libsql://db-org.turso.io)"),
115
+ validate: (v) => v.length > 0 || "Required"
116
+ },
117
+ {
118
+ type: "password",
119
+ name: "authToken",
120
+ message: "Turso auth token:",
121
+ mask: "*",
122
+ validate: (v) => v.length > 0 || "Required"
123
+ }
75
124
  ]);
76
- dbConfig = { ...dbConfig, connectionString, authToken };
77
- } else {
78
- const hints = {
79
- postgres: "postgresql://user:pass@host:5432/dbname",
80
- mysql: "mysql://user:pass@host:3306/dbname",
81
- mongodb: "mongodb+srv://user:pass@cluster.mongodb.net/dbname",
82
- supabase: "postgresql://postgres:pass@db.xxxx.supabase.co:5432/postgres",
83
- neon: "postgresql://user:pass@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require",
84
- planetscale: 'mysql://user:pass@aws.connect.psdb.cloud/dbname?ssl={"rejectUnauthorized":true}',
85
- cockroachdb: "postgresql://user:pass@cluster.cockroachlabs.cloud:26257/dbname?sslmode=verify-full"
86
- };
87
- const { connectionString } = await inquirer.prompt([{
88
- type: "input",
89
- name: "connectionString",
90
- message: "Connection string:",
91
- suffix: chalk.dim(` (e.g. ${hints[dbType] || ""})`)
92
- }]);
93
- dbConfig.connectionString = connectionString;
125
+ return { type: dbType, connectionString: answers.connectionString, authToken: answers.authToken };
94
126
  }
127
+ const hint = CONNECTION_HINTS[dbType] || "";
128
+ const { connectionString } = await inquirer.prompt([{
129
+ type: "input",
130
+ name: "connectionString",
131
+ message: "Connection string:",
132
+ suffix: hint ? chalk.dim(` (e.g. ${hint})`) : "",
133
+ validate: (v) => v.length > 0 || "Connection string is required"
134
+ }]);
135
+ return { type: dbType, connectionString };
136
+ }
137
+
138
+ // src/setup/deployment.ts
139
+ async function promptDeployment(inquirer, chalk) {
140
+ console.log("");
141
+ console.log(chalk.bold.cyan(" Step 3 of 4: Deployment"));
142
+ console.log(chalk.dim(" Where should your dashboard run?\n"));
95
143
  const { deployTarget } = await inquirer.prompt([{
96
144
  type: "list",
97
145
  name: "deployTarget",
98
146
  message: "Deploy to:",
99
147
  choices: [
100
- { name: `AgenticMail Cloud ${chalk.dim("(managed, instant URL)")}`, value: "cloud" },
101
- { name: `Fly.io ${chalk.dim("(your account)")}`, value: "fly" },
102
- { name: `Railway ${chalk.dim("(your account)")}`, value: "railway" },
103
- { name: `Docker ${chalk.dim("(self-hosted)")}`, value: "docker" },
104
- { name: `Local ${chalk.dim("(dev/testing, runs here)")}`, value: "local" }
148
+ {
149
+ name: `AgenticMail Cloud ${chalk.dim("(managed, instant URL)")}`,
150
+ value: "cloud"
151
+ },
152
+ {
153
+ name: `Fly.io ${chalk.dim("(your account)")}`,
154
+ value: "fly"
155
+ },
156
+ {
157
+ name: `Railway ${chalk.dim("(your account)")}`,
158
+ value: "railway"
159
+ },
160
+ {
161
+ name: `Docker ${chalk.dim("(self-hosted)")}`,
162
+ value: "docker"
163
+ },
164
+ {
165
+ name: `Local ${chalk.dim("(dev/testing, runs here)")}`,
166
+ value: "local"
167
+ }
105
168
  ]
106
169
  }]);
107
- const subdomain = companyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
108
- let customDomain;
109
- if (deployTarget !== "local") {
110
- const { wantsDomain } = await inquirer.prompt([{
111
- type: "confirm",
112
- name: "wantsDomain",
113
- message: "Add a custom domain? (can do later)",
114
- default: false
115
- }]);
116
- if (wantsDomain) {
117
- const { domain } = await inquirer.prompt([{
118
- type: "input",
119
- name: "domain",
120
- message: "Custom domain:",
121
- suffix: chalk.dim(" (e.g. agents.acme.com)")
122
- }]);
123
- customDomain = domain;
124
- }
170
+ return { target: deployTarget };
171
+ }
172
+
173
+ // src/setup/domain.ts
174
+ async function promptDomain(inquirer, chalk, deployTarget) {
175
+ if (deployTarget === "local") {
176
+ return {};
125
177
  }
126
178
  console.log("");
179
+ console.log(chalk.bold.cyan(" Step 4 of 4: Custom Domain"));
180
+ console.log(chalk.dim(" Optional \u2014 you can add this later.\n"));
181
+ const { wantsDomain } = await inquirer.prompt([{
182
+ type: "confirm",
183
+ name: "wantsDomain",
184
+ message: "Add a custom domain?",
185
+ default: false
186
+ }]);
187
+ if (!wantsDomain) return {};
188
+ const { domain } = await inquirer.prompt([{
189
+ type: "input",
190
+ name: "domain",
191
+ message: "Custom domain:",
192
+ suffix: chalk.dim(" (e.g. agents.acme.com)"),
193
+ validate: (v) => {
194
+ if (!v.includes(".")) return "Enter a valid domain (e.g. agents.acme.com)";
195
+ return true;
196
+ }
197
+ }]);
198
+ return { customDomain: domain };
199
+ }
200
+
201
+ // src/setup/provision.ts
202
+ import { randomUUID } from "crypto";
203
+ async function provision(config, ora, chalk) {
127
204
  const spinner = ora("Connecting to database...").start();
205
+ const jwtSecret = randomUUID() + randomUUID();
128
206
  try {
129
- const db = await createAdapter(dbConfig);
207
+ const { createAdapter } = await import("./factory-HINWFYZ3.js");
208
+ const db = await createAdapter(config.database);
130
209
  spinner.text = "Running migrations...";
131
210
  await db.migrate();
132
211
  spinner.succeed("Database ready");
133
212
  spinner.start("Creating company...");
134
- const settings = await db.updateSettings({
135
- name: companyName,
136
- subdomain,
137
- domain: customDomain
213
+ await db.updateSettings({
214
+ name: config.company.companyName,
215
+ subdomain: config.company.subdomain,
216
+ domain: config.domain.customDomain
138
217
  });
139
218
  spinner.succeed("Company created");
140
219
  spinner.start("Creating admin account...");
141
220
  const admin = await db.createUser({
142
- email: adminEmail,
221
+ email: config.company.adminEmail,
143
222
  name: "Admin",
144
223
  role: "owner",
145
- password: adminPassword
224
+ password: config.company.adminPassword
146
225
  });
147
226
  await db.logEvent({
148
227
  actor: admin.id,
149
228
  actorType: "system",
150
229
  action: "setup.complete",
151
- resource: `company:${subdomain}`,
152
- details: { dbType, deployTarget, companyName }
230
+ resource: `company:${config.company.subdomain}`,
231
+ details: {
232
+ dbType: config.database.type,
233
+ deployTarget: config.deployTarget,
234
+ companyName: config.company.companyName
235
+ }
153
236
  });
154
237
  spinner.succeed("Admin account created");
155
- const jwtSecret = randomUUID() + randomUUID();
156
- if (deployTarget === "cloud") {
157
- spinner.start("Deploying to AgenticMail Cloud...");
158
- const result = await deployToCloud({ subdomain, plan: "free" });
159
- spinner.succeed(`Deployed to ${result.url}`);
160
- console.log("");
161
- console.log(chalk.green.bold("\u{1F389} Your dashboard is live!"));
162
- console.log("");
163
- console.log(` ${chalk.bold("URL:")} ${result.url}`);
164
- console.log(` ${chalk.bold("Admin:")} ${adminEmail}`);
165
- console.log(` ${chalk.bold("Password:")} (the one you just set)`);
166
- if (customDomain) {
167
- console.log("");
168
- console.log(chalk.dim(` To use ${customDomain}:`));
169
- console.log(chalk.dim(` Add CNAME: ${customDomain} \u2192 ${subdomain}.agenticmail.cloud`));
170
- }
171
- } else if (deployTarget === "docker") {
172
- const compose = generateDockerCompose({
173
- dbType,
174
- dbConnectionString: dbConfig.connectionString || "",
175
- port: 3e3,
176
- jwtSecret
177
- });
178
- const { writeFileSync } = await import("fs");
179
- writeFileSync("docker-compose.yml", compose);
180
- spinner.succeed("docker-compose.yml generated");
181
- console.log("");
182
- console.log(chalk.green.bold("\u{1F433} Docker deployment ready!"));
183
- console.log("");
184
- console.log(" Run: docker compose up -d");
185
- console.log(" Dashboard: http://localhost:3000");
186
- } else if (deployTarget === "fly") {
187
- const flyToml = generateFlyToml(`am-${subdomain}`, "iad");
188
- const { writeFileSync } = await import("fs");
189
- writeFileSync("fly.toml", flyToml);
190
- spinner.succeed("fly.toml generated");
191
- console.log("");
192
- console.log(chalk.green.bold("\u{1FAB0} Fly.io deployment ready!"));
193
- console.log("");
194
- console.log(" Run: fly launch --copy-config");
195
- console.log(` Then: fly secrets set DATABASE_URL="${dbConfig.connectionString}" JWT_SECRET="${jwtSecret}"`);
196
- } else if (deployTarget === "local") {
197
- spinner.start("Starting local server...");
198
- const server = createServer({ port: 3e3, db, jwtSecret });
199
- server.start();
200
- spinner.succeed("Server running");
201
- console.log("");
202
- console.log(chalk.green.bold("\u{1F389} AgenticMail Enterprise is running!"));
203
- console.log("");
204
- console.log(` ${chalk.bold("Dashboard:")} http://localhost:3000`);
205
- console.log(` ${chalk.bold("API:")} http://localhost:3000/api`);
206
- console.log(` ${chalk.bold("Admin:")} ${adminEmail}`);
207
- console.log("");
208
- console.log(chalk.dim(" Press Ctrl+C to stop"));
209
- }
210
- console.log("");
238
+ const result = await deploy(config, db, jwtSecret, spinner, chalk);
239
+ return {
240
+ success: true,
241
+ url: result.url,
242
+ jwtSecret,
243
+ db,
244
+ serverClose: result.close
245
+ };
211
246
  } catch (err) {
212
247
  spinner.fail(`Setup failed: ${err.message}`);
248
+ return {
249
+ success: false,
250
+ error: err.message,
251
+ jwtSecret,
252
+ db: null
253
+ };
254
+ }
255
+ }
256
+ async function deploy(config, db, jwtSecret, spinner, chalk) {
257
+ const { deployTarget, company, database, domain } = config;
258
+ if (deployTarget === "cloud") {
259
+ spinner.start("Deploying to AgenticMail Cloud...");
260
+ const { deployToCloud } = await import("./managed-RZITNPXG.js");
261
+ const result = await deployToCloud({
262
+ subdomain: company.subdomain,
263
+ plan: "free",
264
+ dbType: database.type,
265
+ dbConnectionString: database.connectionString || "",
266
+ jwtSecret
267
+ });
268
+ spinner.succeed(`Deployed to ${result.url}`);
269
+ printCloudSuccess(chalk, result.url, company.adminEmail, domain.customDomain, company.subdomain);
270
+ return { url: result.url };
271
+ }
272
+ if (deployTarget === "docker") {
273
+ const { generateDockerCompose } = await import("./managed-RZITNPXG.js");
274
+ const compose = generateDockerCompose({
275
+ dbType: database.type,
276
+ dbConnectionString: database.connectionString || "",
277
+ port: 3e3,
278
+ jwtSecret
279
+ });
280
+ const { writeFileSync } = await import("fs");
281
+ writeFileSync("docker-compose.yml", compose);
282
+ spinner.succeed("docker-compose.yml generated");
283
+ console.log("");
284
+ console.log(chalk.green.bold(" Docker deployment ready!"));
285
+ console.log("");
286
+ console.log(` Run: ${chalk.cyan("docker compose up -d")}`);
287
+ console.log(` Dashboard: ${chalk.cyan("http://localhost:3000")}`);
288
+ return { url: "http://localhost:3000" };
289
+ }
290
+ if (deployTarget === "fly") {
291
+ const { generateFlyToml } = await import("./managed-RZITNPXG.js");
292
+ const flyToml = generateFlyToml(`am-${company.subdomain}`, "iad");
293
+ const { writeFileSync } = await import("fs");
294
+ writeFileSync("fly.toml", flyToml);
295
+ spinner.succeed("fly.toml generated");
296
+ console.log("");
297
+ console.log(chalk.green.bold(" Fly.io deployment ready!"));
298
+ console.log("");
299
+ console.log(` 1. ${chalk.cyan("fly launch --copy-config")}`);
300
+ console.log(` 2. ${chalk.cyan(`fly secrets set DATABASE_URL="${database.connectionString}" JWT_SECRET="${jwtSecret}"`)}`);
301
+ console.log(` 3. ${chalk.cyan("fly deploy")}`);
302
+ return {};
303
+ }
304
+ if (deployTarget === "railway") {
305
+ const { generateRailwayConfig } = await import("./managed-RZITNPXG.js");
306
+ const railwayConfig = generateRailwayConfig();
307
+ const { writeFileSync } = await import("fs");
308
+ writeFileSync("railway.toml", railwayConfig);
309
+ spinner.succeed("railway.toml generated");
310
+ console.log("");
311
+ console.log(chalk.green.bold(" Railway deployment ready!"));
312
+ console.log("");
313
+ console.log(` 1. ${chalk.cyan("railway init")}`);
314
+ console.log(` 2. ${chalk.cyan("railway link")}`);
315
+ console.log(` 3. ${chalk.cyan("railway up")}`);
316
+ return {};
317
+ }
318
+ spinner.start("Starting local server...");
319
+ const { createServer } = await import("./server-H3C6WUOS.js");
320
+ const server = createServer({ port: 3e3, db, jwtSecret });
321
+ const handle = await server.start();
322
+ spinner.succeed("Server running");
323
+ console.log("");
324
+ console.log(chalk.green.bold(" AgenticMail Enterprise is running!"));
325
+ console.log("");
326
+ console.log(` ${chalk.bold("Dashboard:")} ${chalk.cyan("http://localhost:3000")}`);
327
+ console.log(` ${chalk.bold("API:")} ${chalk.cyan("http://localhost:3000/api")}`);
328
+ console.log(` ${chalk.bold("Admin:")} ${company.adminEmail}`);
329
+ console.log("");
330
+ console.log(chalk.dim(" Press Ctrl+C to stop"));
331
+ return { url: "http://localhost:3000", close: handle.close };
332
+ }
333
+ function printCloudSuccess(chalk, url, adminEmail, customDomain, subdomain) {
334
+ console.log("");
335
+ console.log(chalk.green.bold(" Your dashboard is live!"));
336
+ console.log("");
337
+ console.log(` ${chalk.bold("URL:")} ${chalk.cyan(url)}`);
338
+ console.log(` ${chalk.bold("Admin:")} ${adminEmail}`);
339
+ console.log(` ${chalk.bold("Password:")} (the one you just set)`);
340
+ if (customDomain) {
341
+ console.log("");
342
+ console.log(chalk.dim(` To use ${customDomain}:`));
343
+ console.log(chalk.dim(` Add CNAME: ${customDomain} \u2192 ${subdomain}.agenticmail.cloud`));
344
+ }
345
+ }
346
+
347
+ // src/setup/index.ts
348
+ async function runSetupWizard() {
349
+ const { default: inquirer } = await import("inquirer");
350
+ const { default: ora } = await import("ora");
351
+ const { default: chalk } = await import("chalk");
352
+ console.log("");
353
+ console.log(chalk.bold(" AgenticMail Enterprise"));
354
+ console.log(chalk.dim(" AI Agent Identity & Email for Organizations"));
355
+ console.log("");
356
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
357
+ console.log("");
358
+ const company = await promptCompanyInfo(inquirer, chalk);
359
+ const database = await promptDatabase(inquirer, chalk);
360
+ const { target: deployTarget } = await promptDeployment(inquirer, chalk);
361
+ const domain = await promptDomain(inquirer, chalk, deployTarget);
362
+ console.log("");
363
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
364
+ console.log("");
365
+ const result = await provision(
366
+ { company, database, deployTarget, domain },
367
+ ora,
368
+ chalk
369
+ );
370
+ if (!result.success) {
213
371
  console.error("");
214
- console.error(chalk.dim(err.stack));
372
+ console.error(chalk.red(` Setup failed: ${result.error}`));
373
+ console.error(chalk.dim(" Check your database connection and try again."));
215
374
  process.exit(1);
216
375
  }
376
+ console.log("");
217
377
  }
218
- main().catch(console.error);
378
+
379
+ // src/cli.ts
380
+ runSetupWizard().catch((err) => {
381
+ console.error("Fatal error:", err.message);
382
+ process.exit(1);
383
+ });