agenticmail 0.3.24 → 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 +890 -136
- 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { createInterface as createInterface2, emitKeypressEvents as emitKeypressEvents2 } from "readline";
|
|
5
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, realpathSync } from "fs";
|
|
5
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, realpathSync, unlinkSync } from "fs";
|
|
6
6
|
import { join, dirname } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { createRequire } from "module";
|
|
@@ -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 () => {
|
|
@@ -4054,22 +4394,53 @@ async function waitForApi(host, port, timeoutMs = 15e3) {
|
|
|
4054
4394
|
}
|
|
4055
4395
|
return false;
|
|
4056
4396
|
}
|
|
4057
|
-
var
|
|
4058
|
-
function
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4397
|
+
var PID_FILE = join(homedir(), ".agenticmail", "server.pid");
|
|
4398
|
+
async function startApiServer(config) {
|
|
4399
|
+
const host = config.api.host;
|
|
4400
|
+
const port = config.api.port;
|
|
4401
|
+
try {
|
|
4402
|
+
const probe = await fetch(`http://${host}:${port}/api/agenticmail/health`, {
|
|
4403
|
+
signal: AbortSignal.timeout(2e3)
|
|
4404
|
+
});
|
|
4405
|
+
if (probe.ok) return true;
|
|
4406
|
+
} catch {
|
|
4407
|
+
}
|
|
4408
|
+
const { spawn } = await import("child_process");
|
|
4409
|
+
const apiEntry = resolveApiEntry();
|
|
4410
|
+
const env = configToEnv(config);
|
|
4411
|
+
const child = spawn(process.execPath, [apiEntry], {
|
|
4412
|
+
detached: true,
|
|
4413
|
+
stdio: "ignore",
|
|
4414
|
+
env
|
|
4415
|
+
});
|
|
4416
|
+
child.unref();
|
|
4417
|
+
if (child.pid) {
|
|
4418
|
+
try {
|
|
4419
|
+
writeFileSync2(PID_FILE, String(child.pid));
|
|
4420
|
+
} catch {
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
return waitForApi(host, port);
|
|
4424
|
+
}
|
|
4425
|
+
function stopApiServer() {
|
|
4426
|
+
try {
|
|
4427
|
+
if (!existsSync2(PID_FILE)) return false;
|
|
4428
|
+
const pid = parseInt(readFileSync2(PID_FILE, "utf-8").trim(), 10);
|
|
4429
|
+
if (isNaN(pid)) return false;
|
|
4430
|
+
process.kill(pid, "SIGTERM");
|
|
4431
|
+
try {
|
|
4432
|
+
unlinkSync(PID_FILE);
|
|
4433
|
+
} catch {
|
|
4434
|
+
}
|
|
4435
|
+
return true;
|
|
4436
|
+
} catch {
|
|
4437
|
+
try {
|
|
4438
|
+
unlinkSync(PID_FILE);
|
|
4439
|
+
} catch {
|
|
4440
|
+
}
|
|
4441
|
+
return false;
|
|
4062
4442
|
}
|
|
4063
4443
|
}
|
|
4064
|
-
process.on("exit", cleanupChild);
|
|
4065
|
-
process.on("SIGINT", () => {
|
|
4066
|
-
cleanupChild();
|
|
4067
|
-
process.exit(0);
|
|
4068
|
-
});
|
|
4069
|
-
process.on("SIGTERM", () => {
|
|
4070
|
-
cleanupChild();
|
|
4071
|
-
process.exit(0);
|
|
4072
|
-
});
|
|
4073
4444
|
async function cmdSetup() {
|
|
4074
4445
|
log2("");
|
|
4075
4446
|
log2(` ${c2.bgCyan(" AgenticMail Setup ")}`);
|
|
@@ -4078,7 +4449,7 @@ async function cmdSetup() {
|
|
|
4078
4449
|
log2(` needs to send and receive real email.`);
|
|
4079
4450
|
log2("");
|
|
4080
4451
|
const hasOpenClaw = existsSync2(join(homedir(), ".openclaw", "openclaw.json"));
|
|
4081
|
-
const totalSteps = hasOpenClaw ?
|
|
4452
|
+
const totalSteps = hasOpenClaw ? 6 : 5;
|
|
4082
4453
|
log2(` Here's what we'll do:`);
|
|
4083
4454
|
log2(` ${c2.dim("1.")} Check your system for required tools`);
|
|
4084
4455
|
log2(` ${c2.dim("2.")} Create your private account and keys`);
|
|
@@ -4258,51 +4629,14 @@ async function cmdSetup() {
|
|
|
4258
4629
|
serverSpinner.start();
|
|
4259
4630
|
let serverReady = false;
|
|
4260
4631
|
try {
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
}
|
|
4267
|
-
if (serverReady) {
|
|
4268
|
-
serverSpinner.succeed(`Server already running at ${c2.cyan(`http://${result.config.api.host}:${result.config.api.port}`)}`);
|
|
4269
|
-
} else {
|
|
4270
|
-
try {
|
|
4271
|
-
const { fork } = await import("child_process");
|
|
4272
|
-
const apiEntry = resolveApiEntry();
|
|
4273
|
-
const env = configToEnv(result.config);
|
|
4274
|
-
apiChild = fork(apiEntry, [], { stdio: ["ignore", "ignore", "pipe", "ipc"], env });
|
|
4275
|
-
const stderrLines = [];
|
|
4276
|
-
apiChild.stderr?.on("data", (chunk) => {
|
|
4277
|
-
const lines = chunk.toString().trim().split("\n");
|
|
4278
|
-
for (const line of lines) {
|
|
4279
|
-
stderrLines.push(line);
|
|
4280
|
-
if (stderrLines.length > 50) stderrLines.shift();
|
|
4281
|
-
}
|
|
4282
|
-
});
|
|
4283
|
-
apiChild.on("exit", (code, signal) => {
|
|
4284
|
-
apiChild = null;
|
|
4285
|
-
log2("");
|
|
4286
|
-
fail2(`Server stopped unexpectedly${signal ? ` (signal: ${signal})` : code ? ` (exit code: ${code})` : ""}`);
|
|
4287
|
-
if (stderrLines.length > 0) {
|
|
4288
|
-
log2("");
|
|
4289
|
-
log2(` ${c2.dim("Last server output:")}`);
|
|
4290
|
-
for (const line of stderrLines.slice(-10)) {
|
|
4291
|
-
log2(` ${c2.dim(line)}`);
|
|
4292
|
-
}
|
|
4293
|
-
}
|
|
4294
|
-
log2("");
|
|
4295
|
-
process.exit(code ?? 1);
|
|
4296
|
-
});
|
|
4297
|
-
serverReady = await waitForApi(result.config.api.host, result.config.api.port);
|
|
4298
|
-
if (serverReady) {
|
|
4299
|
-
serverSpinner.succeed(`Server running at ${c2.cyan(`http://${result.config.api.host}:${result.config.api.port}`)}`);
|
|
4300
|
-
} else {
|
|
4301
|
-
serverSpinner.fail("Server did not start in time");
|
|
4302
|
-
}
|
|
4303
|
-
} catch (err) {
|
|
4304
|
-
serverSpinner.fail(`Could not start server: ${err.message}`);
|
|
4632
|
+
serverReady = await startApiServer(result.config);
|
|
4633
|
+
if (serverReady) {
|
|
4634
|
+
serverSpinner.succeed(`Server running at ${c2.cyan(`http://${result.config.api.host}:${result.config.api.port}`)}`);
|
|
4635
|
+
} else {
|
|
4636
|
+
serverSpinner.fail("Server did not start in time");
|
|
4305
4637
|
}
|
|
4638
|
+
} catch (err) {
|
|
4639
|
+
serverSpinner.fail(`Could not start server: ${err.message}`);
|
|
4306
4640
|
}
|
|
4307
4641
|
let existingEmail = null;
|
|
4308
4642
|
let existingProvider = null;
|
|
@@ -4363,7 +4697,6 @@ async function cmdSetup() {
|
|
|
4363
4697
|
if (choice === "1" || choice === "2") {
|
|
4364
4698
|
if (!serverReady) {
|
|
4365
4699
|
info2("You can configure email later by running: agenticmail setup");
|
|
4366
|
-
cleanupChild();
|
|
4367
4700
|
printSummary(result, true);
|
|
4368
4701
|
return;
|
|
4369
4702
|
}
|
|
@@ -4378,22 +4711,130 @@ async function cmdSetup() {
|
|
|
4378
4711
|
if (!emailOk) {
|
|
4379
4712
|
log2("");
|
|
4380
4713
|
info2("Email setup did not complete. Run " + c2.green("npx agenticmail setup") + " again to retry.");
|
|
4381
|
-
cleanupChild();
|
|
4382
4714
|
printSummary(result, true);
|
|
4383
4715
|
return;
|
|
4384
4716
|
}
|
|
4385
4717
|
} else if (!existingEmail) {
|
|
4386
4718
|
info2("No problem! You can set up email anytime by running this again.");
|
|
4387
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
|
+
}
|
|
4388
4828
|
if (hasOpenClaw && serverReady) {
|
|
4389
4829
|
log2("");
|
|
4390
|
-
log2(` ${c2.bold(`Step
|
|
4830
|
+
log2(` ${c2.bold(`Step 6 of ${totalSteps}`)} ${c2.dim("\u2014")} ${c2.bold("Configure OpenClaw integration")}`);
|
|
4391
4831
|
log2("");
|
|
4392
4832
|
await registerWithOpenClaw(result.config);
|
|
4393
4833
|
}
|
|
4394
4834
|
printSummary(result, false);
|
|
4395
4835
|
if (serverReady) {
|
|
4396
|
-
await interactiveShell({ config: result.config, onExit:
|
|
4836
|
+
await interactiveShell({ config: result.config, onExit: () => {
|
|
4837
|
+
} });
|
|
4397
4838
|
}
|
|
4398
4839
|
}
|
|
4399
4840
|
function printSummary(result, exitAfter) {
|
|
@@ -4975,11 +5416,12 @@ async function cmdOpenClaw() {
|
|
|
4975
5416
|
log2(` This will:`);
|
|
4976
5417
|
log2(` ${c2.dim("1.")} Set up the mail server infrastructure`);
|
|
4977
5418
|
log2(` ${c2.dim("2.")} Create an agent email account`);
|
|
4978
|
-
log2(` ${c2.dim("3.")}
|
|
4979
|
-
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`);
|
|
4980
5422
|
log2("");
|
|
4981
5423
|
const setup = new SetupManager();
|
|
4982
|
-
log2(` ${c2.bold("Step 1 of
|
|
5424
|
+
log2(` ${c2.bold("Step 1 of 6")} ${c2.dim("\u2014")} ${c2.bold("Checking infrastructure")}`);
|
|
4983
5425
|
log2("");
|
|
4984
5426
|
let config;
|
|
4985
5427
|
let configPath;
|
|
@@ -5038,7 +5480,7 @@ async function cmdOpenClaw() {
|
|
|
5038
5480
|
}
|
|
5039
5481
|
}
|
|
5040
5482
|
log2("");
|
|
5041
|
-
log2(` ${c2.bold("Step 2 of
|
|
5483
|
+
log2(` ${c2.bold("Step 2 of 6")} ${c2.dim("\u2014")} ${c2.bold("Starting server")}`);
|
|
5042
5484
|
log2("");
|
|
5043
5485
|
const apiHost = config.api.host;
|
|
5044
5486
|
const apiPort = config.api.port;
|
|
@@ -5057,17 +5499,9 @@ async function cmdOpenClaw() {
|
|
|
5057
5499
|
const serverSpinner = new Spinner("server", "Starting the server...");
|
|
5058
5500
|
serverSpinner.start();
|
|
5059
5501
|
try {
|
|
5060
|
-
const
|
|
5061
|
-
const apiEntry = resolveApiEntry();
|
|
5062
|
-
const env = configToEnv(config);
|
|
5063
|
-
apiChild = fork(apiEntry, [], { stdio: ["ignore", "ignore", "pipe", "ipc"], env });
|
|
5064
|
-
apiChild.on("exit", () => {
|
|
5065
|
-
apiChild = null;
|
|
5066
|
-
});
|
|
5067
|
-
const ready = await waitForApi(apiHost, apiPort);
|
|
5502
|
+
const ready = await startApiServer(config);
|
|
5068
5503
|
if (!ready) {
|
|
5069
5504
|
serverSpinner.fail("Server did not start in time");
|
|
5070
|
-
cleanupChild();
|
|
5071
5505
|
process.exit(1);
|
|
5072
5506
|
}
|
|
5073
5507
|
serverSpinner.succeed(`Server running at ${c2.cyan(apiBase)}`);
|
|
@@ -5077,7 +5511,7 @@ async function cmdOpenClaw() {
|
|
|
5077
5511
|
}
|
|
5078
5512
|
}
|
|
5079
5513
|
log2("");
|
|
5080
|
-
log2(` ${c2.bold("Step 3 of
|
|
5514
|
+
log2(` ${c2.bold("Step 3 of 6")} ${c2.dim("\u2014")} ${c2.bold("Agent account")}`);
|
|
5081
5515
|
log2("");
|
|
5082
5516
|
let agentApiKey;
|
|
5083
5517
|
let agentEmail = "";
|
|
@@ -5249,7 +5683,212 @@ async function cmdOpenClaw() {
|
|
|
5249
5683
|
}
|
|
5250
5684
|
}
|
|
5251
5685
|
log2("");
|
|
5252
|
-
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")}`);
|
|
5253
5892
|
log2("");
|
|
5254
5893
|
const pluginDir = resolveOpenClawPluginDir();
|
|
5255
5894
|
if (pluginDir) {
|
|
@@ -5323,7 +5962,7 @@ async function cmdOpenClaw() {
|
|
|
5323
5962
|
}
|
|
5324
5963
|
}
|
|
5325
5964
|
log2("");
|
|
5326
|
-
log2(` ${c2.bold("Step
|
|
5965
|
+
log2(` ${c2.bold("Step 6 of 6")} ${c2.dim("\u2014")} ${c2.bold("Restarting OpenClaw gateway")}`);
|
|
5327
5966
|
log2("");
|
|
5328
5967
|
let gatewayRestarted = false;
|
|
5329
5968
|
let hasOpenClawCli = false;
|
|
@@ -5371,27 +6010,69 @@ async function cmdOpenClaw() {
|
|
|
5371
6010
|
}
|
|
5372
6011
|
log2(` ${c2.dim("Master Key:")} ${c2.yellow(config.masterKey)}`);
|
|
5373
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
|
+
}
|
|
5374
6046
|
log2("");
|
|
5375
6047
|
if (gatewayRestarted) {
|
|
5376
|
-
log2(` Your agent now has ${c2.bold("
|
|
6048
|
+
log2(` Your agent now has ${c2.bold("63 email + SMS tools")} available!`);
|
|
5377
6049
|
log2(` Try: ${c2.dim('"Send an email to test@example.com"')}`);
|
|
5378
6050
|
log2("");
|
|
5379
6051
|
log2(` ${c2.bold("\u{1F380} AgenticMail Coordination")} ${c2.dim("(auto-configured)")}`);
|
|
5380
6052
|
log2(` Your agent can now use ${c2.cyan("agenticmail_call_agent")} to call other agents`);
|
|
5381
6053
|
log2(` with structured task queues, push notifications, and auto-spawned sessions.`);
|
|
5382
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
|
+
}
|
|
5383
6067
|
} else {
|
|
5384
6068
|
log2(` ${c2.bold("Next step:")}`);
|
|
5385
6069
|
log2(` Restart your OpenClaw gateway, then your agent will`);
|
|
5386
|
-
log2(` have ${c2.bold("
|
|
6070
|
+
log2(` have ${c2.bold("63 email + SMS tools")} available!`);
|
|
5387
6071
|
}
|
|
5388
6072
|
log2("");
|
|
5389
6073
|
if (process.stdin.isTTY) {
|
|
5390
|
-
await interactiveShell({ config, onExit:
|
|
5391
|
-
|
|
5392
|
-
if (!serverWasRunning) {
|
|
5393
|
-
cleanupChild();
|
|
5394
|
-
}
|
|
6074
|
+
await interactiveShell({ config, onExit: () => {
|
|
6075
|
+
} });
|
|
5395
6076
|
}
|
|
5396
6077
|
}
|
|
5397
6078
|
function printPluginSnippet(apiUrl, masterKey, agentApiKey) {
|
|
@@ -5532,61 +6213,118 @@ async function cmdStart() {
|
|
|
5532
6213
|
}
|
|
5533
6214
|
const serverSpinner = new Spinner("server", "Launching your server...");
|
|
5534
6215
|
serverSpinner.start();
|
|
5535
|
-
let alreadyRunning = false;
|
|
5536
6216
|
try {
|
|
5537
|
-
const
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
6217
|
+
const ready = await startApiServer(config);
|
|
6218
|
+
if (ready) {
|
|
6219
|
+
serverSpinner.succeed(`Server running at ${c2.cyan(`http://${config.api.host}:${config.api.port}`)}`);
|
|
6220
|
+
} else {
|
|
6221
|
+
serverSpinner.fail("Server did not start in time");
|
|
6222
|
+
process.exit(1);
|
|
6223
|
+
}
|
|
6224
|
+
} catch (err) {
|
|
6225
|
+
serverSpinner.fail(`Couldn't start the server: ${err.message}`);
|
|
6226
|
+
process.exit(1);
|
|
5542
6227
|
}
|
|
5543
|
-
|
|
5544
|
-
|
|
6228
|
+
await interactiveShell({ config, onExit: () => {
|
|
6229
|
+
} });
|
|
6230
|
+
}
|
|
6231
|
+
async function cmdStop() {
|
|
6232
|
+
log2("");
|
|
6233
|
+
const stopped = stopApiServer();
|
|
6234
|
+
if (stopped) {
|
|
6235
|
+
ok2("AgenticMail server stopped");
|
|
5545
6236
|
} else {
|
|
6237
|
+
info2("Server is not running");
|
|
6238
|
+
}
|
|
6239
|
+
log2("");
|
|
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 {
|
|
5546
6286
|
try {
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
cleanupChild();
|
|
5581
|
-
process.exit(1);
|
|
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")}`);
|
|
5582
6320
|
}
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
serverSpinner.fail(`Couldn't start the server: ${err.message}`);
|
|
5586
|
-
process.exit(1);
|
|
6321
|
+
} catch {
|
|
6322
|
+
info2(`Update plugin manually: ${c2.green(pluginCmd)}`);
|
|
5587
6323
|
}
|
|
5588
6324
|
}
|
|
5589
|
-
|
|
6325
|
+
log2("");
|
|
6326
|
+
ok2("Update complete!");
|
|
6327
|
+
log2("");
|
|
5590
6328
|
}
|
|
5591
6329
|
var command = process.argv[2];
|
|
5592
6330
|
switch (command) {
|
|
@@ -5602,6 +6340,14 @@ switch (command) {
|
|
|
5602
6340
|
process.exit(1);
|
|
5603
6341
|
});
|
|
5604
6342
|
break;
|
|
6343
|
+
case "stop":
|
|
6344
|
+
cmdStop().then(() => {
|
|
6345
|
+
process.exit(0);
|
|
6346
|
+
}).catch((err) => {
|
|
6347
|
+
console.error(err);
|
|
6348
|
+
process.exit(1);
|
|
6349
|
+
});
|
|
6350
|
+
break;
|
|
5605
6351
|
case "status":
|
|
5606
6352
|
cmdStatus().then(() => {
|
|
5607
6353
|
process.exit(0);
|
|
@@ -5616,6 +6362,12 @@ switch (command) {
|
|
|
5616
6362
|
process.exit(1);
|
|
5617
6363
|
});
|
|
5618
6364
|
break;
|
|
6365
|
+
case "update":
|
|
6366
|
+
cmdUpdate().catch((err) => {
|
|
6367
|
+
console.error(err);
|
|
6368
|
+
process.exit(1);
|
|
6369
|
+
});
|
|
6370
|
+
break;
|
|
5619
6371
|
case "help":
|
|
5620
6372
|
case "--help":
|
|
5621
6373
|
case "-h":
|
|
@@ -5626,8 +6378,10 @@ switch (command) {
|
|
|
5626
6378
|
log2(` ${c2.green("agenticmail")} Get started (setup + start)`);
|
|
5627
6379
|
log2(` ${c2.green("agenticmail setup")} Re-run the setup wizard`);
|
|
5628
6380
|
log2(` ${c2.green("agenticmail start")} Start the server`);
|
|
6381
|
+
log2(` ${c2.green("agenticmail stop")} Stop the server`);
|
|
5629
6382
|
log2(` ${c2.green("agenticmail status")} See what's running`);
|
|
5630
6383
|
log2(` ${c2.green("agenticmail openclaw")} Set up AgenticMail for OpenClaw`);
|
|
6384
|
+
log2(` ${c2.green("agenticmail update")} Update to the latest version`);
|
|
5631
6385
|
log2("");
|
|
5632
6386
|
process.exit(0);
|
|
5633
6387
|
default:
|