@agenticmail/core 0.5.0 → 0.5.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/dist/index.d.ts +87 -1
- package/dist/index.js +500 -65
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1450,6 +1450,92 @@ declare class DependencyInstaller {
|
|
|
1450
1450
|
installAll(composePath: string): Promise<void>;
|
|
1451
1451
|
}
|
|
1452
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
|
+
|
|
1453
1539
|
interface SetupConfig {
|
|
1454
1540
|
masterKey: string;
|
|
1455
1541
|
stalwart: {
|
|
@@ -1524,4 +1610,4 @@ declare class SetupManager {
|
|
|
1524
1610
|
isInitialized(): boolean;
|
|
1525
1611
|
}
|
|
1526
1612
|
|
|
1527
|
-
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";
|
|
@@ -6301,6 +6301,440 @@ var DependencyInstaller = class {
|
|
|
6301
6301
|
}
|
|
6302
6302
|
};
|
|
6303
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
|
+
|
|
6304
6738
|
// src/setup/index.ts
|
|
6305
6739
|
var SetupManager = class {
|
|
6306
6740
|
checker = new DependencyChecker();
|
|
@@ -6342,13 +6776,13 @@ var SetupManager = class {
|
|
|
6342
6776
|
* falls back to monorepo location.
|
|
6343
6777
|
*/
|
|
6344
6778
|
getComposePath() {
|
|
6345
|
-
const standalonePath =
|
|
6346
|
-
if (
|
|
6779
|
+
const standalonePath = join8(homedir7(), ".agenticmail", "docker-compose.yml");
|
|
6780
|
+
if (existsSync6(standalonePath)) return standalonePath;
|
|
6347
6781
|
const cwd = process.cwd();
|
|
6348
|
-
const candidates = [cwd,
|
|
6782
|
+
const candidates = [cwd, join8(cwd, "..")];
|
|
6349
6783
|
for (const dir of candidates) {
|
|
6350
|
-
const p =
|
|
6351
|
-
if (
|
|
6784
|
+
const p = join8(dir, "docker-compose.yml");
|
|
6785
|
+
if (existsSync6(p)) return p;
|
|
6352
6786
|
}
|
|
6353
6787
|
return standalonePath;
|
|
6354
6788
|
}
|
|
@@ -6358,19 +6792,19 @@ var SetupManager = class {
|
|
|
6358
6792
|
* Always regenerates Docker files to keep passwords in sync.
|
|
6359
6793
|
*/
|
|
6360
6794
|
initConfig() {
|
|
6361
|
-
const dataDir =
|
|
6362
|
-
const configPath =
|
|
6363
|
-
const envPath =
|
|
6364
|
-
if (
|
|
6795
|
+
const dataDir = join8(homedir7(), ".agenticmail");
|
|
6796
|
+
const configPath = join8(dataDir, "config.json");
|
|
6797
|
+
const envPath = join8(dataDir, ".env");
|
|
6798
|
+
if (existsSync6(configPath)) {
|
|
6365
6799
|
try {
|
|
6366
|
-
const existing = JSON.parse(
|
|
6800
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
6367
6801
|
this.generateDockerFiles(existing);
|
|
6368
6802
|
return { configPath, envPath, config: existing, isNew: false };
|
|
6369
6803
|
} catch {
|
|
6370
6804
|
}
|
|
6371
6805
|
}
|
|
6372
|
-
if (!
|
|
6373
|
-
|
|
6806
|
+
if (!existsSync6(dataDir)) {
|
|
6807
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
6374
6808
|
}
|
|
6375
6809
|
const masterKey = `mk_${randomBytes2(24).toString("hex")}`;
|
|
6376
6810
|
const stalwartPassword = randomBytes2(16).toString("hex");
|
|
@@ -6386,7 +6820,7 @@ var SetupManager = class {
|
|
|
6386
6820
|
api: { port: 3100, host: "127.0.0.1" },
|
|
6387
6821
|
dataDir
|
|
6388
6822
|
};
|
|
6389
|
-
|
|
6823
|
+
writeFileSync4(configPath, JSON.stringify(config, null, 2));
|
|
6390
6824
|
chmodSync(configPath, 384);
|
|
6391
6825
|
const envContent = `# Auto-generated by agenticmail setup
|
|
6392
6826
|
STALWART_ADMIN_USER=admin
|
|
@@ -6402,7 +6836,7 @@ SMTP_PORT=587
|
|
|
6402
6836
|
IMAP_HOST=localhost
|
|
6403
6837
|
IMAP_PORT=143
|
|
6404
6838
|
`;
|
|
6405
|
-
|
|
6839
|
+
writeFileSync4(envPath, envContent);
|
|
6406
6840
|
chmodSync(envPath, 384);
|
|
6407
6841
|
this.generateDockerFiles(config);
|
|
6408
6842
|
return { configPath, envPath, config, isNew: true };
|
|
@@ -6412,13 +6846,13 @@ IMAP_PORT=143
|
|
|
6412
6846
|
* with the correct admin password from config.
|
|
6413
6847
|
*/
|
|
6414
6848
|
generateDockerFiles(config) {
|
|
6415
|
-
const dataDir = config.dataDir ||
|
|
6416
|
-
if (!
|
|
6417
|
-
|
|
6849
|
+
const dataDir = config.dataDir || join8(homedir7(), ".agenticmail");
|
|
6850
|
+
if (!existsSync6(dataDir)) {
|
|
6851
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
6418
6852
|
}
|
|
6419
6853
|
const password = config.stalwart?.adminPassword || "changeme";
|
|
6420
|
-
const composePath =
|
|
6421
|
-
|
|
6854
|
+
const composePath = join8(dataDir, "docker-compose.yml");
|
|
6855
|
+
writeFileSync4(composePath, `services:
|
|
6422
6856
|
stalwart:
|
|
6423
6857
|
image: stalwartlabs/stalwart:latest
|
|
6424
6858
|
container_name: agenticmail-stalwart
|
|
@@ -6440,8 +6874,8 @@ IMAP_PORT=143
|
|
|
6440
6874
|
volumes:
|
|
6441
6875
|
stalwart-data:
|
|
6442
6876
|
`);
|
|
6443
|
-
const tomlPath =
|
|
6444
|
-
|
|
6877
|
+
const tomlPath = join8(dataDir, "stalwart.toml");
|
|
6878
|
+
writeFileSync4(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
|
|
6445
6879
|
|
|
6446
6880
|
[server]
|
|
6447
6881
|
hostname = "localhost"
|
|
@@ -6496,8 +6930,8 @@ secret = "${password}"
|
|
|
6496
6930
|
* Check if config has already been initialized.
|
|
6497
6931
|
*/
|
|
6498
6932
|
isInitialized() {
|
|
6499
|
-
const configPath =
|
|
6500
|
-
return
|
|
6933
|
+
const configPath = join8(homedir7(), ".agenticmail", "config.json");
|
|
6934
|
+
return existsSync6(configPath);
|
|
6501
6935
|
}
|
|
6502
6936
|
};
|
|
6503
6937
|
export {
|
|
@@ -6522,6 +6956,7 @@ export {
|
|
|
6522
6956
|
RelayBridge,
|
|
6523
6957
|
RelayGateway,
|
|
6524
6958
|
SPAM_THRESHOLD,
|
|
6959
|
+
ServiceManager,
|
|
6525
6960
|
SetupManager,
|
|
6526
6961
|
SmsManager,
|
|
6527
6962
|
SmsPoller,
|