agenticmail 0.3.25 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -3
- package/dist/cli.js +806 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The main package for [AgenticMail](https://github.com/agenticmail/agenticmail) — email infrastructure for AI agents. This is the package you install to get started.
|
|
4
4
|
|
|
5
|
-
It bundles a setup wizard, API server launcher, and a full interactive shell with
|
|
5
|
+
It bundles a setup wizard, API server launcher, and a full interactive shell with 44 commands for managing agents, sending and receiving email, configuring gateways, and more. It also re-exports everything from `@agenticmail/core` so you can use it as an SDK.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -42,7 +42,11 @@ Running `agenticmail setup` walks you through everything needed to get email wor
|
|
|
42
42
|
|
|
43
43
|
3. **Service startup** — starts Docker if needed, ensures Stalwart is running and healthy.
|
|
44
44
|
|
|
45
|
-
4. **Email connection** — this is where you choose how your agents connect to the outside world
|
|
45
|
+
4. **Email connection** — this is where you choose how your agents connect to the outside world.
|
|
46
|
+
|
|
47
|
+
5. **Phone number access (optional)** — set up Google Voice for SMS. Agents can receive verification codes and send texts. The wizard validates Gmail/Google Voice email matching, warns about mismatches, and collects separate credentials when needed. SMS reading prioritizes direct Google Voice web access (instant) with email forwarding as fallback.
|
|
48
|
+
|
|
49
|
+
6. **OpenClaw integration** — if OpenClaw is detected, automatically registers the plugin.
|
|
46
50
|
|
|
47
51
|
### Relay Mode (Recommended for Getting Started)
|
|
48
52
|
|
|
@@ -99,7 +103,7 @@ If the server crashes, you get clear error output showing what went wrong.
|
|
|
99
103
|
|
|
100
104
|
## The Interactive Shell
|
|
101
105
|
|
|
102
|
-
The shell is the main way to interact with AgenticMail. It provides
|
|
106
|
+
The shell is the main way to interact with AgenticMail. It provides 44 commands organized by category, with arrow-key navigation, color-coded output, and keyboard shortcuts.
|
|
103
107
|
|
|
104
108
|
### Getting Around
|
|
105
109
|
|
|
@@ -182,8 +186,17 @@ The shell is the main way to interact with AgenticMail. It provides 36 commands
|
|
|
182
186
|
|---------|-------------|
|
|
183
187
|
| `/help` | Show all available commands with descriptions. |
|
|
184
188
|
| `/clear` | Clear the screen. |
|
|
189
|
+
| `/update` | Check for and install the latest AgenticMail version. Auto-detects OpenClaw and updates both. |
|
|
185
190
|
| `/exit` | Exit the shell (also `/quit`). Stops the server and cleans up. |
|
|
186
191
|
|
|
192
|
+
### CLI Update Command
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
agenticmail update
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Checks npm for the latest version, compares with your current install, and updates in-place. If OpenClaw is detected, it also updates `@agenticmail/openclaw` and restarts the gateway automatically. Works with npm, pnpm, and bun.
|
|
199
|
+
|
|
187
200
|
---
|
|
188
201
|
|
|
189
202
|
## Inbox Navigation
|
package/dist/cli.js
CHANGED
|
@@ -235,6 +235,21 @@ async function interactiveShell(options) {
|
|
|
235
235
|
log(` ${c.dim("Server:")} ${c.cyan(`http://${config.api.host}:${config.api.port}`)}`);
|
|
236
236
|
if (agentLine) log(` ${c.dim("Agents:")} ${agentLine}`);
|
|
237
237
|
if (emailLine) log(` ${c.dim("Email:")} ${emailLine}`);
|
|
238
|
+
try {
|
|
239
|
+
const agentsResp = await apiFetch("/api/agenticmail/accounts");
|
|
240
|
+
if (agentsResp.ok) {
|
|
241
|
+
const agentsData = await agentsResp.json();
|
|
242
|
+
const agents = agentsData.agents || agentsData.accounts || [];
|
|
243
|
+
for (const a of agents) {
|
|
244
|
+
const smsConf = a.metadata?.sms;
|
|
245
|
+
if (smsConf?.enabled && smsConf.phoneNumber) {
|
|
246
|
+
log(` ${c.dim("Phone:")} ${c.green(smsConf.phoneNumber)} ${c.dim("via Google Voice")} ${c.dim("(" + a.name + ")")}`);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
238
253
|
log("");
|
|
239
254
|
log("");
|
|
240
255
|
log(` ${c.dim("Type")} ${c.bold("/help")} ${c.dim("for commands, or")} ${c.bold("/exit")} ${c.dim("to stop.")}`);
|
|
@@ -1665,6 +1680,14 @@ ${orig.text || ""}`;
|
|
|
1665
1680
|
log(` ${c.cyan("\u{1F4C1}")} ${f.path}${special}`);
|
|
1666
1681
|
}
|
|
1667
1682
|
}
|
|
1683
|
+
try {
|
|
1684
|
+
const smsResp = await agentFetch(agent.apiKey, "/api/agenticmail/sms/config");
|
|
1685
|
+
const smsData = await smsResp.json();
|
|
1686
|
+
if (smsData.sms?.enabled) {
|
|
1687
|
+
log(` ${c.green("\u{1F4F1}")} SMS ${c.dim(`(${smsData.sms.phoneNumber})`)}`);
|
|
1688
|
+
}
|
|
1689
|
+
} catch {
|
|
1690
|
+
}
|
|
1668
1691
|
} else if (choice.trim() === "2" || choice.trim().toLowerCase() === "create") {
|
|
1669
1692
|
const name = await question(` ${c.dim("Folder name:")} `);
|
|
1670
1693
|
if (isBack(name) || !name.trim()) {
|
|
@@ -1694,10 +1717,25 @@ ${orig.text || ""}`;
|
|
|
1694
1717
|
log("");
|
|
1695
1718
|
return;
|
|
1696
1719
|
}
|
|
1720
|
+
let hasSms = false;
|
|
1721
|
+
try {
|
|
1722
|
+
const smsResp = await agentFetch(agent.apiKey, "/api/agenticmail/sms/config");
|
|
1723
|
+
const smsData = await smsResp.json();
|
|
1724
|
+
if (smsData.sms?.enabled) hasSms = true;
|
|
1725
|
+
} catch {
|
|
1726
|
+
}
|
|
1697
1727
|
for (let i = 0; i < folders.length; i++) {
|
|
1698
1728
|
log(` ${c.dim(`[${i + 1}]`)} ${c.cyan(folders[i].path)}`);
|
|
1699
1729
|
}
|
|
1700
|
-
|
|
1730
|
+
if (hasSms) {
|
|
1731
|
+
log(` ${c.dim(`[${folders.length + 1}]`)} ${c.green("SMS")} ${c.dim("(text messages)")}`);
|
|
1732
|
+
}
|
|
1733
|
+
const totalChoices = hasSms ? folders.length + 1 : folders.length;
|
|
1734
|
+
const idx = await askChoice(` ${c.dim("Folder #:")} `, totalChoices);
|
|
1735
|
+
if (idx !== null && hasSms && idx === folders.length) {
|
|
1736
|
+
await commands.sms.run();
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1701
1739
|
if (idx === null) {
|
|
1702
1740
|
log("");
|
|
1703
1741
|
return;
|
|
@@ -3617,6 +3655,308 @@ ${c.dim(boxChar.bl + boxChar.h.repeat(bWidth) + boxChar.br)}`);
|
|
|
3617
3655
|
}
|
|
3618
3656
|
}
|
|
3619
3657
|
},
|
|
3658
|
+
sms: {
|
|
3659
|
+
desc: "Manage SMS / phone number (view, setup, change, disable)",
|
|
3660
|
+
run: async () => {
|
|
3661
|
+
const agent = getActiveAgent();
|
|
3662
|
+
if (!agent) return;
|
|
3663
|
+
log("");
|
|
3664
|
+
log(hr());
|
|
3665
|
+
heading("SMS / Phone Number");
|
|
3666
|
+
log("");
|
|
3667
|
+
let smsConfig = null;
|
|
3668
|
+
try {
|
|
3669
|
+
const resp = await fetch(`${apiBase}/api/agenticmail/sms/config`, {
|
|
3670
|
+
headers: { "Authorization": `Bearer ${agent.apiKey}` }
|
|
3671
|
+
});
|
|
3672
|
+
const data = await resp.json();
|
|
3673
|
+
smsConfig = data.sms;
|
|
3674
|
+
} catch {
|
|
3675
|
+
}
|
|
3676
|
+
if (smsConfig?.enabled) {
|
|
3677
|
+
log(` ${c.green("\u25CF")} SMS is ${c.green("enabled")}`);
|
|
3678
|
+
log(` ${c.dim("Phone number:")} ${c.bold(smsConfig.phoneNumber)}`);
|
|
3679
|
+
log(` ${c.dim("Forwarding to:")} ${smsConfig.forwardingEmail || c.dim("(agent email)")}`);
|
|
3680
|
+
log(` ${c.dim("Provider:")} ${smsConfig.provider}`);
|
|
3681
|
+
log(` ${c.dim("Configured:")} ${smsConfig.configuredAt ? new Date(smsConfig.configuredAt).toLocaleDateString() : "unknown"}`);
|
|
3682
|
+
} else if (smsConfig && !smsConfig.enabled) {
|
|
3683
|
+
log(` ${c.yellow("\u25CF")} SMS is ${c.yellow("disabled")}`);
|
|
3684
|
+
log(` ${c.dim("Phone number:")} ${smsConfig.phoneNumber}`);
|
|
3685
|
+
} else {
|
|
3686
|
+
log(` ${c.dim("\u25CF")} SMS is ${c.dim("not configured")}`);
|
|
3687
|
+
}
|
|
3688
|
+
log("");
|
|
3689
|
+
log(` ${c.bold("Options:")}`);
|
|
3690
|
+
if (!smsConfig?.enabled) {
|
|
3691
|
+
log(` ${c.cyan("1")} Set up a phone number`);
|
|
3692
|
+
} else {
|
|
3693
|
+
log(` ${c.cyan("1")} View SMS messages`);
|
|
3694
|
+
log(` ${c.cyan("2")} Change phone number`);
|
|
3695
|
+
log(` ${c.cyan("3")} Check for verification codes`);
|
|
3696
|
+
log(` ${c.cyan("4")} Disable SMS`);
|
|
3697
|
+
}
|
|
3698
|
+
log(` ${c.dim("Enter")} Go back`);
|
|
3699
|
+
log("");
|
|
3700
|
+
const choice = await new Promise((resolve) => {
|
|
3701
|
+
rl.question(` ${c.bold("Choose:")} `, resolve);
|
|
3702
|
+
});
|
|
3703
|
+
if (!smsConfig?.enabled) {
|
|
3704
|
+
if (choice.trim() === "1") {
|
|
3705
|
+
log("");
|
|
3706
|
+
log(` ${c.bold("Google Voice Setup (takes ~2 minutes):")}`);
|
|
3707
|
+
log("");
|
|
3708
|
+
log(` 1. Go to ${c.cyan("https://voice.google.com")}`);
|
|
3709
|
+
log(` 2. Sign in with your Google account`);
|
|
3710
|
+
log(` 3. Click "Choose a phone number"`);
|
|
3711
|
+
log(` 4. Search by city or area code, pick a number`);
|
|
3712
|
+
log(` 5. Verify with your existing phone`);
|
|
3713
|
+
log(` 6. Go to Settings > Messages > Enable "Forward messages to email"`);
|
|
3714
|
+
log("");
|
|
3715
|
+
const phone = await new Promise((resolve) => {
|
|
3716
|
+
rl.question(` ${c.bold("Google Voice number")} ${c.dim("(e.g. +12125551234):")} `, resolve);
|
|
3717
|
+
});
|
|
3718
|
+
if (!phone.trim()) {
|
|
3719
|
+
info("Cancelled.");
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
const fwdEmail = await new Promise((resolve) => {
|
|
3723
|
+
rl.question(` ${c.bold("Forwarding email")} ${c.dim("(Enter for agent email):")} `, resolve);
|
|
3724
|
+
});
|
|
3725
|
+
try {
|
|
3726
|
+
const resp = await fetch(`${apiBase}/api/agenticmail/sms/setup`, {
|
|
3727
|
+
method: "POST",
|
|
3728
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${agent.apiKey}` },
|
|
3729
|
+
body: JSON.stringify({ phoneNumber: phone.trim(), forwardingEmail: fwdEmail.trim() || void 0 })
|
|
3730
|
+
});
|
|
3731
|
+
const data = await resp.json();
|
|
3732
|
+
if (data.success) {
|
|
3733
|
+
ok(`Phone number saved: ${data.sms?.phoneNumber || phone.trim()}`);
|
|
3734
|
+
info("Make sure SMS forwarding is enabled in Google Voice settings.");
|
|
3735
|
+
} else {
|
|
3736
|
+
fail(data.error || "Setup failed");
|
|
3737
|
+
}
|
|
3738
|
+
} catch (err) {
|
|
3739
|
+
fail(err.message);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
} else {
|
|
3743
|
+
if (choice.trim() === "1") {
|
|
3744
|
+
try {
|
|
3745
|
+
const resp = await fetch(`${apiBase}/api/agenticmail/sms/messages?limit=20`, {
|
|
3746
|
+
headers: { "Authorization": `Bearer ${agent.apiKey}` }
|
|
3747
|
+
});
|
|
3748
|
+
const data = await resp.json();
|
|
3749
|
+
if (!data.messages?.length) {
|
|
3750
|
+
info("No SMS messages yet.");
|
|
3751
|
+
} else {
|
|
3752
|
+
log("");
|
|
3753
|
+
for (const msg of data.messages) {
|
|
3754
|
+
const dir = msg.direction === "inbound" ? c.green("\u2190 IN ") : c.blue("\u2192 OUT");
|
|
3755
|
+
const status = msg.status === "received" ? "" : ` ${c.dim(`[${msg.status}]`)}`;
|
|
3756
|
+
const time = new Date(msg.createdAt).toLocaleString();
|
|
3757
|
+
log(` ${dir} ${c.bold(msg.phoneNumber)}${status} ${c.dim(time)}`);
|
|
3758
|
+
log(` ${msg.body.length > 80 ? msg.body.slice(0, 80) + "..." : msg.body}`);
|
|
3759
|
+
log("");
|
|
3760
|
+
}
|
|
3761
|
+
info(`${data.messages.length} message(s)`);
|
|
3762
|
+
}
|
|
3763
|
+
} catch (err) {
|
|
3764
|
+
fail(err.message);
|
|
3765
|
+
}
|
|
3766
|
+
} else if (choice.trim() === "2") {
|
|
3767
|
+
const newPhone = await new Promise((resolve) => {
|
|
3768
|
+
rl.question(` ${c.bold("New Google Voice number")} ${c.dim("(e.g. +12125551234):")} `, resolve);
|
|
3769
|
+
});
|
|
3770
|
+
if (!newPhone.trim()) {
|
|
3771
|
+
info("Cancelled.");
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
const newFwd = await new Promise((resolve) => {
|
|
3775
|
+
rl.question(` ${c.bold("Forwarding email")} ${c.dim("(Enter to keep current):")} `, resolve);
|
|
3776
|
+
});
|
|
3777
|
+
try {
|
|
3778
|
+
const resp = await fetch(`${apiBase}/api/agenticmail/sms/setup`, {
|
|
3779
|
+
method: "POST",
|
|
3780
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${agent.apiKey}` },
|
|
3781
|
+
body: JSON.stringify({ phoneNumber: newPhone.trim(), forwardingEmail: newFwd.trim() || void 0 })
|
|
3782
|
+
});
|
|
3783
|
+
const data = await resp.json();
|
|
3784
|
+
if (data.success) {
|
|
3785
|
+
ok(`Phone number updated to: ${data.sms?.phoneNumber || newPhone.trim()}`);
|
|
3786
|
+
} else {
|
|
3787
|
+
fail(data.error || "Update failed");
|
|
3788
|
+
}
|
|
3789
|
+
} catch (err) {
|
|
3790
|
+
fail(err.message);
|
|
3791
|
+
}
|
|
3792
|
+
} else if (choice.trim() === "3") {
|
|
3793
|
+
try {
|
|
3794
|
+
const resp = await fetch(`${apiBase}/api/agenticmail/sms/verification-code?minutes=30`, {
|
|
3795
|
+
headers: { "Authorization": `Bearer ${agent.apiKey}` }
|
|
3796
|
+
});
|
|
3797
|
+
const data = await resp.json();
|
|
3798
|
+
if (data.found) {
|
|
3799
|
+
log("");
|
|
3800
|
+
ok(`Verification code found: ${c.bold(c.green(data.code))}`);
|
|
3801
|
+
log(` ${c.dim("From:")} ${data.from}`);
|
|
3802
|
+
log(` ${c.dim("Message:")} ${data.body}`);
|
|
3803
|
+
log(` ${c.dim("Received:")} ${new Date(data.receivedAt).toLocaleString()}`);
|
|
3804
|
+
} else {
|
|
3805
|
+
info("No verification codes found in the last 30 minutes.");
|
|
3806
|
+
info("Make sure Google Voice SMS forwarding is enabled and use /inbox to check for forwarded SMS emails.");
|
|
3807
|
+
}
|
|
3808
|
+
} catch (err) {
|
|
3809
|
+
fail(err.message);
|
|
3810
|
+
}
|
|
3811
|
+
} else if (choice.trim() === "4") {
|
|
3812
|
+
const confirm = await new Promise((resolve) => {
|
|
3813
|
+
rl.question(` ${c.bold("Disable SMS?")} ${c.dim("This keeps your number saved but stops SMS features. (y/N)")} `, resolve);
|
|
3814
|
+
});
|
|
3815
|
+
if (confirm.toLowerCase().startsWith("y")) {
|
|
3816
|
+
try {
|
|
3817
|
+
const resp = await fetch(`${apiBase}/api/agenticmail/sms/disable`, {
|
|
3818
|
+
method: "POST",
|
|
3819
|
+
headers: { "Authorization": `Bearer ${agent.apiKey}` }
|
|
3820
|
+
});
|
|
3821
|
+
const data = await resp.json();
|
|
3822
|
+
if (data.success) {
|
|
3823
|
+
ok("SMS disabled. Your number is still saved. Use /sms to re-enable anytime.");
|
|
3824
|
+
} else {
|
|
3825
|
+
fail(data.error || "Failed to disable");
|
|
3826
|
+
}
|
|
3827
|
+
} catch (err) {
|
|
3828
|
+
fail(err.message);
|
|
3829
|
+
}
|
|
3830
|
+
} else {
|
|
3831
|
+
info("Cancelled.");
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
log("");
|
|
3836
|
+
}
|
|
3837
|
+
},
|
|
3838
|
+
update: {
|
|
3839
|
+
desc: "Check for and install the latest AgenticMail version",
|
|
3840
|
+
run: async () => {
|
|
3841
|
+
log("");
|
|
3842
|
+
log(hr());
|
|
3843
|
+
heading("Update AgenticMail");
|
|
3844
|
+
log("");
|
|
3845
|
+
const { execSync } = await import("child_process");
|
|
3846
|
+
let currentVersion = "unknown";
|
|
3847
|
+
try {
|
|
3848
|
+
const pkg = await import("agenticmail/package.json");
|
|
3849
|
+
currentVersion = pkg.default?.version ?? "unknown";
|
|
3850
|
+
} catch {
|
|
3851
|
+
try {
|
|
3852
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
3853
|
+
const { join: join2, dirname: dirname2 } = await import("path");
|
|
3854
|
+
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
3855
|
+
const thisDir = dirname2(fileURLToPath2(import.meta.url));
|
|
3856
|
+
const pkgPath = join2(thisDir, "..", "package.json");
|
|
3857
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
3858
|
+
currentVersion = pkg.version ?? "unknown";
|
|
3859
|
+
} catch {
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
info(`Current version: ${c.bold(currentVersion)}`);
|
|
3863
|
+
let latestVersion = "unknown";
|
|
3864
|
+
try {
|
|
3865
|
+
latestVersion = execSync("npm view agenticmail version", { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
3866
|
+
} catch {
|
|
3867
|
+
fail("Could not check npm for latest version. Check your internet connection.");
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
info(`Latest version: ${c.bold(latestVersion)}`);
|
|
3871
|
+
if (currentVersion === latestVersion) {
|
|
3872
|
+
ok("You are already on the latest version!");
|
|
3873
|
+
log("");
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
log("");
|
|
3877
|
+
info(`New version available: ${c.yellow(currentVersion)} \u2192 ${c.green(latestVersion)}`);
|
|
3878
|
+
let hasOpenClaw = false;
|
|
3879
|
+
let openClawVersion = "";
|
|
3880
|
+
try {
|
|
3881
|
+
openClawVersion = execSync('openclaw --version 2>/dev/null || echo ""', { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
3882
|
+
if (openClawVersion) hasOpenClaw = true;
|
|
3883
|
+
} catch {
|
|
3884
|
+
}
|
|
3885
|
+
if (hasOpenClaw) {
|
|
3886
|
+
info(`OpenClaw detected: ${c.bold(openClawVersion)}`);
|
|
3887
|
+
try {
|
|
3888
|
+
const peerDeps = execSync(`npm view agenticmail@${latestVersion} peerDependencies --json 2>/dev/null`, { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
3889
|
+
if (peerDeps) {
|
|
3890
|
+
const deps = JSON.parse(peerDeps);
|
|
3891
|
+
if (deps.openclaw) {
|
|
3892
|
+
info(`Required OpenClaw version: ${c.bold(deps.openclaw)}`);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
} catch {
|
|
3896
|
+
}
|
|
3897
|
+
info("OpenClaw plugin will also be updated.");
|
|
3898
|
+
}
|
|
3899
|
+
log("");
|
|
3900
|
+
const confirm = await new Promise((resolve) => {
|
|
3901
|
+
rl.question(` ${c.bold("Update now?")} ${c.dim("(Y/n)")} `, resolve);
|
|
3902
|
+
});
|
|
3903
|
+
if (confirm.toLowerCase().startsWith("n")) {
|
|
3904
|
+
info("Update cancelled.");
|
|
3905
|
+
log("");
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
log("");
|
|
3909
|
+
info("Updating...");
|
|
3910
|
+
try {
|
|
3911
|
+
let pm = "npm";
|
|
3912
|
+
try {
|
|
3913
|
+
execSync("pnpm --version", { stdio: "ignore", timeout: 5e3 });
|
|
3914
|
+
pm = "pnpm";
|
|
3915
|
+
} catch {
|
|
3916
|
+
try {
|
|
3917
|
+
execSync("bun --version", { stdio: "ignore", timeout: 5e3 });
|
|
3918
|
+
pm = "bun";
|
|
3919
|
+
} catch {
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
let isGlobal = false;
|
|
3923
|
+
try {
|
|
3924
|
+
const globalList = execSync(`${pm === "npm" ? "npm" : pm} list -g agenticmail 2>/dev/null`, { encoding: "utf-8", timeout: 1e4 });
|
|
3925
|
+
if (globalList.includes("agenticmail")) isGlobal = true;
|
|
3926
|
+
} catch {
|
|
3927
|
+
}
|
|
3928
|
+
const scope = isGlobal ? "-g" : "";
|
|
3929
|
+
const installCmd = pm === "bun" ? `bun add ${scope} agenticmail@latest` : `${pm} install ${scope} agenticmail@latest`;
|
|
3930
|
+
info(`Running: ${c.dim(installCmd)}`);
|
|
3931
|
+
execSync(installCmd, { stdio: "inherit", timeout: 12e4 });
|
|
3932
|
+
if (hasOpenClaw) {
|
|
3933
|
+
const pluginCmd = pm === "bun" ? `bun add ${scope} @agenticmail/openclaw@latest` : `${pm} install ${scope} @agenticmail/openclaw@latest`;
|
|
3934
|
+
info(`Updating OpenClaw plugin: ${c.dim(pluginCmd)}`);
|
|
3935
|
+
try {
|
|
3936
|
+
execSync(pluginCmd, { stdio: "inherit", timeout: 12e4 });
|
|
3937
|
+
ok("OpenClaw plugin updated.");
|
|
3938
|
+
try {
|
|
3939
|
+
execSync("openclaw gateway restart", { stdio: "pipe", timeout: 3e4 });
|
|
3940
|
+
ok("OpenClaw gateway restarted.");
|
|
3941
|
+
} catch {
|
|
3942
|
+
info(`Restart OpenClaw manually: ${c.green("openclaw gateway restart")}`);
|
|
3943
|
+
}
|
|
3944
|
+
} catch (err) {
|
|
3945
|
+
log(` ${c.yellow("!")} Plugin update failed: ${err.message}`);
|
|
3946
|
+
info(`Update manually: ${c.green(pluginCmd)}`);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
log("");
|
|
3950
|
+
ok(`Updated to agenticmail@${latestVersion}`);
|
|
3951
|
+
info("Restart the shell to use the new version.");
|
|
3952
|
+
log("");
|
|
3953
|
+
} catch (err) {
|
|
3954
|
+
fail(`Update failed: ${err.message}`);
|
|
3955
|
+
info(`Try manually: ${c.green("npm install -g agenticmail@latest")}`);
|
|
3956
|
+
log("");
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
},
|
|
3620
3960
|
exit: {
|
|
3621
3961
|
desc: "Stop the server and exit",
|
|
3622
3962
|
run: async () => {
|
|
@@ -4109,7 +4449,7 @@ async function cmdSetup() {
|
|
|
4109
4449
|
log2(` needs to send and receive real email.`);
|
|
4110
4450
|
log2("");
|
|
4111
4451
|
const hasOpenClaw = existsSync2(join(homedir(), ".openclaw", "openclaw.json"));
|
|
4112
|
-
const totalSteps = hasOpenClaw ?
|
|
4452
|
+
const totalSteps = hasOpenClaw ? 6 : 5;
|
|
4113
4453
|
log2(` Here's what we'll do:`);
|
|
4114
4454
|
log2(` ${c2.dim("1.")} Check your system for required tools`);
|
|
4115
4455
|
log2(` ${c2.dim("2.")} Create your private account and keys`);
|
|
@@ -4377,9 +4717,117 @@ async function cmdSetup() {
|
|
|
4377
4717
|
} else if (!existingEmail) {
|
|
4378
4718
|
info2("No problem! You can set up email anytime by running this again.");
|
|
4379
4719
|
}
|
|
4720
|
+
if (serverReady) {
|
|
4721
|
+
log2("");
|
|
4722
|
+
log2(` ${c2.bold(`Step 5 of ${totalSteps}`)} ${c2.dim("\u2014")} ${c2.bold("Phone number access (optional)")}`);
|
|
4723
|
+
log2("");
|
|
4724
|
+
log2(` ${c2.dim("Give your AI agent a phone number via Google Voice.")}`);
|
|
4725
|
+
log2(` ${c2.dim("This lets agents receive verification codes and send texts.")}`);
|
|
4726
|
+
log2("");
|
|
4727
|
+
const wantSms = await ask(` ${c2.bold("Set up phone number access?")} ${c2.dim("(y/N)")} `);
|
|
4728
|
+
if (wantSms.toLowerCase().startsWith("y")) {
|
|
4729
|
+
log2("");
|
|
4730
|
+
log2(` ${c2.bold("What this does:")}`);
|
|
4731
|
+
log2(` Your AI agent gets a real phone number it can use to:`);
|
|
4732
|
+
log2(` ${c2.dim("*")} Receive verification codes when signing up for services`);
|
|
4733
|
+
log2(` ${c2.dim("*")} Send and receive text messages`);
|
|
4734
|
+
log2(` ${c2.dim("*")} Verify accounts on platforms that require phone numbers`);
|
|
4735
|
+
log2("");
|
|
4736
|
+
log2(` ${c2.bold("How it works:")}`);
|
|
4737
|
+
log2(` Google Voice gives you a free US phone number. When someone`);
|
|
4738
|
+
log2(` texts that number, Google forwards it to your email. Your`);
|
|
4739
|
+
log2(` agent reads the email and extracts the message or code.`);
|
|
4740
|
+
log2("");
|
|
4741
|
+
const hasVoice = await ask(` ${c2.bold("Do you already have a Google Voice number?")} ${c2.dim("(y/N)")} `);
|
|
4742
|
+
if (!hasVoice.toLowerCase().startsWith("y")) {
|
|
4743
|
+
log2("");
|
|
4744
|
+
log2(` ${c2.bold("No problem! Setting up Google Voice takes about 2 minutes:")}`);
|
|
4745
|
+
log2("");
|
|
4746
|
+
log2(` ${c2.cyan("Step 1:")} Open ${c2.bold(c2.cyan("https://voice.google.com"))} in your browser`);
|
|
4747
|
+
log2(` ${c2.cyan("Step 2:")} Sign in with your Google account`);
|
|
4748
|
+
log2(` ${c2.cyan("Step 3:")} Click ${c2.bold('"Choose a phone number"')}`);
|
|
4749
|
+
log2(` ${c2.cyan("Step 4:")} Search for a number by city or area code`);
|
|
4750
|
+
log2(` ${c2.cyan("Step 5:")} Pick a number and click ${c2.bold('"Verify"')}`);
|
|
4751
|
+
log2(` ${c2.dim("(Google will verify via your existing phone number)")}`);
|
|
4752
|
+
log2(` ${c2.cyan("Step 6:")} Once verified, go to ${c2.bold("Settings")} (gear icon)`);
|
|
4753
|
+
log2(` ${c2.cyan("Step 7:")} Under Messages, enable ${c2.bold('"Forward messages to email"')}`);
|
|
4754
|
+
log2("");
|
|
4755
|
+
log2(` ${c2.dim("That's it! Come back here when you have your number.")}`);
|
|
4756
|
+
log2("");
|
|
4757
|
+
const ready = await ask(` ${c2.bold("Press Enter when you have your Google Voice number ready...")} `);
|
|
4758
|
+
}
|
|
4759
|
+
log2("");
|
|
4760
|
+
const phoneNumber = await ask(` ${c2.bold("Your Google Voice phone number")} ${c2.dim("(e.g. +12125551234):")} `);
|
|
4761
|
+
if (phoneNumber.trim()) {
|
|
4762
|
+
const digits = phoneNumber.replace(/[^+\d]/g, "").replace(/\D/g, "");
|
|
4763
|
+
if (digits.length < 10) {
|
|
4764
|
+
log2(` ${c2.yellow("!")} That doesn't look like a valid phone number (need at least 10 digits).`);
|
|
4765
|
+
info2("You can set this up later in the shell with /sms or via the agenticmail_sms_setup tool.");
|
|
4766
|
+
} else {
|
|
4767
|
+
const forwardEmail = await ask(` ${c2.bold("Email Google Voice forwards SMS to")} ${c2.dim("(Enter to use agent email):")} `);
|
|
4768
|
+
try {
|
|
4769
|
+
const apiBase = `http://${result.config.api.host}:${result.config.api.port}`;
|
|
4770
|
+
const resp = await fetch(`${apiBase}/api/agenticmail/sms/setup`, {
|
|
4771
|
+
method: "POST",
|
|
4772
|
+
headers: {
|
|
4773
|
+
"Content-Type": "application/json",
|
|
4774
|
+
"Authorization": `Bearer ${result.config.masterKey}`
|
|
4775
|
+
},
|
|
4776
|
+
body: JSON.stringify({
|
|
4777
|
+
phoneNumber: phoneNumber.trim(),
|
|
4778
|
+
forwardingEmail: forwardEmail.trim() || void 0
|
|
4779
|
+
})
|
|
4780
|
+
});
|
|
4781
|
+
const data = await resp.json();
|
|
4782
|
+
if (data.success) {
|
|
4783
|
+
log2("");
|
|
4784
|
+
log2(` ${c2.green("\u2714")} Phone number saved: ${c2.bold(data.sms?.phoneNumber || phoneNumber.trim())}`);
|
|
4785
|
+
log2("");
|
|
4786
|
+
log2(` ${c2.bold("Important:")} Make sure you enabled SMS forwarding in Google Voice:`);
|
|
4787
|
+
log2(` ${c2.dim("voice.google.com > Settings > Messages > Forward messages to email")}`);
|
|
4788
|
+
log2("");
|
|
4789
|
+
log2(` ${c2.dim("Your agent can now receive verification codes and text messages.")}`);
|
|
4790
|
+
log2(` ${c2.dim("Manage SMS anytime in the shell with /sms")}`);
|
|
4791
|
+
} else {
|
|
4792
|
+
throw new Error(data.error || "API call failed");
|
|
4793
|
+
}
|
|
4794
|
+
} catch {
|
|
4795
|
+
try {
|
|
4796
|
+
const { readFileSync: readFileSync3, writeFileSync: writeFileSync3 } = await import("fs");
|
|
4797
|
+
const { join: join2 } = await import("path");
|
|
4798
|
+
const os = await import("os");
|
|
4799
|
+
const configPath = join2(result.config.dataDir || os.homedir() + "/.agenticmail", "config.json");
|
|
4800
|
+
let fileConfig = {};
|
|
4801
|
+
try {
|
|
4802
|
+
fileConfig = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
4803
|
+
} catch {
|
|
4804
|
+
}
|
|
4805
|
+
fileConfig.sms = {
|
|
4806
|
+
enabled: true,
|
|
4807
|
+
phoneNumber: phoneNumber.trim(),
|
|
4808
|
+
forwardingEmail: forwardEmail.trim() || "",
|
|
4809
|
+
provider: "google_voice",
|
|
4810
|
+
configuredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4811
|
+
};
|
|
4812
|
+
writeFileSync3(configPath, JSON.stringify(fileConfig, null, 2), { encoding: "utf-8", mode: 384 });
|
|
4813
|
+
log2(` ${c2.green("\u2714")} Phone number saved: ${c2.bold(phoneNumber.trim())}`);
|
|
4814
|
+
log2(` ${c2.dim("Make sure SMS forwarding is enabled in Google Voice settings.")}`);
|
|
4815
|
+
} catch (err) {
|
|
4816
|
+
log2(` ${c2.yellow("!")} Could not save: ${err.message}`);
|
|
4817
|
+
log2(` ${c2.dim("Set up later in the shell with /sms")}`);
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
} else {
|
|
4822
|
+
info2("Skipped. Set up anytime in the shell with /sms");
|
|
4823
|
+
}
|
|
4824
|
+
} else {
|
|
4825
|
+
info2("Skipped. Add a phone number anytime with /sms in the shell.");
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4380
4828
|
if (hasOpenClaw && serverReady) {
|
|
4381
4829
|
log2("");
|
|
4382
|
-
log2(` ${c2.bold(`Step
|
|
4830
|
+
log2(` ${c2.bold(`Step 6 of ${totalSteps}`)} ${c2.dim("\u2014")} ${c2.bold("Configure OpenClaw integration")}`);
|
|
4383
4831
|
log2("");
|
|
4384
4832
|
await registerWithOpenClaw(result.config);
|
|
4385
4833
|
}
|
|
@@ -4968,11 +5416,12 @@ async function cmdOpenClaw() {
|
|
|
4968
5416
|
log2(` This will:`);
|
|
4969
5417
|
log2(` ${c2.dim("1.")} Set up the mail server infrastructure`);
|
|
4970
5418
|
log2(` ${c2.dim("2.")} Create an agent email account`);
|
|
4971
|
-
log2(` ${c2.dim("3.")}
|
|
4972
|
-
log2(` ${c2.dim("4.")}
|
|
5419
|
+
log2(` ${c2.dim("3.")} Set up phone number access ${c2.green("NEW")}`);
|
|
5420
|
+
log2(` ${c2.dim("4.")} Configure the OpenClaw plugin`);
|
|
5421
|
+
log2(` ${c2.dim("5.")} Restart the OpenClaw gateway`);
|
|
4973
5422
|
log2("");
|
|
4974
5423
|
const setup = new SetupManager();
|
|
4975
|
-
log2(` ${c2.bold("Step 1 of
|
|
5424
|
+
log2(` ${c2.bold("Step 1 of 6")} ${c2.dim("\u2014")} ${c2.bold("Checking infrastructure")}`);
|
|
4976
5425
|
log2("");
|
|
4977
5426
|
let config;
|
|
4978
5427
|
let configPath;
|
|
@@ -5031,7 +5480,7 @@ async function cmdOpenClaw() {
|
|
|
5031
5480
|
}
|
|
5032
5481
|
}
|
|
5033
5482
|
log2("");
|
|
5034
|
-
log2(` ${c2.bold("Step 2 of
|
|
5483
|
+
log2(` ${c2.bold("Step 2 of 6")} ${c2.dim("\u2014")} ${c2.bold("Starting server")}`);
|
|
5035
5484
|
log2("");
|
|
5036
5485
|
const apiHost = config.api.host;
|
|
5037
5486
|
const apiPort = config.api.port;
|
|
@@ -5062,7 +5511,7 @@ async function cmdOpenClaw() {
|
|
|
5062
5511
|
}
|
|
5063
5512
|
}
|
|
5064
5513
|
log2("");
|
|
5065
|
-
log2(` ${c2.bold("Step 3 of
|
|
5514
|
+
log2(` ${c2.bold("Step 3 of 6")} ${c2.dim("\u2014")} ${c2.bold("Agent account")}`);
|
|
5066
5515
|
log2("");
|
|
5067
5516
|
let agentApiKey;
|
|
5068
5517
|
let agentEmail = "";
|
|
@@ -5234,7 +5683,212 @@ async function cmdOpenClaw() {
|
|
|
5234
5683
|
}
|
|
5235
5684
|
}
|
|
5236
5685
|
log2("");
|
|
5237
|
-
log2(` ${c2.bold("Step 4 of
|
|
5686
|
+
log2(` ${c2.bold("Step 4 of 6")} ${c2.dim("\u2014")} ${c2.bold("Phone number access")} ${c2.green("NEW")}`);
|
|
5687
|
+
log2("");
|
|
5688
|
+
log2(` ${c2.dim("Give your AI agent a phone number via Google Voice.")}`);
|
|
5689
|
+
log2(` ${c2.dim("Agents can receive verification codes and send texts.")}`);
|
|
5690
|
+
log2("");
|
|
5691
|
+
let smsAlreadyConfigured = false;
|
|
5692
|
+
if (agentApiKey) {
|
|
5693
|
+
try {
|
|
5694
|
+
const smsResp = await fetch(`${apiBase}/api/agenticmail/sms/config`, {
|
|
5695
|
+
headers: { "Authorization": `Bearer ${agentApiKey}` },
|
|
5696
|
+
signal: AbortSignal.timeout(3e3)
|
|
5697
|
+
});
|
|
5698
|
+
const smsData = await smsResp.json();
|
|
5699
|
+
if (smsData.sms?.enabled) {
|
|
5700
|
+
smsAlreadyConfigured = true;
|
|
5701
|
+
ok2(`SMS already configured: ${c2.bold(smsData.sms.phoneNumber)}`);
|
|
5702
|
+
}
|
|
5703
|
+
} catch {
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
if (!smsAlreadyConfigured) {
|
|
5707
|
+
const wantSms = await ask(` ${c2.bold("Set up phone number access?")} ${c2.dim("(y/N)")} `);
|
|
5708
|
+
if (wantSms.toLowerCase().startsWith("y")) {
|
|
5709
|
+
log2("");
|
|
5710
|
+
const hasVoice = await ask(` ${c2.bold("Do you already have a Google Voice number?")} ${c2.dim("(y/N)")} `);
|
|
5711
|
+
if (!hasVoice.toLowerCase().startsWith("y")) {
|
|
5712
|
+
log2("");
|
|
5713
|
+
log2(` ${c2.bold("Google Voice Setup (takes about 2 minutes):")}`);
|
|
5714
|
+
log2("");
|
|
5715
|
+
log2(` ${c2.cyan("Step 1:")} Open ${c2.bold(c2.cyan("https://voice.google.com"))} in your browser`);
|
|
5716
|
+
log2(` ${c2.cyan("Step 2:")} Sign in with your Google account`);
|
|
5717
|
+
log2(` ${c2.cyan("Step 3:")} Click ${c2.bold('"Choose a phone number"')}`);
|
|
5718
|
+
log2(` ${c2.cyan("Step 4:")} Search for a number by city or area code`);
|
|
5719
|
+
log2(` ${c2.cyan("Step 5:")} Pick a number and click ${c2.bold('"Verify"')}`);
|
|
5720
|
+
log2(` ${c2.dim("(Google will send a code to your existing phone to verify)")}`);
|
|
5721
|
+
log2(` ${c2.cyan("Step 6:")} Once verified, go to ${c2.bold("Settings")} (gear icon)`);
|
|
5722
|
+
log2(` ${c2.cyan("Step 7:")} Under Messages, enable ${c2.bold('"Forward messages to email"')}`);
|
|
5723
|
+
log2("");
|
|
5724
|
+
log2(` ${c2.dim("Come back here when you have your number.")}`);
|
|
5725
|
+
log2("");
|
|
5726
|
+
await ask(` ${c2.bold("Press Enter when ready...")} `);
|
|
5727
|
+
}
|
|
5728
|
+
log2("");
|
|
5729
|
+
const phoneNumber = await ask(` ${c2.bold("Your Google Voice number")} ${c2.dim("(e.g. +12125551234):")} `);
|
|
5730
|
+
if (phoneNumber.trim()) {
|
|
5731
|
+
const digits = phoneNumber.replace(/[^+\d]/g, "").replace(/\D/g, "");
|
|
5732
|
+
if (digits.length < 10) {
|
|
5733
|
+
log2(` ${c2.yellow("!")} That doesn't look like a valid phone number.`);
|
|
5734
|
+
info2("You can set this up later in the shell with /sms");
|
|
5735
|
+
} else {
|
|
5736
|
+
let forwardingEmail;
|
|
5737
|
+
let forwardingPassword;
|
|
5738
|
+
let relayEmail = "";
|
|
5739
|
+
let relayProvider = "";
|
|
5740
|
+
try {
|
|
5741
|
+
const gwResp = await fetch(`${apiBase}/api/agenticmail/gateway/status`, {
|
|
5742
|
+
headers: { "Authorization": `Bearer ${config.masterKey}` },
|
|
5743
|
+
signal: AbortSignal.timeout(5e3)
|
|
5744
|
+
});
|
|
5745
|
+
const gwData = await gwResp.json();
|
|
5746
|
+
if (gwData.mode === "relay" && gwData.relay?.email) {
|
|
5747
|
+
relayEmail = gwData.relay.email;
|
|
5748
|
+
relayProvider = gwData.relay.provider || "";
|
|
5749
|
+
} else if (gwData.mode === "domain") {
|
|
5750
|
+
relayProvider = "domain";
|
|
5751
|
+
}
|
|
5752
|
+
} catch {
|
|
5753
|
+
}
|
|
5754
|
+
log2("");
|
|
5755
|
+
log2(` \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`);
|
|
5756
|
+
log2(` \u2502 ${c2.red(c2.bold("READ THIS"))} \u2014 Google Voice email matching is ${c2.red(c2.bold("critical"))} \u2502`);
|
|
5757
|
+
log2(` \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`);
|
|
5758
|
+
log2("");
|
|
5759
|
+
log2(` Google Voice forwards SMS ${c2.bold("ONLY")} to the ${c2.green(c2.bold("Gmail account"))}`);
|
|
5760
|
+
log2(` you used to ${c2.green(c2.bold("sign up"))} for Google Voice.`);
|
|
5761
|
+
log2("");
|
|
5762
|
+
log2(` ${c2.yellow("If your agent can't read that Gmail, it will")} ${c2.red(c2.bold("NEVER"))}`);
|
|
5763
|
+
log2(` ${c2.yellow("receive any SMS messages.")}`);
|
|
5764
|
+
log2("");
|
|
5765
|
+
if (relayEmail) {
|
|
5766
|
+
const isGmail = relayEmail.toLowerCase().endsWith("@gmail.com");
|
|
5767
|
+
log2(` Your email relay: ${c2.bold(c2.cyan(relayEmail))}`);
|
|
5768
|
+
if (!isGmail) {
|
|
5769
|
+
log2("");
|
|
5770
|
+
log2(` ${c2.red(c2.bold("!!"))} Your relay is ${c2.bold("not Gmail")}. Google Voice won't forward here.`);
|
|
5771
|
+
log2(` ${c2.red(c2.bold("!!"))} You ${c2.bold("must")} provide the Gmail you used for Google Voice.`);
|
|
5772
|
+
log2("");
|
|
5773
|
+
const gvEmail = await ask(` ${c2.green(c2.bold("Gmail used for Google Voice:"))} `);
|
|
5774
|
+
if (gvEmail.trim() && gvEmail.toLowerCase().includes("@gmail.com")) {
|
|
5775
|
+
log2("");
|
|
5776
|
+
log2(` ${c2.dim("Get an app password at:")} ${c2.cyan("https://myaccount.google.com/apppasswords")}`);
|
|
5777
|
+
const gvPass = await ask(` ${c2.green(c2.bold("App password for"))} ${c2.bold(gvEmail.trim())}: `);
|
|
5778
|
+
if (gvPass.trim()) {
|
|
5779
|
+
forwardingEmail = gvEmail.trim();
|
|
5780
|
+
forwardingPassword = gvPass.trim();
|
|
5781
|
+
} else {
|
|
5782
|
+
log2(` ${c2.red("!")} No password. ${c2.yellow("SMS will not work without this.")}`);
|
|
5783
|
+
log2(` ${c2.dim("Fix later with /sms in the shell.")}`);
|
|
5784
|
+
}
|
|
5785
|
+
} else if (gvEmail.trim()) {
|
|
5786
|
+
log2(` ${c2.red("!")} Google Voice requires a ${c2.bold("Gmail")} address (ends in @gmail.com).`);
|
|
5787
|
+
log2(` ${c2.dim("Fix later with /sms in the shell.")}`);
|
|
5788
|
+
} else {
|
|
5789
|
+
log2(` ${c2.yellow("!")} Skipped. ${c2.dim("SMS will not work until you provide this.")}`);
|
|
5790
|
+
log2(` ${c2.dim("Fix later with /sms in the shell.")}`);
|
|
5791
|
+
}
|
|
5792
|
+
} else {
|
|
5793
|
+
log2("");
|
|
5794
|
+
log2(` ${c2.yellow("?")} Did you sign up for Google Voice with ${c2.bold("this same Gmail")}?`);
|
|
5795
|
+
log2(` ${c2.dim("If you used a different Gmail for Google Voice, say no.")}`);
|
|
5796
|
+
log2("");
|
|
5797
|
+
const sameEmail = await ask(` ${c2.bold("Same Gmail as Google Voice?")} ${c2.dim("(Y/n)")} `);
|
|
5798
|
+
if (sameEmail.toLowerCase().startsWith("n")) {
|
|
5799
|
+
log2("");
|
|
5800
|
+
log2(` ${c2.yellow(c2.bold("Different Gmail detected."))} Your agent needs access to the`);
|
|
5801
|
+
log2(` Google Voice Gmail to receive SMS.`);
|
|
5802
|
+
log2("");
|
|
5803
|
+
const gvEmail = await ask(` ${c2.green(c2.bold("Gmail used for Google Voice:"))} `);
|
|
5804
|
+
if (gvEmail.trim() && gvEmail.toLowerCase().includes("@gmail.com")) {
|
|
5805
|
+
if (gvEmail.trim().toLowerCase() === relayEmail.toLowerCase()) {
|
|
5806
|
+
log2(` ${c2.green("!")} That's the same email as your relay \u2014 you're all set!`);
|
|
5807
|
+
} else {
|
|
5808
|
+
log2("");
|
|
5809
|
+
log2(` ${c2.dim("Get an app password at:")} ${c2.cyan("https://myaccount.google.com/apppasswords")}`);
|
|
5810
|
+
const gvPass = await ask(` ${c2.green(c2.bold("App password for"))} ${c2.bold(gvEmail.trim())}: `);
|
|
5811
|
+
if (gvPass.trim()) {
|
|
5812
|
+
forwardingEmail = gvEmail.trim();
|
|
5813
|
+
forwardingPassword = gvPass.trim();
|
|
5814
|
+
} else {
|
|
5815
|
+
log2(` ${c2.red("!")} No password. ${c2.yellow("SMS will not work without this.")}`);
|
|
5816
|
+
log2(` ${c2.dim("Fix later with /sms in the shell.")}`);
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
} else if (gvEmail.trim()) {
|
|
5820
|
+
log2(` ${c2.red("!")} Google Voice requires a ${c2.bold("Gmail")} address.`);
|
|
5821
|
+
log2(` ${c2.dim("Fix later with /sms in the shell.")}`);
|
|
5822
|
+
} else {
|
|
5823
|
+
log2(` ${c2.yellow("!")} Skipped. ${c2.dim("SMS may not work if emails don't match.")}`);
|
|
5824
|
+
log2(` ${c2.dim("Fix later with /sms in the shell.")}`);
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
}
|
|
5828
|
+
} else if (relayProvider === "domain") {
|
|
5829
|
+
log2(` ${c2.yellow("!")} You're using ${c2.bold("domain mode")} (no Gmail relay).`);
|
|
5830
|
+
log2(` ${c2.yellow("!")} Google Voice needs a Gmail. Provide the one you signed up with.`);
|
|
5831
|
+
log2("");
|
|
5832
|
+
const gvEmail = await ask(` ${c2.green(c2.bold("Gmail used for Google Voice:"))} `);
|
|
5833
|
+
if (gvEmail.trim() && gvEmail.toLowerCase().includes("@gmail.com")) {
|
|
5834
|
+
log2("");
|
|
5835
|
+
log2(` ${c2.dim("Get an app password at:")} ${c2.cyan("https://myaccount.google.com/apppasswords")}`);
|
|
5836
|
+
const gvPass = await ask(` ${c2.green(c2.bold("App password for"))} ${c2.bold(gvEmail.trim())}: `);
|
|
5837
|
+
if (gvPass.trim()) {
|
|
5838
|
+
forwardingEmail = gvEmail.trim();
|
|
5839
|
+
forwardingPassword = gvPass.trim();
|
|
5840
|
+
} else {
|
|
5841
|
+
log2(` ${c2.red("!")} No password. ${c2.yellow("SMS will not work without this.")}`);
|
|
5842
|
+
log2(` ${c2.dim("Fix later with /sms in the shell.")}`);
|
|
5843
|
+
}
|
|
5844
|
+
} else {
|
|
5845
|
+
log2(` ${c2.yellow("!")} Skipped. ${c2.dim("SMS will not work until you provide this.")}`);
|
|
5846
|
+
log2(` ${c2.dim("Fix later with /sms in the shell.")}`);
|
|
5847
|
+
}
|
|
5848
|
+
} else {
|
|
5849
|
+
log2(` ${c2.dim("No email relay detected yet.")}`);
|
|
5850
|
+
log2("");
|
|
5851
|
+
const gvEmail = await ask(` ${c2.green(c2.bold("Gmail used for Google Voice:"))} `);
|
|
5852
|
+
if (gvEmail.trim() && gvEmail.includes("@")) {
|
|
5853
|
+
forwardingEmail = gvEmail.trim();
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
if (agentApiKey) {
|
|
5857
|
+
try {
|
|
5858
|
+
const body = { phoneNumber: phoneNumber.trim() };
|
|
5859
|
+
if (forwardingEmail) body.forwardingEmail = forwardingEmail;
|
|
5860
|
+
if (forwardingPassword) body.forwardingPassword = forwardingPassword;
|
|
5861
|
+
const resp = await fetch(`${apiBase}/api/agenticmail/sms/setup`, {
|
|
5862
|
+
method: "POST",
|
|
5863
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${agentApiKey}` },
|
|
5864
|
+
body: JSON.stringify(body)
|
|
5865
|
+
});
|
|
5866
|
+
const data = await resp.json();
|
|
5867
|
+
if (data.success) {
|
|
5868
|
+
log2("");
|
|
5869
|
+
ok2(`Phone number saved: ${c2.bold(data.sms?.phoneNumber || phoneNumber.trim())}`);
|
|
5870
|
+
if (forwardingEmail) {
|
|
5871
|
+
ok2(`SMS forwarding via: ${c2.bold(forwardingEmail)}`);
|
|
5872
|
+
}
|
|
5873
|
+
log2(` ${c2.dim('Remember: enable "Forward messages to email" in Google Voice settings')}`);
|
|
5874
|
+
log2(` ${c2.dim("Manage SMS anytime in the shell with /sms")}`);
|
|
5875
|
+
} else {
|
|
5876
|
+
fail2(data.error || "Setup failed");
|
|
5877
|
+
}
|
|
5878
|
+
} catch (err) {
|
|
5879
|
+
fail2(err.message);
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
} else {
|
|
5884
|
+
info2("Skipped. Use /sms in the shell anytime.");
|
|
5885
|
+
}
|
|
5886
|
+
} else {
|
|
5887
|
+
info2("Skipped. Add a phone number anytime with /sms in the shell.");
|
|
5888
|
+
}
|
|
5889
|
+
}
|
|
5890
|
+
log2("");
|
|
5891
|
+
log2(` ${c2.bold("Step 5 of 6")} ${c2.dim("\u2014")} ${c2.bold("Installing plugin + configuring OpenClaw")}`);
|
|
5238
5892
|
log2("");
|
|
5239
5893
|
const pluginDir = resolveOpenClawPluginDir();
|
|
5240
5894
|
if (pluginDir) {
|
|
@@ -5308,7 +5962,7 @@ async function cmdOpenClaw() {
|
|
|
5308
5962
|
}
|
|
5309
5963
|
}
|
|
5310
5964
|
log2("");
|
|
5311
|
-
log2(` ${c2.bold("Step
|
|
5965
|
+
log2(` ${c2.bold("Step 6 of 6")} ${c2.dim("\u2014")} ${c2.bold("Restarting OpenClaw gateway")}`);
|
|
5312
5966
|
log2("");
|
|
5313
5967
|
let gatewayRestarted = false;
|
|
5314
5968
|
let hasOpenClawCli = false;
|
|
@@ -5356,19 +6010,64 @@ async function cmdOpenClaw() {
|
|
|
5356
6010
|
}
|
|
5357
6011
|
log2(` ${c2.dim("Master Key:")} ${c2.yellow(config.masterKey)}`);
|
|
5358
6012
|
log2(` ${c2.dim("Server:")} ${c2.cyan(apiBase)}`);
|
|
6013
|
+
let smsPhone = "";
|
|
6014
|
+
if (agentApiKey) {
|
|
6015
|
+
try {
|
|
6016
|
+
const smsResp = await fetch(`${apiBase}/api/agenticmail/sms/config`, {
|
|
6017
|
+
headers: { "Authorization": `Bearer ${agentApiKey}` },
|
|
6018
|
+
signal: AbortSignal.timeout(3e3)
|
|
6019
|
+
});
|
|
6020
|
+
const smsData = await smsResp.json();
|
|
6021
|
+
if (smsData.sms?.enabled && smsData.sms?.phoneNumber) {
|
|
6022
|
+
smsPhone = smsData.sms.phoneNumber;
|
|
6023
|
+
log2(` ${c2.dim("Phone:")} ${c2.green(smsPhone)} ${c2.dim("via Google Voice")}`);
|
|
6024
|
+
}
|
|
6025
|
+
} catch {
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
if (!smsPhone) {
|
|
6029
|
+
try {
|
|
6030
|
+
const acctResp = await fetch(`${apiBase}/api/agenticmail/accounts`, {
|
|
6031
|
+
headers: { "Authorization": `Bearer ${config.masterKey}` },
|
|
6032
|
+
signal: AbortSignal.timeout(3e3)
|
|
6033
|
+
});
|
|
6034
|
+
const acctData = await acctResp.json();
|
|
6035
|
+
for (const a of acctData.agents || []) {
|
|
6036
|
+
const smsConf = a.metadata?.sms;
|
|
6037
|
+
if (smsConf?.enabled && smsConf.phoneNumber) {
|
|
6038
|
+
smsPhone = smsConf.phoneNumber;
|
|
6039
|
+
log2(` ${c2.dim("Phone:")} ${c2.green(smsPhone)} ${c2.dim("via Google Voice")} ${c2.dim("(" + a.name + ")")}`);
|
|
6040
|
+
break;
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
} catch {
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
5359
6046
|
log2("");
|
|
5360
6047
|
if (gatewayRestarted) {
|
|
5361
|
-
log2(` Your agent now has ${c2.bold("
|
|
6048
|
+
log2(` Your agent now has ${c2.bold("63 email + SMS tools")} available!`);
|
|
5362
6049
|
log2(` Try: ${c2.dim('"Send an email to test@example.com"')}`);
|
|
5363
6050
|
log2("");
|
|
5364
6051
|
log2(` ${c2.bold("\u{1F380} AgenticMail Coordination")} ${c2.dim("(auto-configured)")}`);
|
|
5365
6052
|
log2(` Your agent can now use ${c2.cyan("agenticmail_call_agent")} to call other agents`);
|
|
5366
6053
|
log2(` with structured task queues, push notifications, and auto-spawned sessions.`);
|
|
5367
6054
|
log2(` This replaces sessions_spawn for coordinated multi-agent work.`);
|
|
6055
|
+
log2("");
|
|
6056
|
+
if (smsPhone) {
|
|
6057
|
+
log2(` ${c2.bold("\u{1F4F1} SMS & Phone Access")} ${c2.green("ACTIVE")}`);
|
|
6058
|
+
log2(` Phone: ${c2.bold(smsPhone)} via Google Voice`);
|
|
6059
|
+
log2(` Your agent can receive verification codes and send texts.`);
|
|
6060
|
+
log2(` Manage with ${c2.cyan("/sms")} in the shell.`);
|
|
6061
|
+
} else {
|
|
6062
|
+
log2(` ${c2.bold("\u{1F4F1} SMS & Phone Access")} ${c2.dim("(Google Voice)")}`);
|
|
6063
|
+
log2(` Your agent can receive verification codes and send texts.`);
|
|
6064
|
+
log2(` SMS messages are auto-detected from Google Voice email forwarding.`);
|
|
6065
|
+
log2(` Set up with ${c2.cyan("/sms")} in the shell or during setup wizard.`);
|
|
6066
|
+
}
|
|
5368
6067
|
} else {
|
|
5369
6068
|
log2(` ${c2.bold("Next step:")}`);
|
|
5370
6069
|
log2(` Restart your OpenClaw gateway, then your agent will`);
|
|
5371
|
-
log2(` have ${c2.bold("
|
|
6070
|
+
log2(` have ${c2.bold("63 email + SMS tools")} available!`);
|
|
5372
6071
|
}
|
|
5373
6072
|
log2("");
|
|
5374
6073
|
if (process.stdin.isTTY) {
|
|
@@ -5539,6 +6238,94 @@ async function cmdStop() {
|
|
|
5539
6238
|
}
|
|
5540
6239
|
log2("");
|
|
5541
6240
|
}
|
|
6241
|
+
async function cmdUpdate() {
|
|
6242
|
+
const { execSync } = await import("child_process");
|
|
6243
|
+
log2("");
|
|
6244
|
+
log2(` ${c2.dim("\u2500".repeat(50))}`);
|
|
6245
|
+
log2(` ${c2.bold("Update AgenticMail")}`);
|
|
6246
|
+
log2("");
|
|
6247
|
+
let currentVersion = "unknown";
|
|
6248
|
+
try {
|
|
6249
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
6250
|
+
const { join: join2, dirname: dirname2 } = await import("path");
|
|
6251
|
+
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
6252
|
+
const thisDir = dirname2(fileURLToPath2(import.meta.url));
|
|
6253
|
+
const pkg = JSON.parse(readFileSync3(join2(thisDir, "..", "package.json"), "utf-8"));
|
|
6254
|
+
currentVersion = pkg.version ?? "unknown";
|
|
6255
|
+
} catch {
|
|
6256
|
+
}
|
|
6257
|
+
info2(`Current version: ${c2.bold(currentVersion)}`);
|
|
6258
|
+
let latestVersion = "unknown";
|
|
6259
|
+
try {
|
|
6260
|
+
latestVersion = execSync("npm view agenticmail version", { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
6261
|
+
} catch {
|
|
6262
|
+
fail2("Could not check npm. Check your internet connection.");
|
|
6263
|
+
process.exit(1);
|
|
6264
|
+
}
|
|
6265
|
+
info2(`Latest version: ${c2.bold(latestVersion)}`);
|
|
6266
|
+
if (currentVersion === latestVersion) {
|
|
6267
|
+
ok2("Already on the latest version!");
|
|
6268
|
+
log2("");
|
|
6269
|
+
process.exit(0);
|
|
6270
|
+
}
|
|
6271
|
+
info2(`New version available: ${c2.yellow(currentVersion)} \u2192 ${c2.green(latestVersion)}`);
|
|
6272
|
+
log2("");
|
|
6273
|
+
let hasOpenClaw = false;
|
|
6274
|
+
try {
|
|
6275
|
+
execSync("which openclaw", { stdio: "ignore", timeout: 5e3 });
|
|
6276
|
+
hasOpenClaw = true;
|
|
6277
|
+
const ocVersion = execSync('openclaw --version 2>/dev/null || echo "?"', { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
6278
|
+
info2(`OpenClaw detected: ${c2.bold(ocVersion)}`);
|
|
6279
|
+
} catch {
|
|
6280
|
+
}
|
|
6281
|
+
let pm = "npm";
|
|
6282
|
+
try {
|
|
6283
|
+
execSync("pnpm --version", { stdio: "ignore", timeout: 5e3 });
|
|
6284
|
+
pm = "pnpm";
|
|
6285
|
+
} catch {
|
|
6286
|
+
try {
|
|
6287
|
+
execSync("bun --version", { stdio: "ignore", timeout: 5e3 });
|
|
6288
|
+
pm = "bun";
|
|
6289
|
+
} catch {
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
let isGlobal = false;
|
|
6293
|
+
try {
|
|
6294
|
+
const list = execSync(`npm list -g agenticmail 2>/dev/null`, { encoding: "utf-8", timeout: 1e4 });
|
|
6295
|
+
if (list.includes("agenticmail@")) isGlobal = true;
|
|
6296
|
+
} catch {
|
|
6297
|
+
}
|
|
6298
|
+
const scope = isGlobal ? "-g" : "";
|
|
6299
|
+
const installCmd = pm === "bun" ? `bun add ${scope} agenticmail@latest`.trim() : `${pm} install ${scope} agenticmail@latest`.trim();
|
|
6300
|
+
info2(`Running: ${c2.dim(installCmd)}`);
|
|
6301
|
+
try {
|
|
6302
|
+
execSync(installCmd, { stdio: "inherit", timeout: 12e4 });
|
|
6303
|
+
ok2(`Updated to agenticmail@${latestVersion}`);
|
|
6304
|
+
} catch (err) {
|
|
6305
|
+
fail2(`Update failed: ${err.message}`);
|
|
6306
|
+
info2(`Try: ${c2.green("npm install -g agenticmail@latest")}`);
|
|
6307
|
+
process.exit(1);
|
|
6308
|
+
}
|
|
6309
|
+
if (hasOpenClaw) {
|
|
6310
|
+
const pluginCmd = pm === "bun" ? `bun add ${scope} @agenticmail/openclaw@latest`.trim() : `${pm} install ${scope} @agenticmail/openclaw@latest`.trim();
|
|
6311
|
+
info2(`Updating OpenClaw plugin: ${c2.dim(pluginCmd)}`);
|
|
6312
|
+
try {
|
|
6313
|
+
execSync(pluginCmd, { stdio: "inherit", timeout: 12e4 });
|
|
6314
|
+
ok2("OpenClaw plugin updated.");
|
|
6315
|
+
try {
|
|
6316
|
+
execSync("openclaw gateway restart", { stdio: "pipe", timeout: 3e4 });
|
|
6317
|
+
ok2("OpenClaw gateway restarted.");
|
|
6318
|
+
} catch {
|
|
6319
|
+
info2(`Restart OpenClaw: ${c2.green("openclaw gateway restart")}`);
|
|
6320
|
+
}
|
|
6321
|
+
} catch {
|
|
6322
|
+
info2(`Update plugin manually: ${c2.green(pluginCmd)}`);
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
log2("");
|
|
6326
|
+
ok2("Update complete!");
|
|
6327
|
+
log2("");
|
|
6328
|
+
}
|
|
5542
6329
|
var command = process.argv[2];
|
|
5543
6330
|
switch (command) {
|
|
5544
6331
|
case "setup":
|
|
@@ -5575,6 +6362,12 @@ switch (command) {
|
|
|
5575
6362
|
process.exit(1);
|
|
5576
6363
|
});
|
|
5577
6364
|
break;
|
|
6365
|
+
case "update":
|
|
6366
|
+
cmdUpdate().catch((err) => {
|
|
6367
|
+
console.error(err);
|
|
6368
|
+
process.exit(1);
|
|
6369
|
+
});
|
|
6370
|
+
break;
|
|
5578
6371
|
case "help":
|
|
5579
6372
|
case "--help":
|
|
5580
6373
|
case "-h":
|
|
@@ -5588,6 +6381,7 @@ switch (command) {
|
|
|
5588
6381
|
log2(` ${c2.green("agenticmail stop")} Stop the server`);
|
|
5589
6382
|
log2(` ${c2.green("agenticmail status")} See what's running`);
|
|
5590
6383
|
log2(` ${c2.green("agenticmail openclaw")} Set up AgenticMail for OpenClaw`);
|
|
6384
|
+
log2(` ${c2.green("agenticmail update")} Update to the latest version`);
|
|
5591
6385
|
log2("");
|
|
5592
6386
|
process.exit(0);
|
|
5593
6387
|
default:
|