@agenticmail/core 0.4.0 → 0.5.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/index.d.ts +93 -6
- package/dist/index.js +732 -94
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1424,14 +1424,15 @@ declare class DependencyInstaller {
|
|
|
1424
1424
|
*/
|
|
1425
1425
|
installDocker(): Promise<void>;
|
|
1426
1426
|
/**
|
|
1427
|
-
* Attempt to start the Docker daemon.
|
|
1428
|
-
* On macOS:
|
|
1429
|
-
* On Linux: tries systemctl.
|
|
1427
|
+
* Attempt to start the Docker daemon using multiple strategies.
|
|
1428
|
+
* On macOS: tries Docker Desktop app, then docker CLI commands.
|
|
1429
|
+
* On Linux: tries systemctl, then dockerd direct, then snap.
|
|
1430
1430
|
*/
|
|
1431
1431
|
private startDockerDaemon;
|
|
1432
1432
|
/**
|
|
1433
|
-
* Wait for Docker daemon to be ready
|
|
1434
|
-
*
|
|
1433
|
+
* Wait for Docker daemon to be ready, with automatic retry strategies.
|
|
1434
|
+
* Tries multiple approaches to start Docker if the first one fails.
|
|
1435
|
+
* Reports progress as a percentage (0-100).
|
|
1435
1436
|
*/
|
|
1436
1437
|
private waitForDocker;
|
|
1437
1438
|
/**
|
|
@@ -1449,6 +1450,92 @@ declare class DependencyInstaller {
|
|
|
1449
1450
|
installAll(composePath: string): Promise<void>;
|
|
1450
1451
|
}
|
|
1451
1452
|
|
|
1453
|
+
interface ServiceStatus {
|
|
1454
|
+
installed: boolean;
|
|
1455
|
+
running: boolean;
|
|
1456
|
+
platform: 'launchd' | 'systemd' | 'unsupported';
|
|
1457
|
+
servicePath: string | null;
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* ServiceManager handles auto-start on boot for the AgenticMail API server.
|
|
1461
|
+
* - macOS: LaunchAgent plist (user-level, no sudo needed)
|
|
1462
|
+
* - Linux: systemd user service (user-level, no sudo needed)
|
|
1463
|
+
*/
|
|
1464
|
+
declare class ServiceManager {
|
|
1465
|
+
private os;
|
|
1466
|
+
/**
|
|
1467
|
+
* Get the path to the service file.
|
|
1468
|
+
*/
|
|
1469
|
+
private getServicePath;
|
|
1470
|
+
/**
|
|
1471
|
+
* Find the Node.js binary path.
|
|
1472
|
+
*/
|
|
1473
|
+
private getNodePath;
|
|
1474
|
+
/**
|
|
1475
|
+
* Find the API server entry point.
|
|
1476
|
+
* Searches common locations where agenticmail is installed.
|
|
1477
|
+
*/
|
|
1478
|
+
private getApiEntryPath;
|
|
1479
|
+
/**
|
|
1480
|
+
* Cache the API entry path so the service can find it later.
|
|
1481
|
+
*/
|
|
1482
|
+
cacheApiEntryPath(entryPath: string): void;
|
|
1483
|
+
/**
|
|
1484
|
+
* Get the current package version.
|
|
1485
|
+
*/
|
|
1486
|
+
private getVersion;
|
|
1487
|
+
/**
|
|
1488
|
+
* Generate a wrapper script that waits for Docker before starting the API.
|
|
1489
|
+
* This ensures AgenticMail doesn't fail on boot when Docker is still loading.
|
|
1490
|
+
*/
|
|
1491
|
+
private generateStartScript;
|
|
1492
|
+
/**
|
|
1493
|
+
* Generate the launchd plist content for macOS.
|
|
1494
|
+
* More robust than OpenClaw's plist:
|
|
1495
|
+
* - Wrapper script waits for Docker + Stalwart before starting
|
|
1496
|
+
* - KeepAlive: true (unconditional — always restart, not just on crash)
|
|
1497
|
+
* - SoftResourceLimits for file descriptors (email servers need many)
|
|
1498
|
+
* - StartInterval as backup heartbeat (checks every 5 min)
|
|
1499
|
+
* - Service version tracking in env vars
|
|
1500
|
+
*/
|
|
1501
|
+
private generatePlist;
|
|
1502
|
+
/**
|
|
1503
|
+
* Generate the systemd user service content for Linux.
|
|
1504
|
+
* More robust than basic services:
|
|
1505
|
+
* - Wrapper script waits for Docker + Stalwart
|
|
1506
|
+
* - Restart=always (unconditional)
|
|
1507
|
+
* - WatchdogSec for health monitoring
|
|
1508
|
+
* - File descriptor limits
|
|
1509
|
+
* - Proper dependency ordering
|
|
1510
|
+
*/
|
|
1511
|
+
private generateSystemdUnit;
|
|
1512
|
+
/**
|
|
1513
|
+
* Install the auto-start service.
|
|
1514
|
+
*/
|
|
1515
|
+
install(): {
|
|
1516
|
+
installed: boolean;
|
|
1517
|
+
message: string;
|
|
1518
|
+
};
|
|
1519
|
+
/**
|
|
1520
|
+
* Uninstall the auto-start service.
|
|
1521
|
+
*/
|
|
1522
|
+
uninstall(): {
|
|
1523
|
+
removed: boolean;
|
|
1524
|
+
message: string;
|
|
1525
|
+
};
|
|
1526
|
+
/**
|
|
1527
|
+
* Get the current service status.
|
|
1528
|
+
*/
|
|
1529
|
+
status(): ServiceStatus;
|
|
1530
|
+
/**
|
|
1531
|
+
* Reinstall the service (useful after config changes or updates).
|
|
1532
|
+
*/
|
|
1533
|
+
reinstall(): {
|
|
1534
|
+
installed: boolean;
|
|
1535
|
+
message: string;
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1452
1539
|
interface SetupConfig {
|
|
1453
1540
|
masterKey: string;
|
|
1454
1541
|
stalwart: {
|
|
@@ -1523,4 +1610,4 @@ declare class SetupManager {
|
|
|
1523
1610
|
isInitialized(): boolean;
|
|
1524
1611
|
}
|
|
1525
1612
|
|
|
1526
|
-
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, getDatabase, isInternalEmail, isValidPhoneNumber, normalizePhoneNumber, parseEmail, parseGoogleVoiceSms, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, startRelayBridge };
|
|
1613
|
+
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, getDatabase, isInternalEmail, isValidPhoneNumber, normalizePhoneNumber, parseEmail, parseGoogleVoiceSms, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, startRelayBridge };
|
package/dist/index.js
CHANGED
|
@@ -818,10 +818,10 @@ var StalwartAdmin = class {
|
|
|
818
818
|
return ["exec", "agenticmail-stalwart", "stalwart-cli", "-u", "http://localhost:8080", "-c", creds];
|
|
819
819
|
}
|
|
820
820
|
async updateSetting(key, value) {
|
|
821
|
-
const { execFileSync:
|
|
821
|
+
const { execFileSync: execFileSync4 } = await import("child_process");
|
|
822
822
|
const cli = this.cliArgs();
|
|
823
823
|
try {
|
|
824
|
-
|
|
824
|
+
execFileSync4(
|
|
825
825
|
"docker",
|
|
826
826
|
[...cli, "server", "delete-config", key],
|
|
827
827
|
{ timeout: 15e3, stdio: ["ignore", "pipe", "pipe"] }
|
|
@@ -829,13 +829,13 @@ var StalwartAdmin = class {
|
|
|
829
829
|
} catch {
|
|
830
830
|
}
|
|
831
831
|
try {
|
|
832
|
-
|
|
832
|
+
execFileSync4(
|
|
833
833
|
"docker",
|
|
834
834
|
[...cli, "server", "add-config", key, value],
|
|
835
835
|
{ timeout: 15e3, stdio: ["ignore", "pipe", "pipe"] }
|
|
836
836
|
);
|
|
837
837
|
} catch {
|
|
838
|
-
const output =
|
|
838
|
+
const output = execFileSync4(
|
|
839
839
|
"docker",
|
|
840
840
|
[...cli, "server", "list-config", key],
|
|
841
841
|
{ timeout: 15e3, stdio: ["ignore", "pipe", "pipe"] }
|
|
@@ -850,14 +850,14 @@ var StalwartAdmin = class {
|
|
|
850
850
|
* Critical for email deliverability — must match the sending domain.
|
|
851
851
|
*/
|
|
852
852
|
async setHostname(domain) {
|
|
853
|
-
const { readFileSync:
|
|
854
|
-
const { homedir:
|
|
855
|
-
const { join:
|
|
856
|
-
const configPath =
|
|
853
|
+
const { readFileSync: readFileSync4, writeFileSync: writeFileSync5 } = await import("fs");
|
|
854
|
+
const { homedir: homedir8 } = await import("os");
|
|
855
|
+
const { join: join9 } = await import("path");
|
|
856
|
+
const configPath = join9(homedir8(), ".agenticmail", "stalwart.toml");
|
|
857
857
|
try {
|
|
858
|
-
let config =
|
|
858
|
+
let config = readFileSync4(configPath, "utf-8");
|
|
859
859
|
config = config.replace(/^hostname\s*=\s*"[^"]*"/m, `hostname = "${domain}"`);
|
|
860
|
-
|
|
860
|
+
writeFileSync5(configPath, config);
|
|
861
861
|
console.log(`[Stalwart] Updated hostname to "${domain}" in stalwart.toml`);
|
|
862
862
|
} catch (err) {
|
|
863
863
|
throw new Error(`Failed to set config server.hostname=${domain}`);
|
|
@@ -866,15 +866,15 @@ var StalwartAdmin = class {
|
|
|
866
866
|
// --- DKIM ---
|
|
867
867
|
/** Path to the host-side stalwart.toml (mounted read-only into container) */
|
|
868
868
|
get configPath() {
|
|
869
|
-
const { homedir:
|
|
870
|
-
const { join:
|
|
871
|
-
return
|
|
869
|
+
const { homedir: homedir8 } = __require("os");
|
|
870
|
+
const { join: join9 } = __require("path");
|
|
871
|
+
return join9(homedir8(), ".agenticmail", "stalwart.toml");
|
|
872
872
|
}
|
|
873
873
|
/** Path to host-side DKIM key directory */
|
|
874
874
|
get dkimDir() {
|
|
875
|
-
const { homedir:
|
|
876
|
-
const { join:
|
|
877
|
-
return
|
|
875
|
+
const { homedir: homedir8 } = __require("os");
|
|
876
|
+
const { join: join9 } = __require("path");
|
|
877
|
+
return join9(homedir8(), ".agenticmail");
|
|
878
878
|
}
|
|
879
879
|
/**
|
|
880
880
|
* Create/reuse a DKIM signing key for a domain.
|
|
@@ -882,7 +882,7 @@ var StalwartAdmin = class {
|
|
|
882
882
|
* Returns the public key (base64, no headers) for DNS TXT record.
|
|
883
883
|
*/
|
|
884
884
|
async createDkimSignature(domain, selector = "agenticmail") {
|
|
885
|
-
const { execFileSync:
|
|
885
|
+
const { execFileSync: execFileSync4 } = await import("child_process");
|
|
886
886
|
const signatureId = `agenticmail-${domain.replace(/\./g, "-")}`;
|
|
887
887
|
const cli = this.cliArgs();
|
|
888
888
|
const existing = await this.getSettings(`signature.${signatureId}`);
|
|
@@ -890,7 +890,7 @@ var StalwartAdmin = class {
|
|
|
890
890
|
console.log(`[DKIM] Reusing existing signature "${signatureId}" from Stalwart DB`);
|
|
891
891
|
} else {
|
|
892
892
|
try {
|
|
893
|
-
|
|
893
|
+
execFileSync4("docker", [...cli, "server", "delete-config", `signature.${signatureId}`], {
|
|
894
894
|
timeout: 1e4,
|
|
895
895
|
stdio: ["ignore", "pipe", "pipe"]
|
|
896
896
|
});
|
|
@@ -898,7 +898,7 @@ var StalwartAdmin = class {
|
|
|
898
898
|
}
|
|
899
899
|
console.log(`[DKIM] Creating RSA signature for ${domain} via stalwart-cli`);
|
|
900
900
|
try {
|
|
901
|
-
|
|
901
|
+
execFileSync4("docker", [...cli, "dkim", "create", "rsa", domain, signatureId, selector], {
|
|
902
902
|
timeout: 15e3,
|
|
903
903
|
stdio: ["ignore", "pipe", "pipe"]
|
|
904
904
|
});
|
|
@@ -915,7 +915,7 @@ var StalwartAdmin = class {
|
|
|
915
915
|
["auth.dkim.sign.0001.else", "false"]
|
|
916
916
|
];
|
|
917
917
|
for (const [key, value] of rules) {
|
|
918
|
-
|
|
918
|
+
execFileSync4("docker", [...cli, "server", "add-config", key, value], {
|
|
919
919
|
timeout: 1e4,
|
|
920
920
|
stdio: ["ignore", "pipe", "pipe"]
|
|
921
921
|
});
|
|
@@ -923,7 +923,7 @@ var StalwartAdmin = class {
|
|
|
923
923
|
}
|
|
924
924
|
let publicKey;
|
|
925
925
|
try {
|
|
926
|
-
const output =
|
|
926
|
+
const output = execFileSync4("docker", [...cli, "dkim", "get-public-key", signatureId], {
|
|
927
927
|
timeout: 1e4,
|
|
928
928
|
stdio: ["ignore", "pipe", "pipe"]
|
|
929
929
|
}).toString();
|
|
@@ -934,7 +934,7 @@ var StalwartAdmin = class {
|
|
|
934
934
|
throw new Error(`Failed to get DKIM public key: ${err.message}`);
|
|
935
935
|
}
|
|
936
936
|
try {
|
|
937
|
-
|
|
937
|
+
execFileSync4("docker", [...cli, "server", "reload-config"], {
|
|
938
938
|
timeout: 1e4,
|
|
939
939
|
stdio: ["ignore", "pipe", "pipe"]
|
|
940
940
|
});
|
|
@@ -947,9 +947,9 @@ var StalwartAdmin = class {
|
|
|
947
947
|
* Restart the Stalwart Docker container and wait for it to be ready.
|
|
948
948
|
*/
|
|
949
949
|
async restartContainer() {
|
|
950
|
-
const { execFileSync:
|
|
950
|
+
const { execFileSync: execFileSync4 } = await import("child_process");
|
|
951
951
|
try {
|
|
952
|
-
|
|
952
|
+
execFileSync4("docker", ["restart", "agenticmail-stalwart"], { timeout: 3e4, stdio: ["ignore", "pipe", "pipe"] });
|
|
953
953
|
for (let i = 0; i < 15; i++) {
|
|
954
954
|
try {
|
|
955
955
|
const res = await fetch(`${this.baseUrl}/health`, { signal: AbortSignal.timeout(2e3) });
|
|
@@ -975,12 +975,12 @@ var StalwartAdmin = class {
|
|
|
975
975
|
* This bypasses the need for a PTR record on the sending IP.
|
|
976
976
|
*/
|
|
977
977
|
async configureOutboundRelay(config) {
|
|
978
|
-
const { readFileSync:
|
|
979
|
-
const { homedir:
|
|
980
|
-
const { join:
|
|
978
|
+
const { readFileSync: readFileSync4, writeFileSync: writeFileSync5 } = await import("fs");
|
|
979
|
+
const { homedir: homedir8 } = await import("os");
|
|
980
|
+
const { join: join9 } = await import("path");
|
|
981
981
|
const routeName = config.routeName ?? "gmail";
|
|
982
|
-
const tomlPath =
|
|
983
|
-
let toml =
|
|
982
|
+
const tomlPath = join9(homedir8(), ".agenticmail", "stalwart.toml");
|
|
983
|
+
let toml = readFileSync4(tomlPath, "utf-8");
|
|
984
984
|
toml = toml.replace(/\n\[queue\.route\.gmail\][\s\S]*?(?=\n\[|$)/, "");
|
|
985
985
|
toml = toml.replace(/\n\[queue\.strategy\][\s\S]*?(?=\n\[|$)/, "");
|
|
986
986
|
toml += `
|
|
@@ -999,7 +999,7 @@ auth.secret = "${config.password}"
|
|
|
999
999
|
route = [ { if = "is_local_domain('', rcpt_domain)", then = "'local'" },
|
|
1000
1000
|
{ else = "'${routeName}'" } ]
|
|
1001
1001
|
`;
|
|
1002
|
-
|
|
1002
|
+
writeFileSync5(tomlPath, toml, "utf-8");
|
|
1003
1003
|
await this.restartContainer();
|
|
1004
1004
|
}
|
|
1005
1005
|
};
|
|
@@ -3819,8 +3819,8 @@ var CloudflareClient = class {
|
|
|
3819
3819
|
let available = false;
|
|
3820
3820
|
if (result.supported_tld && !hasRegistration) {
|
|
3821
3821
|
try {
|
|
3822
|
-
const { execFileSync:
|
|
3823
|
-
const whoisOutput =
|
|
3822
|
+
const { execFileSync: execFileSync4 } = await import("child_process");
|
|
3823
|
+
const whoisOutput = execFileSync4("whois", [domain], { timeout: 1e4, stdio: ["ignore", "pipe", "pipe"] }).toString().toLowerCase();
|
|
3824
3824
|
available = whoisOutput.includes("domain not found") || whoisOutput.includes("no match") || whoisOutput.includes("not found") || whoisOutput.includes("no data found") || whoisOutput.includes("status: free") || whoisOutput.includes("no entries found");
|
|
3825
3825
|
} catch {
|
|
3826
3826
|
available = false;
|
|
@@ -4284,8 +4284,8 @@ var TunnelManager = class {
|
|
|
4284
4284
|
return this.binPath;
|
|
4285
4285
|
}
|
|
4286
4286
|
try {
|
|
4287
|
-
const { execFileSync:
|
|
4288
|
-
const sysPath =
|
|
4287
|
+
const { execFileSync: execFileSync4 } = await import("child_process");
|
|
4288
|
+
const sysPath = execFileSync4("which", ["cloudflared"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
4289
4289
|
if (sysPath && existsSync2(sysPath)) {
|
|
4290
4290
|
this.binPath = sysPath;
|
|
4291
4291
|
return sysPath;
|
|
@@ -5177,12 +5177,12 @@ var GatewayManager = class {
|
|
|
5177
5177
|
zone = await this.cfClient.createZone(domain);
|
|
5178
5178
|
}
|
|
5179
5179
|
const existingRecords = await this.cfClient.listDnsRecords(zone.id);
|
|
5180
|
-
const { homedir:
|
|
5181
|
-
const backupDir = join4(
|
|
5180
|
+
const { homedir: homedir8 } = await import("os");
|
|
5181
|
+
const backupDir = join4(homedir8(), ".agenticmail");
|
|
5182
5182
|
const backupPath = join4(backupDir, `dns-backup-${domain}-${Date.now()}.json`);
|
|
5183
|
-
const { writeFileSync:
|
|
5184
|
-
|
|
5185
|
-
|
|
5183
|
+
const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync5 } = await import("fs");
|
|
5184
|
+
mkdirSync5(backupDir, { recursive: true });
|
|
5185
|
+
writeFileSync5(backupPath, JSON.stringify({
|
|
5186
5186
|
domain,
|
|
5187
5187
|
zoneId: zone.id,
|
|
5188
5188
|
backedUpAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5813,9 +5813,9 @@ var RELAY_PRESETS = {
|
|
|
5813
5813
|
|
|
5814
5814
|
// src/setup/index.ts
|
|
5815
5815
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5816
|
-
import { existsSync as
|
|
5817
|
-
import { join as
|
|
5818
|
-
import { homedir as
|
|
5816
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, chmodSync } from "fs";
|
|
5817
|
+
import { join as join8 } from "path";
|
|
5818
|
+
import { homedir as homedir7 } from "os";
|
|
5819
5819
|
|
|
5820
5820
|
// src/setup/deps.ts
|
|
5821
5821
|
import { execFileSync } from "child_process";
|
|
@@ -5893,11 +5893,129 @@ var DependencyChecker = class {
|
|
|
5893
5893
|
};
|
|
5894
5894
|
|
|
5895
5895
|
// src/setup/installer.ts
|
|
5896
|
-
import { execFileSync as execFileSync2, execSync } from "child_process";
|
|
5896
|
+
import { execFileSync as execFileSync2, execSync, spawn as spawnChild } from "child_process";
|
|
5897
5897
|
import { existsSync as existsSync4 } from "fs";
|
|
5898
5898
|
import { writeFile, rename, chmod as chmod2, mkdir as mkdir2, unlink } from "fs/promises";
|
|
5899
5899
|
import { join as join6 } from "path";
|
|
5900
5900
|
import { homedir as homedir5, platform as platform2, arch as arch2 } from "os";
|
|
5901
|
+
function runWithRollingOutput(command, args, opts = {}) {
|
|
5902
|
+
const maxLines = opts.maxLines ?? 20;
|
|
5903
|
+
const timeout = opts.timeout ?? 3e5;
|
|
5904
|
+
return new Promise((resolve, reject) => {
|
|
5905
|
+
const child = spawnChild(command, args, {
|
|
5906
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5907
|
+
timeout
|
|
5908
|
+
});
|
|
5909
|
+
const lines = [];
|
|
5910
|
+
let displayedCount = 0;
|
|
5911
|
+
let fullOutput = "";
|
|
5912
|
+
const processData = (data) => {
|
|
5913
|
+
const text = data.toString();
|
|
5914
|
+
fullOutput += text;
|
|
5915
|
+
const newLines = text.split("\n");
|
|
5916
|
+
for (const line of newLines) {
|
|
5917
|
+
const trimmed = line.trimEnd();
|
|
5918
|
+
if (!trimmed) continue;
|
|
5919
|
+
lines.push(trimmed);
|
|
5920
|
+
if (displayedCount > 0) {
|
|
5921
|
+
const toClear = Math.min(displayedCount, maxLines);
|
|
5922
|
+
process.stdout.write(`\x1B[${toClear}A`);
|
|
5923
|
+
for (let i = 0; i < toClear; i++) {
|
|
5924
|
+
process.stdout.write("\x1B[2K\n");
|
|
5925
|
+
}
|
|
5926
|
+
process.stdout.write(`\x1B[${toClear}A`);
|
|
5927
|
+
}
|
|
5928
|
+
const visible = lines.slice(-maxLines);
|
|
5929
|
+
for (const vLine of visible) {
|
|
5930
|
+
process.stdout.write(` \x1B[90m${vLine.slice(0, 100)}\x1B[0m
|
|
5931
|
+
`);
|
|
5932
|
+
}
|
|
5933
|
+
displayedCount = visible.length;
|
|
5934
|
+
}
|
|
5935
|
+
};
|
|
5936
|
+
child.stdout?.on("data", processData);
|
|
5937
|
+
child.stderr?.on("data", processData);
|
|
5938
|
+
child.on("close", (code) => {
|
|
5939
|
+
if (displayedCount > 0) {
|
|
5940
|
+
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
5941
|
+
for (let i = 0; i < displayedCount; i++) {
|
|
5942
|
+
process.stdout.write("\x1B[2K\n");
|
|
5943
|
+
}
|
|
5944
|
+
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
5945
|
+
}
|
|
5946
|
+
resolve({ exitCode: code ?? 1, fullOutput });
|
|
5947
|
+
});
|
|
5948
|
+
child.on("error", (err) => {
|
|
5949
|
+
if (displayedCount > 0) {
|
|
5950
|
+
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
5951
|
+
for (let i = 0; i < displayedCount; i++) {
|
|
5952
|
+
process.stdout.write("\x1B[2K\n");
|
|
5953
|
+
}
|
|
5954
|
+
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
5955
|
+
}
|
|
5956
|
+
reject(err);
|
|
5957
|
+
});
|
|
5958
|
+
});
|
|
5959
|
+
}
|
|
5960
|
+
function runShellWithRollingOutput(cmd, opts = {}) {
|
|
5961
|
+
const maxLines = opts.maxLines ?? 20;
|
|
5962
|
+
const timeout = opts.timeout ?? 3e5;
|
|
5963
|
+
return new Promise((resolve, reject) => {
|
|
5964
|
+
const child = spawnChild("sh", ["-c", cmd], {
|
|
5965
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5966
|
+
timeout
|
|
5967
|
+
});
|
|
5968
|
+
const lines = [];
|
|
5969
|
+
let displayedCount = 0;
|
|
5970
|
+
let fullOutput = "";
|
|
5971
|
+
const processData = (data) => {
|
|
5972
|
+
const text = data.toString();
|
|
5973
|
+
fullOutput += text;
|
|
5974
|
+
const newLines = text.split("\n");
|
|
5975
|
+
for (const line of newLines) {
|
|
5976
|
+
const trimmed = line.trimEnd();
|
|
5977
|
+
if (!trimmed) continue;
|
|
5978
|
+
lines.push(trimmed);
|
|
5979
|
+
if (displayedCount > 0) {
|
|
5980
|
+
const toClear = Math.min(displayedCount, maxLines);
|
|
5981
|
+
process.stdout.write(`\x1B[${toClear}A`);
|
|
5982
|
+
for (let i = 0; i < toClear; i++) {
|
|
5983
|
+
process.stdout.write("\x1B[2K\n");
|
|
5984
|
+
}
|
|
5985
|
+
process.stdout.write(`\x1B[${toClear}A`);
|
|
5986
|
+
}
|
|
5987
|
+
const visible = lines.slice(-maxLines);
|
|
5988
|
+
for (const vLine of visible) {
|
|
5989
|
+
process.stdout.write(` \x1B[90m${vLine.slice(0, 100)}\x1B[0m
|
|
5990
|
+
`);
|
|
5991
|
+
}
|
|
5992
|
+
displayedCount = visible.length;
|
|
5993
|
+
}
|
|
5994
|
+
};
|
|
5995
|
+
child.stdout?.on("data", processData);
|
|
5996
|
+
child.stderr?.on("data", processData);
|
|
5997
|
+
child.on("close", (code) => {
|
|
5998
|
+
if (displayedCount > 0) {
|
|
5999
|
+
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
6000
|
+
for (let i = 0; i < displayedCount; i++) {
|
|
6001
|
+
process.stdout.write("\x1B[2K\n");
|
|
6002
|
+
}
|
|
6003
|
+
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
6004
|
+
}
|
|
6005
|
+
resolve({ exitCode: code ?? 1, fullOutput });
|
|
6006
|
+
});
|
|
6007
|
+
child.on("error", (err) => {
|
|
6008
|
+
if (displayedCount > 0) {
|
|
6009
|
+
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
6010
|
+
for (let i = 0; i < displayedCount; i++) {
|
|
6011
|
+
process.stdout.write("\x1B[2K\n");
|
|
6012
|
+
}
|
|
6013
|
+
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
6014
|
+
}
|
|
6015
|
+
reject(err);
|
|
6016
|
+
});
|
|
6017
|
+
});
|
|
6018
|
+
}
|
|
5901
6019
|
var DependencyInstaller = class {
|
|
5902
6020
|
onProgress;
|
|
5903
6021
|
constructor(onProgress) {
|
|
@@ -5935,14 +6053,20 @@ var DependencyInstaller = class {
|
|
|
5935
6053
|
} catch {
|
|
5936
6054
|
throw new Error("Homebrew is required to install Docker on macOS. Install it from https://brew.sh then try again.");
|
|
5937
6055
|
}
|
|
5938
|
-
|
|
6056
|
+
const brewResult = await runWithRollingOutput("brew", ["install", "--cask", "docker"], { timeout: 3e5 });
|
|
6057
|
+
if (brewResult.exitCode !== 0) {
|
|
6058
|
+
throw new Error("Failed to install Docker via Homebrew. Try: brew install --cask docker");
|
|
6059
|
+
}
|
|
5939
6060
|
this.onProgress("Docker installed. Starting Docker Desktop...");
|
|
5940
6061
|
this.startDockerDaemon();
|
|
5941
6062
|
await this.waitForDocker();
|
|
5942
6063
|
} else if (os === "linux") {
|
|
5943
6064
|
this.onProgress("Installing Docker...");
|
|
5944
6065
|
try {
|
|
5945
|
-
|
|
6066
|
+
const result = await runShellWithRollingOutput("curl -fsSL https://get.docker.com | sh", { timeout: 3e5 });
|
|
6067
|
+
if (result.exitCode !== 0) {
|
|
6068
|
+
throw new Error("Install script failed");
|
|
6069
|
+
}
|
|
5946
6070
|
} catch {
|
|
5947
6071
|
throw new Error("Failed to install Docker. Install it manually: https://docs.docker.com/get-docker/");
|
|
5948
6072
|
}
|
|
@@ -5952,48 +6076,123 @@ var DependencyInstaller = class {
|
|
|
5952
6076
|
}
|
|
5953
6077
|
}
|
|
5954
6078
|
/**
|
|
5955
|
-
* Attempt to start the Docker daemon.
|
|
5956
|
-
* On macOS:
|
|
5957
|
-
* On Linux: tries systemctl.
|
|
6079
|
+
* Attempt to start the Docker daemon using multiple strategies.
|
|
6080
|
+
* On macOS: tries Docker Desktop app, then docker CLI commands.
|
|
6081
|
+
* On Linux: tries systemctl, then dockerd direct, then snap.
|
|
5958
6082
|
*/
|
|
5959
|
-
startDockerDaemon() {
|
|
6083
|
+
startDockerDaemon(strategy) {
|
|
5960
6084
|
const os = platform2();
|
|
5961
6085
|
if (os === "darwin") {
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
6086
|
+
switch (strategy) {
|
|
6087
|
+
case "cli":
|
|
6088
|
+
try {
|
|
6089
|
+
execSync("docker context use default 2>/dev/null; docker info", { timeout: 5e3, stdio: "ignore" });
|
|
6090
|
+
} catch {
|
|
6091
|
+
}
|
|
6092
|
+
break;
|
|
6093
|
+
case "reopen":
|
|
6094
|
+
try {
|
|
6095
|
+
execSync(`osascript -e 'quit app "Docker"'`, { timeout: 5e3, stdio: "ignore" });
|
|
6096
|
+
} catch {
|
|
6097
|
+
}
|
|
6098
|
+
try {
|
|
6099
|
+
execFileSync2("sleep", ["2"], { timeout: 5e3, stdio: "ignore" });
|
|
6100
|
+
} catch {
|
|
6101
|
+
}
|
|
6102
|
+
try {
|
|
6103
|
+
execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
|
|
6104
|
+
} catch {
|
|
6105
|
+
}
|
|
6106
|
+
break;
|
|
6107
|
+
case "background":
|
|
6108
|
+
try {
|
|
6109
|
+
const appBin = "/Applications/Docker.app/Contents/MacOS/Docker";
|
|
6110
|
+
if (existsSync4(appBin)) {
|
|
6111
|
+
execSync(`"${appBin}" &`, { timeout: 5e3, stdio: "ignore", shell: "sh" });
|
|
6112
|
+
}
|
|
6113
|
+
} catch {
|
|
6114
|
+
}
|
|
6115
|
+
break;
|
|
6116
|
+
default:
|
|
6117
|
+
try {
|
|
6118
|
+
execFileSync2("open", ["-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
|
|
6119
|
+
} catch {
|
|
6120
|
+
}
|
|
5965
6121
|
}
|
|
5966
6122
|
} else if (os === "linux") {
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
6123
|
+
switch (strategy) {
|
|
6124
|
+
case "snap":
|
|
6125
|
+
try {
|
|
6126
|
+
execFileSync2("sudo", ["snap", "start", "docker"], { timeout: 15e3, stdio: "ignore" });
|
|
6127
|
+
} catch {
|
|
6128
|
+
}
|
|
6129
|
+
break;
|
|
6130
|
+
case "service":
|
|
6131
|
+
try {
|
|
6132
|
+
execFileSync2("sudo", ["service", "docker", "start"], { timeout: 15e3, stdio: "ignore" });
|
|
6133
|
+
} catch {
|
|
6134
|
+
}
|
|
6135
|
+
break;
|
|
6136
|
+
default:
|
|
6137
|
+
try {
|
|
6138
|
+
execFileSync2("sudo", ["systemctl", "start", "docker"], { timeout: 15e3, stdio: "ignore" });
|
|
6139
|
+
} catch {
|
|
6140
|
+
}
|
|
5970
6141
|
}
|
|
5971
6142
|
}
|
|
5972
6143
|
}
|
|
5973
6144
|
/**
|
|
5974
|
-
* Wait for Docker daemon to be ready
|
|
5975
|
-
*
|
|
6145
|
+
* Wait for Docker daemon to be ready, with automatic retry strategies.
|
|
6146
|
+
* Tries multiple approaches to start Docker if the first one fails.
|
|
6147
|
+
* Reports progress as a percentage (0-100).
|
|
5976
6148
|
*/
|
|
5977
6149
|
async waitForDocker() {
|
|
5978
|
-
|
|
5979
|
-
const
|
|
6150
|
+
const os = platform2();
|
|
6151
|
+
const strategies = os === "darwin" ? ["default", "cli", "reopen", "background"] : ["default", "service", "snap"];
|
|
6152
|
+
const totalTime = 24e4;
|
|
6153
|
+
const perStrategyTime = Math.floor(totalTime / strategies.length);
|
|
5980
6154
|
const start = Date.now();
|
|
5981
|
-
let
|
|
5982
|
-
|
|
6155
|
+
let strategyIdx = 0;
|
|
6156
|
+
this.onProgress("__progress__:0:Starting Docker...");
|
|
6157
|
+
while (Date.now() - start < totalTime) {
|
|
5983
6158
|
try {
|
|
5984
6159
|
execFileSync2("docker", ["info"], { timeout: 5e3, stdio: "ignore" });
|
|
6160
|
+
this.onProgress("__progress__:100:Docker is ready!");
|
|
5985
6161
|
return;
|
|
5986
6162
|
} catch {
|
|
5987
6163
|
}
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
6164
|
+
const elapsed = Date.now() - start;
|
|
6165
|
+
const pct = Math.min(95, Math.round(elapsed / totalTime * 100));
|
|
6166
|
+
const currentStrategyElapsed = elapsed - strategyIdx * perStrategyTime;
|
|
6167
|
+
if (currentStrategyElapsed >= perStrategyTime && strategyIdx < strategies.length - 1) {
|
|
6168
|
+
strategyIdx++;
|
|
6169
|
+
const strategy = strategies[strategyIdx];
|
|
6170
|
+
const msgs = {
|
|
6171
|
+
cli: "Trying Docker CLI...",
|
|
6172
|
+
reopen: "Restarting Docker Desktop...",
|
|
6173
|
+
background: "Trying direct launch...",
|
|
6174
|
+
service: "Trying service command...",
|
|
6175
|
+
snap: "Trying snap..."
|
|
6176
|
+
};
|
|
6177
|
+
this.onProgress(`__progress__:${pct}:${msgs[strategy] || "Trying another approach..."}`);
|
|
6178
|
+
this.startDockerDaemon(strategy);
|
|
6179
|
+
} else {
|
|
6180
|
+
const msgs = [
|
|
6181
|
+
"Starting Docker...",
|
|
6182
|
+
"Waiting for Docker engine...",
|
|
6183
|
+
"Docker is loading...",
|
|
6184
|
+
"Almost there...",
|
|
6185
|
+
"Still starting up...",
|
|
6186
|
+
"First launch takes a bit longer...",
|
|
6187
|
+
"Hang tight..."
|
|
6188
|
+
];
|
|
6189
|
+
const msgIdx = Math.floor(elapsed / 1e4) % msgs.length;
|
|
6190
|
+
this.onProgress(`__progress__:${pct}:${msgs[msgIdx]}`);
|
|
5992
6191
|
}
|
|
5993
6192
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
5994
6193
|
}
|
|
5995
6194
|
throw new Error(
|
|
5996
|
-
"
|
|
6195
|
+
"DOCKER_MANUAL_START"
|
|
5997
6196
|
);
|
|
5998
6197
|
}
|
|
5999
6198
|
/**
|
|
@@ -6010,14 +6209,17 @@ var DependencyInstaller = class {
|
|
|
6010
6209
|
this.startDockerDaemon();
|
|
6011
6210
|
await this.waitForDocker();
|
|
6012
6211
|
}
|
|
6013
|
-
this.onProgress("
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
}
|
|
6212
|
+
this.onProgress("__progress__:10:Pulling mail server image...");
|
|
6213
|
+
const composeResult = await runWithRollingOutput("docker", ["compose", "-f", composePath, "up", "-d"], { timeout: 12e4 });
|
|
6214
|
+
if (composeResult.exitCode !== 0) {
|
|
6215
|
+
throw new Error("Failed to start mail server container. Check Docker is running.");
|
|
6216
|
+
}
|
|
6217
|
+
this.onProgress("__progress__:60:Waiting for mail server to start...");
|
|
6018
6218
|
const maxWait = 3e4;
|
|
6019
6219
|
const start = Date.now();
|
|
6020
6220
|
while (Date.now() - start < maxWait) {
|
|
6221
|
+
const pct = 60 + Math.round((Date.now() - start) / maxWait * 35);
|
|
6222
|
+
this.onProgress(`__progress__:${Math.min(95, pct)}:Starting mail server...`);
|
|
6021
6223
|
try {
|
|
6022
6224
|
const output = execFileSync2(
|
|
6023
6225
|
"docker",
|
|
@@ -6025,6 +6227,7 @@ var DependencyInstaller = class {
|
|
|
6025
6227
|
{ timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
|
|
6026
6228
|
).toString().trim();
|
|
6027
6229
|
if (output.toLowerCase().includes("up")) {
|
|
6230
|
+
this.onProgress("__progress__:100:Mail server ready!");
|
|
6028
6231
|
return;
|
|
6029
6232
|
}
|
|
6030
6233
|
} catch {
|
|
@@ -6098,6 +6301,440 @@ var DependencyInstaller = class {
|
|
|
6098
6301
|
}
|
|
6099
6302
|
};
|
|
6100
6303
|
|
|
6304
|
+
// src/setup/service.ts
|
|
6305
|
+
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
6306
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
6307
|
+
import { join as join7 } from "path";
|
|
6308
|
+
import { homedir as homedir6, platform as platform3 } from "os";
|
|
6309
|
+
var PLIST_LABEL = "com.agenticmail.server";
|
|
6310
|
+
var SYSTEMD_UNIT = "agenticmail.service";
|
|
6311
|
+
var ServiceManager = class {
|
|
6312
|
+
os = platform3();
|
|
6313
|
+
/**
|
|
6314
|
+
* Get the path to the service file.
|
|
6315
|
+
*/
|
|
6316
|
+
getServicePath() {
|
|
6317
|
+
if (this.os === "darwin") {
|
|
6318
|
+
return join7(homedir6(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
6319
|
+
} else {
|
|
6320
|
+
return join7(homedir6(), ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
/**
|
|
6324
|
+
* Find the Node.js binary path.
|
|
6325
|
+
*/
|
|
6326
|
+
getNodePath() {
|
|
6327
|
+
try {
|
|
6328
|
+
return execFileSync3("which", ["node"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
6329
|
+
} catch {
|
|
6330
|
+
return process.execPath;
|
|
6331
|
+
}
|
|
6332
|
+
}
|
|
6333
|
+
/**
|
|
6334
|
+
* Find the API server entry point.
|
|
6335
|
+
* Searches common locations where agenticmail is installed.
|
|
6336
|
+
*/
|
|
6337
|
+
getApiEntryPath() {
|
|
6338
|
+
const searchDirs = [
|
|
6339
|
+
// Global npm install
|
|
6340
|
+
join7(homedir6(), "node_modules", "agenticmail"),
|
|
6341
|
+
// npx cache / global prefix
|
|
6342
|
+
...(() => {
|
|
6343
|
+
try {
|
|
6344
|
+
const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
6345
|
+
return [
|
|
6346
|
+
join7(prefix, "lib", "node_modules", "agenticmail"),
|
|
6347
|
+
join7(prefix, "node_modules", "agenticmail")
|
|
6348
|
+
];
|
|
6349
|
+
} catch {
|
|
6350
|
+
return [];
|
|
6351
|
+
}
|
|
6352
|
+
})(),
|
|
6353
|
+
// Homebrew on macOS
|
|
6354
|
+
"/opt/homebrew/lib/node_modules/agenticmail",
|
|
6355
|
+
"/usr/local/lib/node_modules/agenticmail"
|
|
6356
|
+
];
|
|
6357
|
+
for (const base of searchDirs) {
|
|
6358
|
+
const apiPaths = [
|
|
6359
|
+
join7(base, "node_modules", "@agenticmail", "api", "dist", "index.js"),
|
|
6360
|
+
join7(base, "..", "@agenticmail", "api", "dist", "index.js")
|
|
6361
|
+
];
|
|
6362
|
+
for (const p of apiPaths) {
|
|
6363
|
+
if (existsSync5(p)) return p;
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
const dataDir = join7(homedir6(), ".agenticmail");
|
|
6367
|
+
const entryCache = join7(dataDir, "api-entry.path");
|
|
6368
|
+
if (existsSync5(entryCache)) {
|
|
6369
|
+
const cached = readFileSync2(entryCache, "utf-8").trim();
|
|
6370
|
+
if (existsSync5(cached)) return cached;
|
|
6371
|
+
}
|
|
6372
|
+
throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
|
|
6373
|
+
}
|
|
6374
|
+
/**
|
|
6375
|
+
* Cache the API entry path so the service can find it later.
|
|
6376
|
+
*/
|
|
6377
|
+
cacheApiEntryPath(entryPath) {
|
|
6378
|
+
const dataDir = join7(homedir6(), ".agenticmail");
|
|
6379
|
+
if (!existsSync5(dataDir)) mkdirSync3(dataDir, { recursive: true });
|
|
6380
|
+
writeFileSync3(join7(dataDir, "api-entry.path"), entryPath);
|
|
6381
|
+
}
|
|
6382
|
+
/**
|
|
6383
|
+
* Get the current package version.
|
|
6384
|
+
*/
|
|
6385
|
+
getVersion() {
|
|
6386
|
+
try {
|
|
6387
|
+
const pkgPaths = [
|
|
6388
|
+
join7(homedir6(), "node_modules", "agenticmail", "package.json"),
|
|
6389
|
+
join7(homedir6(), ".agenticmail", "package-version.json")
|
|
6390
|
+
];
|
|
6391
|
+
try {
|
|
6392
|
+
const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
6393
|
+
pkgPaths.push(join7(prefix, "lib", "node_modules", "agenticmail", "package.json"));
|
|
6394
|
+
} catch {
|
|
6395
|
+
}
|
|
6396
|
+
for (const p of pkgPaths) {
|
|
6397
|
+
if (existsSync5(p)) {
|
|
6398
|
+
const pkg = JSON.parse(readFileSync2(p, "utf-8"));
|
|
6399
|
+
if (pkg.version) return pkg.version;
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
} catch {
|
|
6403
|
+
}
|
|
6404
|
+
return "unknown";
|
|
6405
|
+
}
|
|
6406
|
+
/**
|
|
6407
|
+
* Generate a wrapper script that waits for Docker before starting the API.
|
|
6408
|
+
* This ensures AgenticMail doesn't fail on boot when Docker is still loading.
|
|
6409
|
+
*/
|
|
6410
|
+
generateStartScript(nodePath, apiEntry) {
|
|
6411
|
+
const scriptPath = join7(homedir6(), ".agenticmail", "bin", "start-server.sh");
|
|
6412
|
+
const scriptDir = join7(homedir6(), ".agenticmail", "bin");
|
|
6413
|
+
if (!existsSync5(scriptDir)) mkdirSync3(scriptDir, { recursive: true });
|
|
6414
|
+
const script = [
|
|
6415
|
+
"#!/bin/bash",
|
|
6416
|
+
"# AgenticMail auto-start script",
|
|
6417
|
+
"# Waits for Docker to be ready, then starts the API server.",
|
|
6418
|
+
"",
|
|
6419
|
+
'LOG_DIR="$HOME/.agenticmail/logs"',
|
|
6420
|
+
'mkdir -p "$LOG_DIR"',
|
|
6421
|
+
"",
|
|
6422
|
+
"log() {",
|
|
6423
|
+
` echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_DIR/startup.log"`,
|
|
6424
|
+
"}",
|
|
6425
|
+
"",
|
|
6426
|
+
'log "AgenticMail starting..."',
|
|
6427
|
+
"",
|
|
6428
|
+
"# Wait for Docker daemon (up to 10 minutes \u2014 Docker Desktop can be very slow on first boot)",
|
|
6429
|
+
"MAX_WAIT=600",
|
|
6430
|
+
"WAITED=0",
|
|
6431
|
+
"while ! docker info >/dev/null 2>&1; do",
|
|
6432
|
+
" if [ $WAITED -ge $MAX_WAIT ]; then",
|
|
6433
|
+
' log "ERROR: Docker did not start after ${MAX_WAIT}s. Exiting."',
|
|
6434
|
+
" exit 1",
|
|
6435
|
+
" fi",
|
|
6436
|
+
" sleep 5",
|
|
6437
|
+
" WAITED=$((WAITED + 5))",
|
|
6438
|
+
' log "Waiting for Docker... (${WAITED}s)"',
|
|
6439
|
+
"done",
|
|
6440
|
+
'log "Docker is ready (waited ${WAITED}s)"',
|
|
6441
|
+
"",
|
|
6442
|
+
"# Wait for Stalwart container (up to 60s)",
|
|
6443
|
+
"MAX_STALWART=60",
|
|
6444
|
+
"WAITED=0",
|
|
6445
|
+
'while ! docker ps --filter "name=agenticmail-stalwart" --format "{{.Status}}" 2>/dev/null | grep -qi "up"; do',
|
|
6446
|
+
" if [ $WAITED -ge $MAX_STALWART ]; then",
|
|
6447
|
+
' log "WARNING: Stalwart not running. Attempting to start..."',
|
|
6448
|
+
' COMPOSE="$HOME/.agenticmail/docker-compose.yml"',
|
|
6449
|
+
' if [ -f "$COMPOSE" ]; then',
|
|
6450
|
+
' docker compose -f "$COMPOSE" up -d 2>>"$LOG_DIR/startup.log"',
|
|
6451
|
+
" sleep 5",
|
|
6452
|
+
" fi",
|
|
6453
|
+
" break",
|
|
6454
|
+
" fi",
|
|
6455
|
+
" sleep 3",
|
|
6456
|
+
" WAITED=$((WAITED + 3))",
|
|
6457
|
+
"done",
|
|
6458
|
+
'log "Stalwart check complete"',
|
|
6459
|
+
"",
|
|
6460
|
+
"# Start the API server",
|
|
6461
|
+
`log "Starting API server: ${nodePath} ${apiEntry}"`,
|
|
6462
|
+
`exec "${nodePath}" "${apiEntry}"`
|
|
6463
|
+
].join("\n") + "\n";
|
|
6464
|
+
writeFileSync3(scriptPath, script, { mode: 493 });
|
|
6465
|
+
return scriptPath;
|
|
6466
|
+
}
|
|
6467
|
+
/**
|
|
6468
|
+
* Generate the launchd plist content for macOS.
|
|
6469
|
+
* More robust than OpenClaw's plist:
|
|
6470
|
+
* - Wrapper script waits for Docker + Stalwart before starting
|
|
6471
|
+
* - KeepAlive: true (unconditional — always restart, not just on crash)
|
|
6472
|
+
* - SoftResourceLimits for file descriptors (email servers need many)
|
|
6473
|
+
* - StartInterval as backup heartbeat (checks every 5 min)
|
|
6474
|
+
* - Service version tracking in env vars
|
|
6475
|
+
*/
|
|
6476
|
+
generatePlist(nodePath, apiEntry, configPath) {
|
|
6477
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
6478
|
+
const logDir = join7(homedir6(), ".agenticmail", "logs");
|
|
6479
|
+
if (!existsSync5(logDir)) mkdirSync3(logDir, { recursive: true });
|
|
6480
|
+
const version = this.getVersion();
|
|
6481
|
+
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
6482
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
6483
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
6484
|
+
<plist version="1.0">
|
|
6485
|
+
<dict>
|
|
6486
|
+
<key>Label</key>
|
|
6487
|
+
<string>${PLIST_LABEL}</string>
|
|
6488
|
+
|
|
6489
|
+
<key>Comment</key>
|
|
6490
|
+
<string>AgenticMail API Server (v${version})</string>
|
|
6491
|
+
|
|
6492
|
+
<key>ProgramArguments</key>
|
|
6493
|
+
<array>
|
|
6494
|
+
<string>${startScript}</string>
|
|
6495
|
+
</array>
|
|
6496
|
+
|
|
6497
|
+
<key>EnvironmentVariables</key>
|
|
6498
|
+
<dict>
|
|
6499
|
+
<key>HOME</key>
|
|
6500
|
+
<string>${homedir6()}</string>
|
|
6501
|
+
<key>AGENTICMAIL_DATA_DIR</key>
|
|
6502
|
+
<string>${config.dataDir || join7(homedir6(), ".agenticmail")}</string>
|
|
6503
|
+
<key>AGENTICMAIL_MASTER_KEY</key>
|
|
6504
|
+
<string>${config.masterKey}</string>
|
|
6505
|
+
<key>STALWART_ADMIN_USER</key>
|
|
6506
|
+
<string>${config.stalwart.adminUser}</string>
|
|
6507
|
+
<key>STALWART_ADMIN_PASSWORD</key>
|
|
6508
|
+
<string>${config.stalwart.adminPassword}</string>
|
|
6509
|
+
<key>STALWART_URL</key>
|
|
6510
|
+
<string>${config.stalwart.url}</string>
|
|
6511
|
+
<key>AGENTICMAIL_API_PORT</key>
|
|
6512
|
+
<string>${String(config.api.port)}</string>
|
|
6513
|
+
<key>AGENTICMAIL_API_HOST</key>
|
|
6514
|
+
<string>${config.api.host}</string>
|
|
6515
|
+
<key>SMTP_HOST</key>
|
|
6516
|
+
<string>${config.smtp.host}</string>
|
|
6517
|
+
<key>SMTP_PORT</key>
|
|
6518
|
+
<string>${String(config.smtp.port)}</string>
|
|
6519
|
+
<key>IMAP_HOST</key>
|
|
6520
|
+
<string>${config.imap.host}</string>
|
|
6521
|
+
<key>IMAP_PORT</key>
|
|
6522
|
+
<string>${String(config.imap.port)}</string>
|
|
6523
|
+
<key>PATH</key>
|
|
6524
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
6525
|
+
<key>AGENTICMAIL_SERVICE_VERSION</key>
|
|
6526
|
+
<string>${version}</string>
|
|
6527
|
+
<key>AGENTICMAIL_SERVICE_LABEL</key>
|
|
6528
|
+
<string>${PLIST_LABEL}</string>
|
|
6529
|
+
</dict>
|
|
6530
|
+
|
|
6531
|
+
<!-- Start when user logs in -->
|
|
6532
|
+
<key>RunAtLoad</key>
|
|
6533
|
+
<true/>
|
|
6534
|
+
|
|
6535
|
+
<!-- Always keep running \u2014 restart unconditionally if it ever stops -->
|
|
6536
|
+
<key>KeepAlive</key>
|
|
6537
|
+
<true/>
|
|
6538
|
+
|
|
6539
|
+
<!-- Minimum 15s between restarts to avoid rapid crash loops -->
|
|
6540
|
+
<key>ThrottleInterval</key>
|
|
6541
|
+
<integer>15</integer>
|
|
6542
|
+
|
|
6543
|
+
<!-- File descriptor limits \u2014 email servers need many open connections -->
|
|
6544
|
+
<key>SoftResourceLimits</key>
|
|
6545
|
+
<dict>
|
|
6546
|
+
<key>NumberOfFiles</key>
|
|
6547
|
+
<integer>8192</integer>
|
|
6548
|
+
</dict>
|
|
6549
|
+
<key>HardResourceLimits</key>
|
|
6550
|
+
<dict>
|
|
6551
|
+
<key>NumberOfFiles</key>
|
|
6552
|
+
<integer>16384</integer>
|
|
6553
|
+
</dict>
|
|
6554
|
+
|
|
6555
|
+
<key>StandardOutPath</key>
|
|
6556
|
+
<string>${logDir}/server.log</string>
|
|
6557
|
+
<key>StandardErrorPath</key>
|
|
6558
|
+
<string>${logDir}/server.err.log</string>
|
|
6559
|
+
|
|
6560
|
+
<key>ProcessType</key>
|
|
6561
|
+
<string>Background</string>
|
|
6562
|
+
</dict>
|
|
6563
|
+
</plist>`;
|
|
6564
|
+
}
|
|
6565
|
+
/**
|
|
6566
|
+
* Generate the systemd user service content for Linux.
|
|
6567
|
+
* More robust than basic services:
|
|
6568
|
+
* - Wrapper script waits for Docker + Stalwart
|
|
6569
|
+
* - Restart=always (unconditional)
|
|
6570
|
+
* - WatchdogSec for health monitoring
|
|
6571
|
+
* - File descriptor limits
|
|
6572
|
+
* - Proper dependency ordering
|
|
6573
|
+
*/
|
|
6574
|
+
generateSystemdUnit(nodePath, apiEntry, configPath) {
|
|
6575
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
6576
|
+
const dataDir = config.dataDir || join7(homedir6(), ".agenticmail");
|
|
6577
|
+
const version = this.getVersion();
|
|
6578
|
+
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
6579
|
+
return `[Unit]
|
|
6580
|
+
Description=AgenticMail API Server (v${version})
|
|
6581
|
+
After=network-online.target docker.service
|
|
6582
|
+
Wants=network-online.target docker.service
|
|
6583
|
+
StartLimitIntervalSec=300
|
|
6584
|
+
StartLimitBurst=5
|
|
6585
|
+
|
|
6586
|
+
[Service]
|
|
6587
|
+
Type=simple
|
|
6588
|
+
ExecStart=${startScript}
|
|
6589
|
+
Restart=always
|
|
6590
|
+
RestartSec=15
|
|
6591
|
+
TimeoutStartSec=660
|
|
6592
|
+
LimitNOFILE=8192
|
|
6593
|
+
Environment=HOME=${homedir6()}
|
|
6594
|
+
Environment=AGENTICMAIL_DATA_DIR=${dataDir}
|
|
6595
|
+
Environment=AGENTICMAIL_MASTER_KEY=${config.masterKey}
|
|
6596
|
+
Environment=STALWART_ADMIN_USER=${config.stalwart.adminUser}
|
|
6597
|
+
Environment=STALWART_ADMIN_PASSWORD=${config.stalwart.adminPassword}
|
|
6598
|
+
Environment=STALWART_URL=${config.stalwart.url}
|
|
6599
|
+
Environment=AGENTICMAIL_API_PORT=${config.api.port}
|
|
6600
|
+
Environment=AGENTICMAIL_API_HOST=${config.api.host}
|
|
6601
|
+
Environment=SMTP_HOST=${config.smtp.host}
|
|
6602
|
+
Environment=SMTP_PORT=${config.smtp.port}
|
|
6603
|
+
Environment=IMAP_HOST=${config.imap.host}
|
|
6604
|
+
Environment=IMAP_PORT=${config.imap.port}
|
|
6605
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin
|
|
6606
|
+
Environment=AGENTICMAIL_SERVICE_VERSION=${version}
|
|
6607
|
+
|
|
6608
|
+
[Install]
|
|
6609
|
+
WantedBy=default.target
|
|
6610
|
+
`;
|
|
6611
|
+
}
|
|
6612
|
+
/**
|
|
6613
|
+
* Install the auto-start service.
|
|
6614
|
+
*/
|
|
6615
|
+
install() {
|
|
6616
|
+
const configPath = join7(homedir6(), ".agenticmail", "config.json");
|
|
6617
|
+
if (!existsSync5(configPath)) {
|
|
6618
|
+
return { installed: false, message: "Config not found. Run agenticmail setup first." };
|
|
6619
|
+
}
|
|
6620
|
+
const nodePath = this.getNodePath();
|
|
6621
|
+
let apiEntry;
|
|
6622
|
+
try {
|
|
6623
|
+
apiEntry = this.getApiEntryPath();
|
|
6624
|
+
} catch (err) {
|
|
6625
|
+
return { installed: false, message: err.message };
|
|
6626
|
+
}
|
|
6627
|
+
const servicePath = this.getServicePath();
|
|
6628
|
+
if (this.os === "darwin") {
|
|
6629
|
+
const dir = join7(homedir6(), "Library", "LaunchAgents");
|
|
6630
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
6631
|
+
if (existsSync5(servicePath)) {
|
|
6632
|
+
try {
|
|
6633
|
+
execFileSync3("launchctl", ["unload", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
6634
|
+
} catch {
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
const plist = this.generatePlist(nodePath, apiEntry, configPath);
|
|
6638
|
+
writeFileSync3(servicePath, plist);
|
|
6639
|
+
try {
|
|
6640
|
+
execFileSync3("launchctl", ["load", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
6641
|
+
} catch (err) {
|
|
6642
|
+
return { installed: false, message: `Failed to load service: ${err.message}` };
|
|
6643
|
+
}
|
|
6644
|
+
return { installed: true, message: `Service installed at ${servicePath}` };
|
|
6645
|
+
} else if (this.os === "linux") {
|
|
6646
|
+
const dir = join7(homedir6(), ".config", "systemd", "user");
|
|
6647
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
6648
|
+
const unit = this.generateSystemdUnit(nodePath, apiEntry, configPath);
|
|
6649
|
+
writeFileSync3(servicePath, unit);
|
|
6650
|
+
try {
|
|
6651
|
+
execFileSync3("systemctl", ["--user", "daemon-reload"], { timeout: 1e4, stdio: "ignore" });
|
|
6652
|
+
execFileSync3("systemctl", ["--user", "enable", SYSTEMD_UNIT], { timeout: 1e4, stdio: "ignore" });
|
|
6653
|
+
execFileSync3("systemctl", ["--user", "start", SYSTEMD_UNIT], { timeout: 1e4, stdio: "ignore" });
|
|
6654
|
+
try {
|
|
6655
|
+
execFileSync3("loginctl", ["enable-linger"], { timeout: 1e4, stdio: "ignore" });
|
|
6656
|
+
} catch {
|
|
6657
|
+
}
|
|
6658
|
+
} catch (err) {
|
|
6659
|
+
return { installed: false, message: `Failed to enable service: ${err.message}` };
|
|
6660
|
+
}
|
|
6661
|
+
return { installed: true, message: `Service installed at ${servicePath}` };
|
|
6662
|
+
} else {
|
|
6663
|
+
return { installed: false, message: `Auto-start not supported on ${this.os}` };
|
|
6664
|
+
}
|
|
6665
|
+
}
|
|
6666
|
+
/**
|
|
6667
|
+
* Uninstall the auto-start service.
|
|
6668
|
+
*/
|
|
6669
|
+
uninstall() {
|
|
6670
|
+
const servicePath = this.getServicePath();
|
|
6671
|
+
if (!existsSync5(servicePath)) {
|
|
6672
|
+
return { removed: false, message: "Service is not installed." };
|
|
6673
|
+
}
|
|
6674
|
+
if (this.os === "darwin") {
|
|
6675
|
+
try {
|
|
6676
|
+
execFileSync3("launchctl", ["unload", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
6677
|
+
} catch {
|
|
6678
|
+
}
|
|
6679
|
+
try {
|
|
6680
|
+
unlinkSync(servicePath);
|
|
6681
|
+
} catch {
|
|
6682
|
+
}
|
|
6683
|
+
return { removed: true, message: "Service removed." };
|
|
6684
|
+
} else if (this.os === "linux") {
|
|
6685
|
+
try {
|
|
6686
|
+
execFileSync3("systemctl", ["--user", "stop", SYSTEMD_UNIT], { timeout: 1e4, stdio: "ignore" });
|
|
6687
|
+
execFileSync3("systemctl", ["--user", "disable", SYSTEMD_UNIT], { timeout: 1e4, stdio: "ignore" });
|
|
6688
|
+
} catch {
|
|
6689
|
+
}
|
|
6690
|
+
try {
|
|
6691
|
+
unlinkSync(servicePath);
|
|
6692
|
+
} catch {
|
|
6693
|
+
}
|
|
6694
|
+
try {
|
|
6695
|
+
execFileSync3("systemctl", ["--user", "daemon-reload"], { timeout: 1e4, stdio: "ignore" });
|
|
6696
|
+
} catch {
|
|
6697
|
+
}
|
|
6698
|
+
return { removed: true, message: "Service removed." };
|
|
6699
|
+
} else {
|
|
6700
|
+
return { removed: false, message: `Not supported on ${this.os}` };
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
/**
|
|
6704
|
+
* Get the current service status.
|
|
6705
|
+
*/
|
|
6706
|
+
status() {
|
|
6707
|
+
const servicePath = this.getServicePath();
|
|
6708
|
+
const plat = this.os === "darwin" ? "launchd" : this.os === "linux" ? "systemd" : "unsupported";
|
|
6709
|
+
const installed = existsSync5(servicePath);
|
|
6710
|
+
let running = false;
|
|
6711
|
+
if (installed) {
|
|
6712
|
+
if (this.os === "darwin") {
|
|
6713
|
+
try {
|
|
6714
|
+
const output = execSync2(`launchctl list | grep ${PLIST_LABEL}`, { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
6715
|
+
const pid = output.trim().split(" ")[0];
|
|
6716
|
+
running = pid !== "-" && pid !== "" && !isNaN(parseInt(pid));
|
|
6717
|
+
} catch {
|
|
6718
|
+
}
|
|
6719
|
+
} else if (this.os === "linux") {
|
|
6720
|
+
try {
|
|
6721
|
+
execFileSync3("systemctl", ["--user", "is-active", SYSTEMD_UNIT], { timeout: 5e3, stdio: "ignore" });
|
|
6722
|
+
running = true;
|
|
6723
|
+
} catch {
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
return { installed, running, platform: plat, servicePath: installed ? servicePath : null };
|
|
6728
|
+
}
|
|
6729
|
+
/**
|
|
6730
|
+
* Reinstall the service (useful after config changes or updates).
|
|
6731
|
+
*/
|
|
6732
|
+
reinstall() {
|
|
6733
|
+
this.uninstall();
|
|
6734
|
+
return this.install();
|
|
6735
|
+
}
|
|
6736
|
+
};
|
|
6737
|
+
|
|
6101
6738
|
// src/setup/index.ts
|
|
6102
6739
|
var SetupManager = class {
|
|
6103
6740
|
checker = new DependencyChecker();
|
|
@@ -6139,13 +6776,13 @@ var SetupManager = class {
|
|
|
6139
6776
|
* falls back to monorepo location.
|
|
6140
6777
|
*/
|
|
6141
6778
|
getComposePath() {
|
|
6142
|
-
const standalonePath =
|
|
6143
|
-
if (
|
|
6779
|
+
const standalonePath = join8(homedir7(), ".agenticmail", "docker-compose.yml");
|
|
6780
|
+
if (existsSync6(standalonePath)) return standalonePath;
|
|
6144
6781
|
const cwd = process.cwd();
|
|
6145
|
-
const candidates = [cwd,
|
|
6782
|
+
const candidates = [cwd, join8(cwd, "..")];
|
|
6146
6783
|
for (const dir of candidates) {
|
|
6147
|
-
const p =
|
|
6148
|
-
if (
|
|
6784
|
+
const p = join8(dir, "docker-compose.yml");
|
|
6785
|
+
if (existsSync6(p)) return p;
|
|
6149
6786
|
}
|
|
6150
6787
|
return standalonePath;
|
|
6151
6788
|
}
|
|
@@ -6155,19 +6792,19 @@ var SetupManager = class {
|
|
|
6155
6792
|
* Always regenerates Docker files to keep passwords in sync.
|
|
6156
6793
|
*/
|
|
6157
6794
|
initConfig() {
|
|
6158
|
-
const dataDir =
|
|
6159
|
-
const configPath =
|
|
6160
|
-
const envPath =
|
|
6161
|
-
if (
|
|
6795
|
+
const dataDir = join8(homedir7(), ".agenticmail");
|
|
6796
|
+
const configPath = join8(dataDir, "config.json");
|
|
6797
|
+
const envPath = join8(dataDir, ".env");
|
|
6798
|
+
if (existsSync6(configPath)) {
|
|
6162
6799
|
try {
|
|
6163
|
-
const existing = JSON.parse(
|
|
6800
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
6164
6801
|
this.generateDockerFiles(existing);
|
|
6165
6802
|
return { configPath, envPath, config: existing, isNew: false };
|
|
6166
6803
|
} catch {
|
|
6167
6804
|
}
|
|
6168
6805
|
}
|
|
6169
|
-
if (!
|
|
6170
|
-
|
|
6806
|
+
if (!existsSync6(dataDir)) {
|
|
6807
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
6171
6808
|
}
|
|
6172
6809
|
const masterKey = `mk_${randomBytes2(24).toString("hex")}`;
|
|
6173
6810
|
const stalwartPassword = randomBytes2(16).toString("hex");
|
|
@@ -6183,7 +6820,7 @@ var SetupManager = class {
|
|
|
6183
6820
|
api: { port: 3100, host: "127.0.0.1" },
|
|
6184
6821
|
dataDir
|
|
6185
6822
|
};
|
|
6186
|
-
|
|
6823
|
+
writeFileSync4(configPath, JSON.stringify(config, null, 2));
|
|
6187
6824
|
chmodSync(configPath, 384);
|
|
6188
6825
|
const envContent = `# Auto-generated by agenticmail setup
|
|
6189
6826
|
STALWART_ADMIN_USER=admin
|
|
@@ -6199,7 +6836,7 @@ SMTP_PORT=587
|
|
|
6199
6836
|
IMAP_HOST=localhost
|
|
6200
6837
|
IMAP_PORT=143
|
|
6201
6838
|
`;
|
|
6202
|
-
|
|
6839
|
+
writeFileSync4(envPath, envContent);
|
|
6203
6840
|
chmodSync(envPath, 384);
|
|
6204
6841
|
this.generateDockerFiles(config);
|
|
6205
6842
|
return { configPath, envPath, config, isNew: true };
|
|
@@ -6209,13 +6846,13 @@ IMAP_PORT=143
|
|
|
6209
6846
|
* with the correct admin password from config.
|
|
6210
6847
|
*/
|
|
6211
6848
|
generateDockerFiles(config) {
|
|
6212
|
-
const dataDir = config.dataDir ||
|
|
6213
|
-
if (!
|
|
6214
|
-
|
|
6849
|
+
const dataDir = config.dataDir || join8(homedir7(), ".agenticmail");
|
|
6850
|
+
if (!existsSync6(dataDir)) {
|
|
6851
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
6215
6852
|
}
|
|
6216
6853
|
const password = config.stalwart?.adminPassword || "changeme";
|
|
6217
|
-
const composePath =
|
|
6218
|
-
|
|
6854
|
+
const composePath = join8(dataDir, "docker-compose.yml");
|
|
6855
|
+
writeFileSync4(composePath, `services:
|
|
6219
6856
|
stalwart:
|
|
6220
6857
|
image: stalwartlabs/stalwart:latest
|
|
6221
6858
|
container_name: agenticmail-stalwart
|
|
@@ -6237,8 +6874,8 @@ IMAP_PORT=143
|
|
|
6237
6874
|
volumes:
|
|
6238
6875
|
stalwart-data:
|
|
6239
6876
|
`);
|
|
6240
|
-
const tomlPath =
|
|
6241
|
-
|
|
6877
|
+
const tomlPath = join8(dataDir, "stalwart.toml");
|
|
6878
|
+
writeFileSync4(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
|
|
6242
6879
|
|
|
6243
6880
|
[server]
|
|
6244
6881
|
hostname = "localhost"
|
|
@@ -6293,8 +6930,8 @@ secret = "${password}"
|
|
|
6293
6930
|
* Check if config has already been initialized.
|
|
6294
6931
|
*/
|
|
6295
6932
|
isInitialized() {
|
|
6296
|
-
const configPath =
|
|
6297
|
-
return
|
|
6933
|
+
const configPath = join8(homedir7(), ".agenticmail", "config.json");
|
|
6934
|
+
return existsSync6(configPath);
|
|
6298
6935
|
}
|
|
6299
6936
|
};
|
|
6300
6937
|
export {
|
|
@@ -6319,6 +6956,7 @@ export {
|
|
|
6319
6956
|
RelayBridge,
|
|
6320
6957
|
RelayGateway,
|
|
6321
6958
|
SPAM_THRESHOLD,
|
|
6959
|
+
ServiceManager,
|
|
6322
6960
|
SetupManager,
|
|
6323
6961
|
SmsManager,
|
|
6324
6962
|
SmsPoller,
|