@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.
Files changed (30) hide show
  1. package/dist/agents/skills/workspace.js +3 -0
  2. package/dist/agents/taskmaster-tools.js +14 -0
  3. package/dist/agents/tool-policy.js +18 -0
  4. package/dist/agents/tools/brand-settings-tool.js +106 -0
  5. package/dist/agents/tools/channel-settings-tool.js +130 -0
  6. package/dist/agents/tools/logs-read-tool.js +67 -0
  7. package/dist/agents/tools/public-chat-settings-tool.js +138 -0
  8. package/dist/agents/tools/skill-manage-tool.js +105 -0
  9. package/dist/agents/tools/system-status-tool.js +82 -0
  10. package/dist/agents/tools/usage-report-tool.js +46 -0
  11. package/dist/build-info.json +3 -3
  12. package/dist/config/port-defaults.js +0 -8
  13. package/dist/config/zod-schema.js +6 -1
  14. package/dist/control-ui/assets/{index-YAjVyXqJ.js → index-DpMaqt-b.js} +657 -677
  15. package/dist/control-ui/assets/index-DpMaqt-b.js.map +1 -0
  16. package/dist/control-ui/index.html +1 -1
  17. package/dist/gateway/control-ui.js +2 -2
  18. package/dist/gateway/public-chat/deliver-otp.js +54 -18
  19. package/dist/gateway/public-chat/deliver-sms.js +96 -24
  20. package/dist/gateway/public-chat-api.js +12 -7
  21. package/dist/gateway/server-methods/public-chat.js +11 -6
  22. package/package.json +1 -1
  23. package/skills/brevo/SKILL.md +9 -8
  24. package/skills/brevo/references/browser-setup.md +13 -3
  25. package/skills/brevo/references/sms-credits.md +76 -0
  26. package/skills/system-admin/SKILL.md +68 -0
  27. package/skills/twilio/SKILL.md +14 -11
  28. package/skills/twilio/references/browser-setup.md +15 -24
  29. package/taskmaster-docs/USER-GUIDE.md +51 -8
  30. 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-YAjVyXqJ.js"></script>
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 ?? ["phone"], greeting: config.publicChat?.greetings?.[accountId] || undefined })};` +
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 (primary), SMS (fallback), or email (Brevo).
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 (resolveSmsCredentials())
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 use the existing WhatsApp -> SMS fallback chain.
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
- // Email identifiers deliver via Brevo only
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 first, SMS fallback
44
- const hasWhatsApp = !!getActiveWebListener(accountId);
45
- const smsCreds = resolveSmsCredentials();
46
- if (hasWhatsApp) {
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 (smsCreds) {
56
- await sendSms(identifier, message, smsCreds);
57
- return { channel: "sms" };
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
- if (hasWhatsApp) {
60
- throw new Error("Failed to send verification code via WhatsApp and SMS is not configured.");
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("No verification channel available. Connect WhatsApp or configure SMS credentials.");
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 Twilio SMS sender — single HTTP POST, no SDK dependency.
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
- * Read SMS credentials from `publicChat.sms` in config.
7
- * Returns null if any required field is missing.
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
- export function resolveSmsCredentials() {
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 sms = cfg.publicChat?.sms;
12
- if (!sms?.accountSid || !sms.authToken || !sms.fromNumber)
77
+ const apiKey = cfg.publicChat?.email?.apiKey;
78
+ if (!apiKey)
13
79
  return null;
14
- return {
15
- accountSid: sms.accountSid,
16
- authToken: sms.authToken,
17
- fromNumber: sms.fromNumber,
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 Twilio REST API.
93
+ * Send an SMS via the Brevo transactional SMS API.
22
94
  */
23
95
  export async function sendSms(to, body, creds) {
24
- const url = `https://api.twilio.com/2010-04-01/Accounts/${encodeURIComponent(creds.accountSid)}/Messages.json`;
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
- Authorization: `Basic ${auth}`,
34
- "Content-Type": "application/x-www-form-urlencoded",
99
+ "api-key": creds.apiKey,
100
+ "Content-Type": "application/json",
101
+ Accept: "application/json",
35
102
  },
36
- body: params.toString(),
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(`Twilio SMS failed (${res.status}): ${text}`);
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 { sid: json.sid ?? "unknown" };
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 isEmail = rawIdentifier.includes("@");
232
- const verifyMethods = cfg.publicChat?.verifyMethods ?? ["phone"];
231
+ const isEmailId = rawIdentifier.includes("@");
232
+ const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
233
233
  let identifier;
234
- if (isEmail) {
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.includes("phone")) {
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 ?? ["phone"];
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 isEmail = rawIdentifier.includes("@");
50
- const verifyMethods = cfg.publicChat?.verifyMethods ?? ["phone"];
49
+ const isEmailId = rawIdentifier.includes("@");
50
+ const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
51
51
  let identifier;
52
- if (isEmail) {
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.includes("phone")) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,29 +1,30 @@
1
1
  ---
2
2
  name: brevo
3
- description: Guide users through getting a free Brevo API key for email OTP verification.
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 email is not configured
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 for public chat visitors
20
- - Verification codes delivered via email to any address
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 Email — Guided Setup
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 API. You don't need to set up contacts, campaigns, or templates. |
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."