mercury-agent 0.4.7 ā 0.4.8
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/docs/auth/dashboard.md +28 -28
- package/examples/extensions/voice-synth/index.ts +94 -94
- package/package.json +1 -1
- package/src/adapters/whatsapp.ts +635 -632
- package/src/agent/model-capabilities.ts +231 -231
- package/src/bridges/discord.ts +178 -178
- package/src/bridges/slack.ts +179 -179
- package/src/bridges/teams.ts +162 -162
- package/src/bridges/telegram.ts +579 -579
- package/src/cli/mercury.ts +2536 -2536
- package/src/cli/whatsapp-auth.ts +263 -260
- package/src/config.ts +316 -316
- package/src/core/permissions.ts +196 -196
- package/src/core/router.ts +191 -191
- package/src/core/routes/chat.ts +175 -175
- package/src/core/routes/dashboard.ts +2491 -2491
- package/src/core/routes/messages.ts +37 -37
- package/src/core/routes/mutes.ts +95 -95
- package/src/core/routes/roles.ts +135 -135
- package/src/core/runtime.ts +1140 -1140
- package/src/core/task-scheduler.ts +139 -139
- package/src/extensions/catalog.ts +117 -117
- package/src/extensions/hooks.ts +161 -161
- package/src/extensions/installer.ts +306 -306
- package/src/extensions/loader.ts +271 -271
- package/src/extensions/permission-guard.ts +52 -52
- package/src/server.ts +391 -391
- package/src/storage/db.ts +1625 -1625
- package/src/storage/pi-auth.ts +95 -95
- package/src/tts/azure.ts +52 -52
- package/src/tts/synthesize.ts +133 -133
package/src/cli/whatsapp-auth.ts
CHANGED
|
@@ -1,260 +1,263 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* WhatsApp Authentication Script
|
|
5
|
-
*
|
|
6
|
-
* Run this during setup to authenticate with WhatsApp.
|
|
7
|
-
* Displays QR code, waits for scan, saves credentials, then exits.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* mercury auth whatsapp # QR code mode
|
|
11
|
-
* mercury auth whatsapp --pairing-code --phone 14155551234 # Pairing code mode
|
|
12
|
-
*/
|
|
13
|
-
import fs from "node:fs";
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
import readline from "node:readline";
|
|
16
|
-
import makeWASocket, {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
console.log(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
console.log(
|
|
121
|
-
console.log(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
console.log("
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
(_, i, arr) => arr[i - 1] === "--
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WhatsApp Authentication Script
|
|
5
|
+
*
|
|
6
|
+
* Run this during setup to authenticate with WhatsApp.
|
|
7
|
+
* Displays QR code, waits for scan, saves credentials, then exits.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* mercury auth whatsapp # QR code mode
|
|
11
|
+
* mercury auth whatsapp --pairing-code --phone 14155551234 # Pairing code mode
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import readline from "node:readline";
|
|
16
|
+
import makeWASocket, {
|
|
17
|
+
DisconnectReason,
|
|
18
|
+
fetchLatestWaWebVersion,
|
|
19
|
+
makeCacheableSignalKeyStore,
|
|
20
|
+
useMultiFileAuthState,
|
|
21
|
+
} from "@whiskeysockets/baileys";
|
|
22
|
+
import qrcode from "qrcode-terminal";
|
|
23
|
+
import { loadConfig, resolveProjectPath } from "../config.js";
|
|
24
|
+
|
|
25
|
+
export interface WhatsAppAuthOptions {
|
|
26
|
+
authDir: string;
|
|
27
|
+
statusDir: string;
|
|
28
|
+
usePairingCode?: boolean;
|
|
29
|
+
phoneNumber?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const STATUS_FILE_NAME = "whatsapp-status.txt";
|
|
33
|
+
const QR_FILE_NAME = "whatsapp-qr.txt";
|
|
34
|
+
|
|
35
|
+
// Silent logger for Baileys
|
|
36
|
+
const silentLogger = {
|
|
37
|
+
level: "silent",
|
|
38
|
+
child: () => silentLogger,
|
|
39
|
+
trace: () => undefined,
|
|
40
|
+
debug: () => undefined,
|
|
41
|
+
info: () => undefined,
|
|
42
|
+
warn: () => undefined,
|
|
43
|
+
error: () => undefined,
|
|
44
|
+
fatal: () => undefined,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function askQuestion(prompt: string): Promise<string> {
|
|
48
|
+
const rl = readline.createInterface({
|
|
49
|
+
input: process.stdin,
|
|
50
|
+
output: process.stdout,
|
|
51
|
+
});
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
rl.question(prompt, (answer) => {
|
|
54
|
+
rl.close();
|
|
55
|
+
resolve(answer.trim());
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writeStatus(statusDir: string, status: string): void {
|
|
61
|
+
const statusPath = path.join(statusDir, STATUS_FILE_NAME);
|
|
62
|
+
fs.writeFileSync(statusPath, status);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeQrData(statusDir: string, qr: string): void {
|
|
66
|
+
const qrPath = path.join(statusDir, QR_FILE_NAME);
|
|
67
|
+
fs.writeFileSync(qrPath, qr);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function clearQrData(statusDir: string): void {
|
|
71
|
+
const qrPath = path.join(statusDir, QR_FILE_NAME);
|
|
72
|
+
try {
|
|
73
|
+
fs.unlinkSync(qrPath);
|
|
74
|
+
} catch {
|
|
75
|
+
// Ignore if file doesn't exist
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function connectSocket(
|
|
80
|
+
options: WhatsAppAuthOptions,
|
|
81
|
+
isReconnect = false,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
const { authDir, statusDir, usePairingCode, phoneNumber } = options;
|
|
84
|
+
|
|
85
|
+
const { state, saveCreds } = await useMultiFileAuthState(authDir);
|
|
86
|
+
|
|
87
|
+
if (state.creds.registered && !isReconnect) {
|
|
88
|
+
writeStatus(statusDir, "already_authenticated");
|
|
89
|
+
console.log("ā Already authenticated with WhatsApp");
|
|
90
|
+
console.log(` To re-authenticate, delete ${authDir} and run again.`);
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const { version } = await fetchLatestWaWebVersion({}).catch(() => {
|
|
95
|
+
console.warn("Failed to fetch latest WA Web version, using default");
|
|
96
|
+
return { version: undefined };
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const sock = makeWASocket({
|
|
100
|
+
version,
|
|
101
|
+
auth: {
|
|
102
|
+
creds: state.creds,
|
|
103
|
+
keys: makeCacheableSignalKeyStore(state.keys, silentLogger),
|
|
104
|
+
},
|
|
105
|
+
printQRInTerminal: false,
|
|
106
|
+
logger: silentLogger,
|
|
107
|
+
browser: [
|
|
108
|
+
process.env.MERCURY_WHATSAPP_DEVICE_NAME || "Mercury",
|
|
109
|
+
"Chrome",
|
|
110
|
+
"22.0",
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (usePairingCode && phoneNumber && !state.creds.me) {
|
|
115
|
+
// Request pairing code after a short delay for connection to initialize
|
|
116
|
+
// Only on first connect (not reconnect after 515)
|
|
117
|
+
setTimeout(async () => {
|
|
118
|
+
try {
|
|
119
|
+
const code = await sock.requestPairingCode(phoneNumber);
|
|
120
|
+
console.log(`\nš Your pairing code: ${code}\n`);
|
|
121
|
+
console.log(" 1. Open WhatsApp on your phone");
|
|
122
|
+
console.log(" 2. Tap Settings ā Linked Devices ā Link a Device");
|
|
123
|
+
console.log(' 3. Tap "Link with phone number instead"');
|
|
124
|
+
console.log(` 4. Enter this code: ${code}\n`);
|
|
125
|
+
writeStatus(statusDir, `pairing_code:${code}`);
|
|
126
|
+
} catch (err: unknown) {
|
|
127
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
128
|
+
console.error("Failed to request pairing code:", message);
|
|
129
|
+
writeStatus(statusDir, `failed:pairing_code_error`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}, 3000);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
sock.ev.on("connection.update", (update) => {
|
|
136
|
+
const { connection, lastDisconnect, qr } = update;
|
|
137
|
+
|
|
138
|
+
if (qr) {
|
|
139
|
+
// Write raw QR data to file so external tools can render it
|
|
140
|
+
writeQrData(statusDir, qr);
|
|
141
|
+
writeStatus(statusDir, "waiting_qr");
|
|
142
|
+
console.log("Scan this QR code with WhatsApp:\n");
|
|
143
|
+
console.log(" 1. Open WhatsApp on your phone");
|
|
144
|
+
console.log(" 2. Tap Settings ā Linked Devices ā Link a Device");
|
|
145
|
+
console.log(" 3. Point your camera at the QR code below\n");
|
|
146
|
+
qrcode.generate(qr, { small: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (connection === "close") {
|
|
150
|
+
const reason = (
|
|
151
|
+
lastDisconnect?.error as { output?: { statusCode?: number } }
|
|
152
|
+
)?.output?.statusCode;
|
|
153
|
+
|
|
154
|
+
if (reason === DisconnectReason.loggedOut) {
|
|
155
|
+
writeStatus(statusDir, "failed:logged_out");
|
|
156
|
+
clearQrData(statusDir);
|
|
157
|
+
console.log("\nā Logged out. Delete auth folder and try again.");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
} else if (reason === DisconnectReason.timedOut) {
|
|
160
|
+
writeStatus(statusDir, "failed:qr_timeout");
|
|
161
|
+
clearQrData(statusDir);
|
|
162
|
+
console.log("\nā QR code timed out. Please try again.");
|
|
163
|
+
process.exit(1);
|
|
164
|
+
} else if (reason === 515) {
|
|
165
|
+
// 515 = stream error, often happens after pairing succeeds but before
|
|
166
|
+
// registration completes. Reconnect to finish the handshake.
|
|
167
|
+
console.log("\nā³ Stream error (515) after pairing ā reconnecting...");
|
|
168
|
+
connectSocket(options, true);
|
|
169
|
+
} else {
|
|
170
|
+
writeStatus(statusDir, `failed:${reason || "unknown"}`);
|
|
171
|
+
clearQrData(statusDir);
|
|
172
|
+
console.log("\nā Connection failed. Please try again.");
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (connection === "open") {
|
|
178
|
+
writeStatus(statusDir, "authenticated");
|
|
179
|
+
clearQrData(statusDir);
|
|
180
|
+
console.log("\nā Successfully authenticated with WhatsApp!");
|
|
181
|
+
const myJid = sock.user?.id;
|
|
182
|
+
if (myJid) {
|
|
183
|
+
console.log(`\n Your WhatsApp ID: whatsapp:${myJid}`);
|
|
184
|
+
console.log(
|
|
185
|
+
" Add to MERCURY_ADMINS in .env to make yourself admin.\n",
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
console.log(` Credentials saved to ${authDir}/`);
|
|
189
|
+
console.log(
|
|
190
|
+
" You can now start mercury with 'mercury service install'.\n",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Give it a moment to save credentials, then exit
|
|
194
|
+
setTimeout(() => process.exit(0), 1000);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
sock.ev.on("creds.update", saveCreds);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function authenticate(
|
|
202
|
+
options: WhatsAppAuthOptions,
|
|
203
|
+
): Promise<void> {
|
|
204
|
+
const { authDir, statusDir, usePairingCode } = options;
|
|
205
|
+
let { phoneNumber } = options;
|
|
206
|
+
|
|
207
|
+
// Ensure directories exist
|
|
208
|
+
fs.mkdirSync(authDir, { recursive: true });
|
|
209
|
+
fs.mkdirSync(statusDir, { recursive: true });
|
|
210
|
+
|
|
211
|
+
// Clean up any stale QR/status files from previous runs
|
|
212
|
+
clearQrData(statusDir);
|
|
213
|
+
try {
|
|
214
|
+
fs.unlinkSync(path.join(statusDir, STATUS_FILE_NAME));
|
|
215
|
+
} catch {
|
|
216
|
+
// Ignore
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (usePairingCode && !phoneNumber) {
|
|
220
|
+
phoneNumber = await askQuestion(
|
|
221
|
+
"Enter your phone number (with country code, no + or spaces, e.g. 14155551234): ",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log("Starting WhatsApp authentication...\n");
|
|
226
|
+
|
|
227
|
+
await connectSocket({ ...options, phoneNumber });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// CLI entry point when run directly
|
|
231
|
+
if (import.meta.main) {
|
|
232
|
+
const args = process.argv.slice(2);
|
|
233
|
+
const usePairingCode = args.includes("--pairing-code");
|
|
234
|
+
const phoneIndex = args.findIndex((_, i, arr) => arr[i - 1] === "--phone");
|
|
235
|
+
const phoneNumber = phoneIndex >= 0 ? args[phoneIndex] : undefined;
|
|
236
|
+
const authDirIndex = args.findIndex(
|
|
237
|
+
(_, i, arr) => arr[i - 1] === "--auth-dir",
|
|
238
|
+
);
|
|
239
|
+
const statusDirIndex = args.findIndex(
|
|
240
|
+
(_, i, arr) => arr[i - 1] === "--status-dir",
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const cfg = loadConfig();
|
|
244
|
+
const authDir =
|
|
245
|
+
authDirIndex >= 0
|
|
246
|
+
? args[authDirIndex]
|
|
247
|
+
: resolveProjectPath(cfg.whatsappAuthDir);
|
|
248
|
+
const statusDir =
|
|
249
|
+
statusDirIndex >= 0
|
|
250
|
+
? args[statusDirIndex]
|
|
251
|
+
: resolveProjectPath(cfg.dataDir);
|
|
252
|
+
|
|
253
|
+
authenticate({
|
|
254
|
+
authDir,
|
|
255
|
+
statusDir,
|
|
256
|
+
usePairingCode,
|
|
257
|
+
phoneNumber,
|
|
258
|
+
}).catch((err: unknown) => {
|
|
259
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
260
|
+
console.error("Authentication failed:", message);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
});
|
|
263
|
+
}
|