@rubytech/taskmaster 1.11.1 → 1.12.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/dist/agents/skills/workspace.js +3 -0
- package/dist/agents/taskmaster-tools.js +14 -0
- package/dist/agents/tool-policy.js +18 -0
- package/dist/agents/tools/brand-settings-tool.js +106 -0
- package/dist/agents/tools/channel-settings-tool.js +130 -0
- package/dist/agents/tools/logs-read-tool.js +67 -0
- package/dist/agents/tools/public-chat-settings-tool.js +138 -0
- package/dist/agents/tools/skill-manage-tool.js +105 -0
- package/dist/agents/tools/system-status-tool.js +82 -0
- package/dist/agents/tools/usage-report-tool.js +46 -0
- package/dist/build-info.json +3 -3
- package/dist/config/port-defaults.js +0 -8
- package/dist/config/zod-schema.js +6 -1
- package/dist/control-ui/assets/{index-YAjVyXqJ.js → index-DpMaqt-b.js} +657 -677
- package/dist/control-ui/assets/index-DpMaqt-b.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/control-ui.js +2 -2
- package/dist/gateway/public-chat/deliver-otp.js +54 -18
- package/dist/gateway/public-chat/deliver-sms.js +96 -24
- package/dist/gateway/public-chat-api.js +12 -7
- package/dist/gateway/server-methods/public-chat.js +11 -6
- package/package.json +1 -1
- package/skills/brevo/SKILL.md +9 -8
- package/skills/brevo/references/browser-setup.md +13 -3
- package/skills/brevo/references/sms-credits.md +76 -0
- package/skills/system-admin/SKILL.md +68 -0
- package/skills/twilio/SKILL.md +14 -11
- package/skills/twilio/references/browser-setup.md +15 -24
- package/taskmaster-docs/USER-GUIDE.md +51 -8
- package/dist/control-ui/assets/index-YAjVyXqJ.js.map +0 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Taskmaster Control</title>
|
|
7
7
|
<meta name="color-scheme" content="dark light" />
|
|
8
8
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
9
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-DpMaqt-b.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="./assets/index-CpaEIgQy.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
@@ -5,7 +5,7 @@ import { resolveAgentWorkspaceRoot } from "../agents/agent-scope.js";
|
|
|
5
5
|
import { buildAgentSummaries } from "../commands/agents.config.js";
|
|
6
6
|
import { DEFAULT_ASSISTANT_IDENTITY, resolveAssistantIdentity } from "./assistant-identity.js";
|
|
7
7
|
import { resolveAgentBoundAccountId } from "../routing/bindings.js";
|
|
8
|
-
import { detectOtpChannels } from "./public-chat/deliver-otp.js";
|
|
8
|
+
import { detectOtpChannels, normalizeVerifyMethods } from "./public-chat/deliver-otp.js";
|
|
9
9
|
import { resolvePublicAgentId } from "./public-chat/session.js";
|
|
10
10
|
import { findBrandLogo } from "./server-methods/brand.js";
|
|
11
11
|
import { buildControlUiAvatarUrl, CONTROL_UI_AVATAR_PREFIX, normalizeControlUiBasePath, resolveAssistantAvatarUrl, } from "./control-ui-shared.js";
|
|
@@ -570,7 +570,7 @@ export function handlePublicChatHttpRequest(req, res, opts) {
|
|
|
570
570
|
// Inject public-chat globals before </head>
|
|
571
571
|
const publicScript = `<script>` +
|
|
572
572
|
`window.__TASKMASTER_PUBLIC_CHAT__=true;` +
|
|
573
|
-
`window.__TASKMASTER_PUBLIC_CHAT_CONFIG__=${JSON.stringify({ accountId, auth: authMode, cookieTtlDays, otpChannels, verifyMethods: config.publicChat?.verifyMethods ?? ["
|
|
573
|
+
`window.__TASKMASTER_PUBLIC_CHAT_CONFIG__=${JSON.stringify({ accountId, auth: authMode, cookieTtlDays, otpChannels, verifyMethods: normalizeVerifyMethods(config.publicChat?.verifyMethods ?? ["whatsapp", "sms"]), greeting: config.publicChat?.greetings?.[accountId] || undefined })};` +
|
|
574
574
|
`</script>`;
|
|
575
575
|
const headClose = baseInjected.indexOf("</head>");
|
|
576
576
|
const withPublic = headClose !== -1
|
|
@@ -1,14 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deliver OTP verification codes via WhatsApp
|
|
2
|
+
* Deliver OTP verification codes via WhatsApp, SMS (Brevo), or email (Brevo).
|
|
3
|
+
* Both SMS and email use the same Brevo API key — SMS requires prepaid credits.
|
|
4
|
+
*
|
|
5
|
+
* The admin controls which channels are enabled via `verifyMethods` config:
|
|
6
|
+
* "whatsapp", "sms", "email". The delivery logic only tries enabled channels.
|
|
3
7
|
*/
|
|
4
8
|
import { getActiveWebListener } from "../../web/active-listener.js";
|
|
5
9
|
import { sendMessageWhatsApp } from "../../web/outbound.js";
|
|
6
|
-
import { resolveSmsCredentials, sendSms } from "./deliver-sms.js";
|
|
10
|
+
import { hasSmsApiKey, resolveSmsCredentials, sendSms } from "./deliver-sms.js";
|
|
7
11
|
import { hasEmailApiKey, resolveEmailCredentials, sendEmail } from "./deliver-email.js";
|
|
8
12
|
/** Detect whether an identifier is an email address. */
|
|
9
13
|
function isEmail(identifier) {
|
|
10
14
|
return identifier.includes("@");
|
|
11
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Normalize verify methods — expand deprecated "phone" into ["whatsapp", "sms"].
|
|
18
|
+
* Returns a deduplicated array of canonical method names.
|
|
19
|
+
*/
|
|
20
|
+
export function normalizeVerifyMethods(methods) {
|
|
21
|
+
const result = new Set();
|
|
22
|
+
for (const m of methods) {
|
|
23
|
+
if (m === "phone") {
|
|
24
|
+
result.add("whatsapp");
|
|
25
|
+
result.add("sms");
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
result.add(m);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return [...result];
|
|
32
|
+
}
|
|
33
|
+
/** Check whether any phone-based verify method is enabled. */
|
|
34
|
+
export function hasPhoneMethod(methods) {
|
|
35
|
+
return methods.includes("whatsapp") || methods.includes("sms");
|
|
36
|
+
}
|
|
12
37
|
/**
|
|
13
38
|
* Detect which OTP delivery channels are currently available.
|
|
14
39
|
* Does not attempt delivery — used by the capabilities endpoint.
|
|
@@ -17,7 +42,7 @@ export function detectOtpChannels(whatsappAccountId) {
|
|
|
17
42
|
const channels = [];
|
|
18
43
|
if (getActiveWebListener(whatsappAccountId))
|
|
19
44
|
channels.push("whatsapp");
|
|
20
|
-
if (
|
|
45
|
+
if (hasSmsApiKey())
|
|
21
46
|
channels.push("sms");
|
|
22
47
|
if (hasEmailApiKey())
|
|
23
48
|
channels.push("email");
|
|
@@ -26,12 +51,14 @@ export function detectOtpChannels(whatsappAccountId) {
|
|
|
26
51
|
/**
|
|
27
52
|
* Deliver a verification code to the given identifier.
|
|
28
53
|
*
|
|
29
|
-
* Email identifiers (containing @) are sent via Brevo.
|
|
30
|
-
* Phone identifiers
|
|
54
|
+
* Email identifiers (containing @) are sent via Brevo email API.
|
|
55
|
+
* Phone identifiers try WhatsApp then SMS, but only channels that
|
|
56
|
+
* are enabled in `enabledMethods` are attempted.
|
|
31
57
|
*/
|
|
32
|
-
export async function deliverOtp(identifier, code, accountId) {
|
|
58
|
+
export async function deliverOtp(identifier, code, accountId, enabledMethods) {
|
|
33
59
|
const message = `Your verification code is: ${code}`;
|
|
34
|
-
|
|
60
|
+
const methods = enabledMethods ?? ["whatsapp", "sms", "email"];
|
|
61
|
+
// Email identifiers — deliver via Brevo email API
|
|
35
62
|
if (isEmail(identifier)) {
|
|
36
63
|
const emailCreds = await resolveEmailCredentials();
|
|
37
64
|
if (!emailCreds) {
|
|
@@ -40,24 +67,33 @@ export async function deliverOtp(identifier, code, accountId) {
|
|
|
40
67
|
await sendEmail(identifier, "Your verification code", message, emailCreds);
|
|
41
68
|
return { channel: "email" };
|
|
42
69
|
}
|
|
43
|
-
// Phone identifiers — WhatsApp
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
70
|
+
// Phone identifiers — try enabled channels in order: WhatsApp, then SMS
|
|
71
|
+
const tryWhatsApp = methods.includes("whatsapp") && !!getActiveWebListener(accountId);
|
|
72
|
+
const trySms = methods.includes("sms");
|
|
73
|
+
if (tryWhatsApp) {
|
|
47
74
|
try {
|
|
48
75
|
await sendMessageWhatsApp(identifier, message, { verbose: false, accountId });
|
|
49
76
|
return { channel: "whatsapp" };
|
|
50
77
|
}
|
|
51
78
|
catch {
|
|
52
|
-
// WhatsApp failed — fall through to SMS
|
|
79
|
+
// WhatsApp failed — fall through to SMS if enabled
|
|
53
80
|
}
|
|
54
81
|
}
|
|
55
|
-
if (
|
|
56
|
-
await
|
|
57
|
-
|
|
82
|
+
if (trySms) {
|
|
83
|
+
const smsCreds = await resolveSmsCredentials();
|
|
84
|
+
if (smsCreds) {
|
|
85
|
+
await sendSms(identifier, message, smsCreds);
|
|
86
|
+
return { channel: "sms" };
|
|
87
|
+
}
|
|
58
88
|
}
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
// Build a specific error message based on what was tried
|
|
90
|
+
const tried = [];
|
|
91
|
+
if (methods.includes("whatsapp"))
|
|
92
|
+
tried.push("WhatsApp");
|
|
93
|
+
if (methods.includes("sms"))
|
|
94
|
+
tried.push("SMS");
|
|
95
|
+
if (tried.length === 0) {
|
|
96
|
+
throw new Error("No phone verification channel is enabled. Enable WhatsApp or SMS in Public Chat settings.");
|
|
61
97
|
}
|
|
62
|
-
throw new Error(
|
|
98
|
+
throw new Error(`Failed to send verification code via ${tried.join(" and ")}. Check that the channel is connected and configured.`);
|
|
63
99
|
}
|
|
@@ -1,44 +1,116 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lightweight
|
|
2
|
+
* Lightweight Brevo SMS sender — single HTTP POST, no SDK dependency.
|
|
3
|
+
*
|
|
4
|
+
* Uses the same Brevo API key as email delivery. The SMS sender name comes
|
|
5
|
+
* from the same Brevo verified sender that email uses — matched by email
|
|
6
|
+
* address, so both channels present consistent branding.
|
|
7
|
+
* SMS requires prepaid credits in the Brevo account.
|
|
3
8
|
*/
|
|
4
9
|
import { loadConfig } from "../../config/config.js";
|
|
10
|
+
import { resolveEmailCredentials } from "./deliver-email.js";
|
|
11
|
+
/** Cached sender name (already truncated to ≤11 chars). */
|
|
12
|
+
let cachedSenderName = null;
|
|
5
13
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
14
|
+
* Truncate a sender name to fit the GSM alphanumeric sender limit (11 chars).
|
|
15
|
+
* Tries to break at a word boundary to avoid ugly truncations.
|
|
8
16
|
*/
|
|
9
|
-
|
|
17
|
+
function truncateSender(name) {
|
|
18
|
+
if (name.length <= 11)
|
|
19
|
+
return name;
|
|
20
|
+
// Try the first word — often the business name
|
|
21
|
+
const firstWord = name.split(/\s/)[0];
|
|
22
|
+
if (firstWord.length <= 11)
|
|
23
|
+
return firstWord;
|
|
24
|
+
// Last resort: hard truncate
|
|
25
|
+
return name.slice(0, 11);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the SMS sender name from Brevo's verified senders.
|
|
29
|
+
*
|
|
30
|
+
* If an email address is provided (from config), finds the matching sender
|
|
31
|
+
* and uses its display name — this ensures SMS and email use the same identity.
|
|
32
|
+
* Falls back to the first active sender, then "Verify" as a safe default.
|
|
33
|
+
*/
|
|
34
|
+
async function fetchSenderName(apiKey, matchEmail) {
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch("https://api.brevo.com/v3/senders", {
|
|
37
|
+
headers: { "api-key": apiKey, Accept: "application/json" },
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
return "Verify";
|
|
41
|
+
const json = (await res.json());
|
|
42
|
+
const senders = json.senders ?? [];
|
|
43
|
+
// Match the sender whose email matches the one configured for email delivery
|
|
44
|
+
if (matchEmail) {
|
|
45
|
+
const match = senders.find((s) => s.email?.toLowerCase() === matchEmail.toLowerCase() && s.name);
|
|
46
|
+
if (match?.name)
|
|
47
|
+
return truncateSender(match.name);
|
|
48
|
+
}
|
|
49
|
+
// Fallback: first active sender with a name
|
|
50
|
+
const active = senders.find((s) => s.active && s.name);
|
|
51
|
+
const raw = active?.name ?? senders[0]?.name ?? "Verify";
|
|
52
|
+
return truncateSender(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return "Verify";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Sync check: is a Brevo API key configured?
|
|
60
|
+
* Used by detectOtpChannels() for capability reporting.
|
|
61
|
+
*/
|
|
62
|
+
export function hasSmsApiKey() {
|
|
63
|
+
const cfg = loadConfig();
|
|
64
|
+
return !!cfg.publicChat?.email?.apiKey;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve SMS credentials from config.
|
|
68
|
+
*
|
|
69
|
+
* Uses the same Brevo API key as email. The sender name is resolved by asking
|
|
70
|
+
* the email delivery module which sender it uses, then finding that sender's
|
|
71
|
+
* display name in Brevo. This ensures both email and SMS present the same
|
|
72
|
+
* brand identity. The name is truncated to 11 characters (GSM limit).
|
|
73
|
+
* Returns null if no API key is configured.
|
|
74
|
+
*/
|
|
75
|
+
export async function resolveSmsCredentials() {
|
|
10
76
|
const cfg = loadConfig();
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
77
|
+
const apiKey = cfg.publicChat?.email?.apiKey;
|
|
78
|
+
if (!apiKey)
|
|
13
79
|
return null;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
80
|
+
if (cachedSenderName &&
|
|
81
|
+
cachedSenderName.apiKey === apiKey &&
|
|
82
|
+
cachedSenderName.name.length <= 11) {
|
|
83
|
+
return { apiKey, sender: cachedSenderName.name };
|
|
84
|
+
}
|
|
85
|
+
// Resolve the email address that email delivery uses — ensures SMS matches
|
|
86
|
+
// the same Brevo sender regardless of whether `from` is set in config.
|
|
87
|
+
const emailCreds = await resolveEmailCredentials();
|
|
88
|
+
const name = await fetchSenderName(apiKey, emailCreds?.from);
|
|
89
|
+
cachedSenderName = { apiKey, name };
|
|
90
|
+
return { apiKey, sender: name };
|
|
19
91
|
}
|
|
20
92
|
/**
|
|
21
|
-
* Send an SMS via the
|
|
93
|
+
* Send an SMS via the Brevo transactional SMS API.
|
|
22
94
|
*/
|
|
23
95
|
export async function sendSms(to, body, creds) {
|
|
24
|
-
const
|
|
25
|
-
const auth = Buffer.from(`${creds.accountSid}:${creds.authToken}`).toString("base64");
|
|
26
|
-
const params = new URLSearchParams();
|
|
27
|
-
params.set("To", to);
|
|
28
|
-
params.set("From", creds.fromNumber);
|
|
29
|
-
params.set("Body", body);
|
|
30
|
-
const res = await fetch(url, {
|
|
96
|
+
const res = await fetch("https://api.brevo.com/v3/transactionalSMS/sms", {
|
|
31
97
|
method: "POST",
|
|
32
98
|
headers: {
|
|
33
|
-
|
|
34
|
-
"Content-Type": "application/
|
|
99
|
+
"api-key": creds.apiKey,
|
|
100
|
+
"Content-Type": "application/json",
|
|
101
|
+
Accept: "application/json",
|
|
35
102
|
},
|
|
36
|
-
body:
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
type: "transactional",
|
|
105
|
+
sender: creds.sender,
|
|
106
|
+
recipient: to,
|
|
107
|
+
content: body,
|
|
108
|
+
}),
|
|
37
109
|
});
|
|
38
110
|
if (!res.ok) {
|
|
39
111
|
const text = await res.text().catch(() => "");
|
|
40
|
-
throw new Error(`
|
|
112
|
+
throw new Error(`Brevo SMS failed (${res.status}): ${text} [sender=${JSON.stringify(creds.sender)}, recipient=${to}]`);
|
|
41
113
|
}
|
|
42
114
|
const json = (await res.json());
|
|
43
|
-
return {
|
|
115
|
+
return { id: String(json.messageId ?? "unknown") };
|
|
44
116
|
}
|
|
@@ -36,7 +36,7 @@ import { createInternalHookEvent, triggerInternalHook } from "../hooks/internal-
|
|
|
36
36
|
import { emitAgentEvent, onAgentEvent } from "../infra/agent-events.js";
|
|
37
37
|
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js";
|
|
38
38
|
import { requestOtp, verifyOtp } from "./public-chat/otp.js";
|
|
39
|
-
import { deliverOtp, detectOtpChannels } from "./public-chat/deliver-otp.js";
|
|
39
|
+
import { deliverOtp, detectOtpChannels, hasPhoneMethod, normalizeVerifyMethods, } from "./public-chat/deliver-otp.js";
|
|
40
40
|
import { buildPublicSessionKey, resolvePublicAgentId } from "./public-chat/session.js";
|
|
41
41
|
import { loadSessionEntry, readSessionMessages } from "./session-utils.js";
|
|
42
42
|
import { extractFileAttachments, sanitizeMediaForChat, stripEnvelopeFromMessages, } from "./chat-sanitize.js";
|
|
@@ -228,10 +228,10 @@ async function handleOtpRequest(req, res, accountId, cfg, maxBodyBytes) {
|
|
|
228
228
|
: typeof payload.phone === "string"
|
|
229
229
|
? payload.phone.trim()
|
|
230
230
|
: "";
|
|
231
|
-
const
|
|
232
|
-
const verifyMethods = cfg.publicChat?.verifyMethods ?? ["
|
|
231
|
+
const isEmailId = rawIdentifier.includes("@");
|
|
232
|
+
const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
|
|
233
233
|
let identifier;
|
|
234
|
-
if (
|
|
234
|
+
if (isEmailId) {
|
|
235
235
|
if (!verifyMethods.includes("email")) {
|
|
236
236
|
sendForbidden(res, "email verification is not enabled for this account");
|
|
237
237
|
return;
|
|
@@ -243,7 +243,7 @@ async function handleOtpRequest(req, res, accountId, cfg, maxBodyBytes) {
|
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
else {
|
|
246
|
-
if (!verifyMethods
|
|
246
|
+
if (!hasPhoneMethod(verifyMethods)) {
|
|
247
247
|
sendForbidden(res, "phone verification is not enabled for this account");
|
|
248
248
|
return;
|
|
249
249
|
}
|
|
@@ -265,8 +265,13 @@ async function handleOtpRequest(req, res, accountId, cfg, maxBodyBytes) {
|
|
|
265
265
|
// OTP code is sent from the correct number (not the first active account).
|
|
266
266
|
const agentId = resolvePublicAgentId(cfg, accountId);
|
|
267
267
|
const whatsappAccountId = resolveAgentBoundAccountId(cfg, agentId, "whatsapp") ?? undefined;
|
|
268
|
+
// Narrow delivery to visitor's preferred channel if specified and admin-enabled
|
|
269
|
+
const preferredChannel = typeof payload.channel === "string" ? payload.channel : undefined;
|
|
270
|
+
const deliveryMethods = preferredChannel && verifyMethods.includes(preferredChannel)
|
|
271
|
+
? [preferredChannel]
|
|
272
|
+
: verifyMethods;
|
|
268
273
|
try {
|
|
269
|
-
const delivery = await deliverOtp(identifier, result.code, whatsappAccountId);
|
|
274
|
+
const delivery = await deliverOtp(identifier, result.code, whatsappAccountId, deliveryMethods);
|
|
270
275
|
sendJson(res, 200, { ok: true, channel: delivery.channel });
|
|
271
276
|
}
|
|
272
277
|
catch (err) {
|
|
@@ -751,7 +756,7 @@ async function handleCapabilities(req, res, accountId, cfg) {
|
|
|
751
756
|
return;
|
|
752
757
|
}
|
|
753
758
|
const authMode = cfg.publicChat?.auth ?? "anonymous";
|
|
754
|
-
const verifyMethods = cfg.publicChat?.verifyMethods ?? ["
|
|
759
|
+
const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
|
|
755
760
|
const agentId = resolvePublicAgentId(cfg, accountId);
|
|
756
761
|
const whatsappAccountId = resolveAgentBoundAccountId(cfg, agentId, "whatsapp") ?? undefined;
|
|
757
762
|
const channels = detectOtpChannels(whatsappAccountId);
|
|
@@ -6,7 +6,7 @@ import { resolveAgentBoundAccountId } from "../../routing/bindings.js";
|
|
|
6
6
|
import { generateGreeting } from "../../suggestions/greeting.js";
|
|
7
7
|
import { ErrorCodes, errorShape } from "../protocol/index.js";
|
|
8
8
|
import { requestOtp, verifyOtp } from "../public-chat/otp.js";
|
|
9
|
-
import { deliverOtp } from "../public-chat/deliver-otp.js";
|
|
9
|
+
import { deliverOtp, hasPhoneMethod, normalizeVerifyMethods } from "../public-chat/deliver-otp.js";
|
|
10
10
|
import { buildPublicSessionKey, resolvePublicAgentId } from "../public-chat/session.js";
|
|
11
11
|
/** Strip spaces, dashes, and parentheses from a phone number. */
|
|
12
12
|
function normalizePhone(raw) {
|
|
@@ -46,10 +46,10 @@ export const publicChatHandlers = {
|
|
|
46
46
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "public chat disabled"));
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
const
|
|
50
|
-
const verifyMethods = cfg.publicChat?.verifyMethods ?? ["
|
|
49
|
+
const isEmailId = rawIdentifier.includes("@");
|
|
50
|
+
const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
|
|
51
51
|
let identifier;
|
|
52
|
-
if (
|
|
52
|
+
if (isEmailId) {
|
|
53
53
|
if (!verifyMethods.includes("email")) {
|
|
54
54
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "email verification is not enabled"));
|
|
55
55
|
return;
|
|
@@ -61,7 +61,7 @@ export const publicChatHandlers = {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
64
|
-
if (!verifyMethods
|
|
64
|
+
if (!hasPhoneMethod(verifyMethods)) {
|
|
65
65
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "phone verification is not enabled"));
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
@@ -82,8 +82,13 @@ export const publicChatHandlers = {
|
|
|
82
82
|
const whatsappAccountId = agentId
|
|
83
83
|
? (resolveAgentBoundAccountId(cfg, agentId, "whatsapp") ?? undefined)
|
|
84
84
|
: undefined;
|
|
85
|
+
// Narrow delivery to visitor's preferred channel if specified and admin-enabled
|
|
86
|
+
const preferredChannel = typeof params.channel === "string" ? params.channel : undefined;
|
|
87
|
+
const deliveryMethods = preferredChannel && verifyMethods.includes(preferredChannel)
|
|
88
|
+
? [preferredChannel]
|
|
89
|
+
: verifyMethods;
|
|
85
90
|
try {
|
|
86
|
-
const delivery = await deliverOtp(identifier, result.code, whatsappAccountId);
|
|
91
|
+
const delivery = await deliverOtp(identifier, result.code, whatsappAccountId, deliveryMethods);
|
|
87
92
|
respond(true, { ok: true, channel: delivery.channel });
|
|
88
93
|
}
|
|
89
94
|
catch (err) {
|
package/package.json
CHANGED
package/skills/brevo/SKILL.md
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brevo
|
|
3
|
-
description: Guide users through getting a free Brevo API key for email
|
|
3
|
+
description: Guide users through getting a free Brevo API key for email and SMS verification.
|
|
4
4
|
metadata: {"taskmaster":{"emoji":"🔑"}}
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Brevo Setup
|
|
8
8
|
|
|
9
|
-
Walks users through creating a Brevo account, verifying a sender email address, and obtaining an API key. Brevo uses single sender verification — the user verifies one email address by entering a 6-digit code sent to it. No DNS records or domain ownership required.
|
|
9
|
+
Walks users through creating a Brevo account, verifying a sender email address, and obtaining an API key. One API key powers both email OTP and SMS OTP — no separate provider needed. Brevo uses single sender verification — the user verifies one email address by entering a 6-digit code sent to it. No DNS records or domain ownership required.
|
|
10
10
|
|
|
11
11
|
## When to activate
|
|
12
12
|
|
|
13
|
-
- User enables Email OTP or Visitor Chooses mode for Public Chat and
|
|
14
|
-
- User asks about email verification setup or Brevo
|
|
15
|
-
- BOOTSTRAP detects missing Brevo API key when email verification is enabled
|
|
13
|
+
- User enables Email OTP, Phone OTP, or Visitor Chooses mode for Public Chat and Brevo is not configured
|
|
14
|
+
- User asks about email or SMS verification setup, or about Brevo
|
|
15
|
+
- BOOTSTRAP detects missing Brevo API key when email or SMS verification is enabled
|
|
16
|
+
- User asks about SMS fallback for phone verification
|
|
16
17
|
|
|
17
18
|
## What it unlocks
|
|
18
19
|
|
|
19
|
-
- Email OTP verification
|
|
20
|
-
-
|
|
21
|
-
- Free tier: 300 emails per day, no credit card required
|
|
20
|
+
- **Email OTP** — verification codes delivered via email to any address. Free tier: 300 emails per day, no credit card required.
|
|
21
|
+
- **SMS OTP** — verification codes delivered via text message when WhatsApp is unavailable. Requires prepaid SMS credits in Brevo (purchased separately). Same API key as email — no additional configuration.
|
|
22
22
|
|
|
23
23
|
## References
|
|
24
24
|
|
|
25
25
|
| Task | When to use | Reference |
|
|
26
26
|
|------|-------------|-----------|
|
|
27
27
|
| Guided setup | User wants help getting the key | `references/browser-setup.md` |
|
|
28
|
+
| SMS credits | User wants to enable SMS fallback | `references/sms-credits.md` |
|
|
28
29
|
|
|
29
30
|
Load the reference and follow its instructions.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Brevo
|
|
1
|
+
# Brevo — Guided Setup
|
|
2
2
|
|
|
3
|
-
Walk the user through getting Brevo credentials. Brevo uses single sender verification — the user verifies an email address by entering a 6-digit code sent to it. No DNS records required. Guide with clear instructions.
|
|
3
|
+
Walk the user through getting Brevo credentials. One API key powers both email OTP and SMS OTP. Brevo uses single sender verification — the user verifies an email address by entering a 6-digit code sent to it. No DNS records required. Guide with clear instructions.
|
|
4
4
|
|
|
5
5
|
**Important:** Brevo is primarily a marketing email platform. The signup flow and dashboard are oriented around marketing campaigns, contacts, and newsletters. The user does NOT need any of that. Guide them directly to the two things they need: a verified sender and an API key. Both are in the **Settings** page — accessed via the **gear icon** in the top-right toolbar. On the Settings page, both options are in the left sidebar under **Organization settings**.
|
|
6
6
|
|
|
@@ -119,6 +119,14 @@ The key is applied immediately — no restart needed. Confirm to the user:
|
|
|
119
119
|
|
|
120
120
|
That's it. No further configuration is needed in Taskmaster — the verified sender address is detected automatically from Brevo.
|
|
121
121
|
|
|
122
|
+
## SMS fallback (optional)
|
|
123
|
+
|
|
124
|
+
If the user wants SMS verification codes (for when WhatsApp is unavailable), they can enable it with the same Brevo account. No additional API key or configuration is needed — just SMS credits.
|
|
125
|
+
|
|
126
|
+
> "Your Brevo API key also works for SMS — same key, no extra setup. You just need SMS credits in your Brevo account. I can walk you through that if you'd like."
|
|
127
|
+
|
|
128
|
+
If the user wants to proceed, load `references/sms-credits.md` and follow it.
|
|
129
|
+
|
|
122
130
|
---
|
|
123
131
|
|
|
124
132
|
## If the user already has a Brevo account
|
|
@@ -158,4 +166,6 @@ If yes, skip to Step 4. If they're unsure:
|
|
|
158
166
|
| "Do I need to set up DNS records?" | No. Single sender verification works without DNS. Brevo will recommend domain authentication for better deliverability, but it's not required. |
|
|
159
167
|
| "Can I change the sender address later?" | Yes — add and verify a new sender in Brevo. Taskmaster auto-detects the verified sender, so no changes needed on this end. |
|
|
160
168
|
| "Where does Taskmaster get the sender address?" | Automatically from the Brevo API. When you verify a sender in Brevo, Taskmaster detects it — you don't need to enter it anywhere else. |
|
|
161
|
-
| "What about all the marketing stuff?" | Ignore it. Brevo is a full marketing platform, but we only use its transactional email
|
|
169
|
+
| "What about all the marketing stuff?" | Ignore it. Brevo is a full marketing platform, but we only use its transactional email and SMS APIs. You don't need to set up contacts, campaigns, or templates. |
|
|
170
|
+
| "Can Brevo also send SMS?" | Yes — the same API key works for both email and SMS. You just need to purchase SMS credits in your Brevo account. See the SMS credits reference. |
|
|
171
|
+
| "Do I need a separate provider for SMS?" | No. Brevo handles both email and SMS verification with the same API key. No Twilio or other SMS provider needed. |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Brevo SMS Credits — Guided Setup
|
|
2
|
+
|
|
3
|
+
Walk the user through purchasing SMS credits in Brevo. SMS verification uses the same Brevo API key as email — no additional configuration in Taskmaster. The user just needs credits in their Brevo account.
|
|
4
|
+
|
|
5
|
+
**Important:** SMS credits are prepaid and never expire. The cost depends on the destination country and number of messages.
|
|
6
|
+
|
|
7
|
+
**SMS sender name:** The name that appears on the SMS (e.g. "Taskmaster") is taken from the same Brevo verified sender used for email — matched by email address. If you have multiple senders in Brevo, the one whose email matches the configured sender is used. The name is truncated to 11 characters (GSM limit) — if longer, the first word is used (e.g. "Rubytech LLC" becomes "Rubytech"). To control the SMS sender name, edit the sender's display name in Brevo under **Senders, Domains & Dedicated IPs → Senders**.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- User already has a Brevo account with an API key configured in Taskmaster (if not, use `references/browser-setup.md` first)
|
|
14
|
+
- Chat channel for sending instructions
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Step 1: Explain
|
|
19
|
+
|
|
20
|
+
> "Your Brevo API key already works for SMS — no extra setup needed on our end. You just need SMS credits in your Brevo account. Credits are prepaid and never expire, so you only pay for what you need.
|
|
21
|
+
>
|
|
22
|
+
> Let me walk you through purchasing credits. It takes about 2 minutes."
|
|
23
|
+
|
|
24
|
+
## Step 2: Purchase SMS credits
|
|
25
|
+
|
|
26
|
+
**Navigation: Dashboard home → lightning icon (top-right toolbar) → "Campaign usage and plan" popup → SMS section → "Get more credits".**
|
|
27
|
+
|
|
28
|
+
> "1. Go to **app.brevo.com** and log in — you should see the main dashboard ('Hello [name]')
|
|
29
|
+
> 2. In the **top-right toolbar**, click the **lightning bolt icon** (⚡) — it's to the left of the help (?) and gear (⚙️) icons
|
|
30
|
+
> 3. A **'Campaign usage and plan'** popup appears showing your plan, email credits, and SMS credits
|
|
31
|
+
> 4. Under the **SMS** section (shows '0 credits left' if you haven't bought any), click **'Get more credits'**
|
|
32
|
+
> 5. On the **'Add messages credits'** page:
|
|
33
|
+
> - **Type of message** — select **SMS**
|
|
34
|
+
> - **Number of messages** — enter **100** (a good starting amount for verification codes)
|
|
35
|
+
> - **Message destination** — select your country (e.g. **United Kingdom**)
|
|
36
|
+
> - The **Purchase summary** on the right shows the total cost
|
|
37
|
+
> 6. Click **'Proceed to checkout'** and complete the purchase
|
|
38
|
+
>
|
|
39
|
+
> Let me know when that's done."
|
|
40
|
+
|
|
41
|
+
If the user can't find the lightning icon:
|
|
42
|
+
|
|
43
|
+
> "In the top-right toolbar of the Brevo dashboard, there are several small icons. The lightning bolt (⚡) is the first one on the left, before the help (?), gear (⚙️), bell (🔔), and chart icons. Click that — it opens a popup showing your plan and credits."
|
|
44
|
+
|
|
45
|
+
Wait for the user to confirm.
|
|
46
|
+
|
|
47
|
+
## Step 3: Confirm
|
|
48
|
+
|
|
49
|
+
> "SMS credits are ready. To enable SMS verification, go to the **Setup** page → **Public Chat** settings → check the **SMS** checkbox under verification methods. You can enable SMS alongside WhatsApp, or on its own. When a visitor chooses SMS on the public chat page, the code is sent by text message. No restart needed — it uses your existing Brevo API key."
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Pricing reference
|
|
54
|
+
|
|
55
|
+
Pricing depends on the destination country and volume. The exact cost is shown on the checkout page when you select a country and message count. As a rough guide:
|
|
56
|
+
|
|
57
|
+
| Destination | 100 messages |
|
|
58
|
+
|-------------|-------------|
|
|
59
|
+
| UK | ~£2.82 |
|
|
60
|
+
| US / Canada | ~$1.09 |
|
|
61
|
+
|
|
62
|
+
Credits are called "message credits" and can be used for SMS. On the free plan, WhatsApp messages via Brevo require a Professional plan upgrade — but we don't use Brevo for WhatsApp, so this doesn't apply.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Common questions
|
|
67
|
+
|
|
68
|
+
| Question | Answer |
|
|
69
|
+
|----------|--------|
|
|
70
|
+
| "Do credits expire?" | No — prepaid SMS credits never expire. |
|
|
71
|
+
| "How many credits per SMS?" | Depends on the destination country. The purchase page shows the exact conversion (e.g. 316.5 credits = 100 UK messages). |
|
|
72
|
+
| "What phone number do SMS come from?" | Brevo assigns a sender automatically based on the destination country. You don't need to buy or manage a phone number. |
|
|
73
|
+
| "What name appears on the SMS?" | The display name from the same Brevo verified sender used for email (matched by email address). Must be 11 characters or fewer — if longer, the first word is used. Edit sender names under **Senders, Domains & Dedicated IPs → Senders** in Brevo. |
|
|
74
|
+
| "Do I need to change anything in Taskmaster?" | No. If your Brevo API key is already configured, SMS works automatically when you have credits. |
|
|
75
|
+
| "Can I use SMS without email?" | The Brevo API key is configured through the email setup, but once configured it serves both email and SMS. You need at least one verified sender. |
|
|
76
|
+
| "Where do I check my remaining credits?" | Click the lightning bolt icon (⚡) in the top-right toolbar. The popup shows your SMS credits remaining. |
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: system-admin
|
|
3
|
+
description: "System administration through conversation. Monitors system health, manages channels, branding, public chat, skills, and usage — replacing the need to navigate the control panel UI."
|
|
4
|
+
metadata: {"taskmaster":{"always":true,"emoji":"⚙️","skillKey":"system-admin"}}
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# System Administration
|
|
8
|
+
|
|
9
|
+
You are the system administrator — the single point of contact for everything the business owner would otherwise need the control panel for. Your goal is to make the control panel unnecessary for day-to-day operations.
|
|
10
|
+
|
|
11
|
+
## Philosophy
|
|
12
|
+
|
|
13
|
+
The user chose Taskmaster because they want ONE conversation, not a dozen apps. Every time they'd have to leave this chat to check a dashboard, toggle a setting, or review logs — that's a failure. Handle it here.
|
|
14
|
+
|
|
15
|
+
## Available Tools
|
|
16
|
+
|
|
17
|
+
You have seven system administration tools. Use them proactively and reactively.
|
|
18
|
+
|
|
19
|
+
### Monitoring (read-only, always safe)
|
|
20
|
+
|
|
21
|
+
- **system_status** — System health, auth status, license, channel connections, software version. Use `overview` for a combined snapshot when the user asks "how's the system?" or at the start of any admin session.
|
|
22
|
+
- **usage_report** — Token counts and cost data. Use `summary` for token breakdown, `cost` for spend over time. Offer monthly cost reports proactively.
|
|
23
|
+
- **logs_read** — System logs and session transcripts. Use `system` for gateway errors, `sessions` for conversation traces. Essential for debugging any "it's not working" report.
|
|
24
|
+
|
|
25
|
+
### Configuration (read + write, check before acting)
|
|
26
|
+
|
|
27
|
+
- **channel_settings** — WhatsApp/iMessage connection status and settings. Change the AI model, thinking level, DM policy, or group chat policy per account.
|
|
28
|
+
- **brand_settings** — Accent color, background color, logo status. Set colors via hex values. Logo upload requires the UI (binary data).
|
|
29
|
+
- **public_chat_settings** — Enable/disable the public chat widget, manage the greeting, set access mode (open/verified/visitor-chooses).
|
|
30
|
+
- **skill_manage** — List, create, update, delete, or install skills. Preloaded skills cannot be deleted.
|
|
31
|
+
|
|
32
|
+
### Already available (from other tools)
|
|
33
|
+
|
|
34
|
+
- **api_keys** — Manage provider API keys (Google, Tavily, OpenAI, etc.)
|
|
35
|
+
- **software_update** — Check for updates, install them, restart the gateway
|
|
36
|
+
- **cron** — Schedule recurring events and reminders
|
|
37
|
+
- **gateway** — Restart the gateway, apply config changes
|
|
38
|
+
|
|
39
|
+
## When to Use These Tools
|
|
40
|
+
|
|
41
|
+
### Reactively — the user asks
|
|
42
|
+
|
|
43
|
+
| User says | You do |
|
|
44
|
+
|-----------|--------|
|
|
45
|
+
| "How's the system?" / "Is everything working?" | `system_status` overview |
|
|
46
|
+
| "What's my usage?" / "How much am I spending?" | `usage_report` cost + summary |
|
|
47
|
+
| "Is WhatsApp connected?" | `channel_settings` status |
|
|
48
|
+
| "Change the color to blue" | `brand_settings` set_colors |
|
|
49
|
+
| "Enable the public chat" | `public_chat_settings` enable |
|
|
50
|
+
| "What model am I using?" | `channel_settings` status |
|
|
51
|
+
| "Switch to a cheaper model" | `channel_settings` set_model |
|
|
52
|
+
| "Show me the logs" | `logs_read` system |
|
|
53
|
+
| "Something isn't working" | `logs_read` system + `system_status` overview |
|
|
54
|
+
| "Is there an update?" | `software_update` check |
|
|
55
|
+
|
|
56
|
+
### Proactively — you notice something
|
|
57
|
+
|
|
58
|
+
- After a restart or reconnection, check `system_status` and report any issues.
|
|
59
|
+
- When discussing costs, offer to pull `usage_report` data.
|
|
60
|
+
- If a user reports a message not going through, check `channel_settings` status and `logs_read`.
|
|
61
|
+
- When generating a greeting for a new public chat setup, use `public_chat_settings` regenerate_greeting.
|
|
62
|
+
|
|
63
|
+
## Principles
|
|
64
|
+
|
|
65
|
+
- **Explain, don't just execute.** When changing a setting, tell the user what you're doing and what it means. "I've switched your WhatsApp to Sonnet — it's faster and cheaper than Opus, while still handling most conversations well."
|
|
66
|
+
- **Confirm destructive actions.** Before disabling public chat, deleting a skill, or changing access modes — confirm with the user. Read operations never need confirmation.
|
|
67
|
+
- **Surface the important, hide the noise.** The user doesn't need to see raw JSON. Summarize system status in plain language. "Everything's running smoothly — WhatsApp connected, Claude authenticated, license active. You've used about $4.20 this month."
|
|
68
|
+
- **Know your limits.** Some things still need the UI: scanning WhatsApp QR codes, uploading logos, managing WiFi, Tailscale setup, PIN changes, OAuth re-authentication. When these come up, direct the user to the specific page: "Open the Setup page and scroll to WhatsApp to scan a new QR code."
|