@rubytech/taskmaster 1.11.2 → 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.
@@ -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-s8s_YKvR.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,6 +1,9 @@
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
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.
4
7
  */
5
8
  import { getActiveWebListener } from "../../web/active-listener.js";
6
9
  import { sendMessageWhatsApp } from "../../web/outbound.js";
@@ -10,6 +13,27 @@ import { hasEmailApiKey, resolveEmailCredentials, sendEmail } from "./deliver-em
10
13
  function isEmail(identifier) {
11
14
  return identifier.includes("@");
12
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
+ }
13
37
  /**
14
38
  * Detect which OTP delivery channels are currently available.
15
39
  * Does not attempt delivery — used by the capabilities endpoint.
@@ -28,11 +52,12 @@ export function detectOtpChannels(whatsappAccountId) {
28
52
  * Deliver a verification code to the given identifier.
29
53
  *
30
54
  * Email identifiers (containing @) are sent via Brevo email API.
31
- * Phone identifiers use the existing WhatsApp -> SMS fallback chain.
32
- * Both SMS and email share the same Brevo API key.
55
+ * Phone identifiers try WhatsApp then SMS, but only channels that
56
+ * are enabled in `enabledMethods` are attempted.
33
57
  */
34
- export async function deliverOtp(identifier, code, accountId) {
58
+ export async function deliverOtp(identifier, code, accountId, enabledMethods) {
35
59
  const message = `Your verification code is: ${code}`;
60
+ const methods = enabledMethods ?? ["whatsapp", "sms", "email"];
36
61
  // Email identifiers — deliver via Brevo email API
37
62
  if (isEmail(identifier)) {
38
63
  const emailCreds = await resolveEmailCredentials();
@@ -42,24 +67,33 @@ export async function deliverOtp(identifier, code, accountId) {
42
67
  await sendEmail(identifier, "Your verification code", message, emailCreds);
43
68
  return { channel: "email" };
44
69
  }
45
- // Phone identifiers — WhatsApp first, SMS fallback
46
- const hasWhatsApp = !!getActiveWebListener(accountId);
47
- const smsCreds = await resolveSmsCredentials();
48
- 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) {
49
74
  try {
50
75
  await sendMessageWhatsApp(identifier, message, { verbose: false, accountId });
51
76
  return { channel: "whatsapp" };
52
77
  }
53
78
  catch {
54
- // WhatsApp failed — fall through to SMS
79
+ // WhatsApp failed — fall through to SMS if enabled
55
80
  }
56
81
  }
57
- if (smsCreds) {
58
- await sendSms(identifier, message, smsCreds);
59
- 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
+ }
60
88
  }
61
- if (hasWhatsApp) {
62
- 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.");
63
97
  }
64
- throw new Error("No verification channel available. Connect WhatsApp or add a Brevo API key with SMS credits.");
98
+ throw new Error(`Failed to send verification code via ${tried.join(" and ")}. Check that the channel is connected and configured.`);
65
99
  }
@@ -1,18 +1,37 @@
1
1
  /**
2
2
  * Lightweight Brevo SMS sender — single HTTP POST, no SDK dependency.
3
3
  *
4
- * Uses the same Brevo API key as email delivery. The sender name is
5
- * auto-detected from the verified email sender's display name.
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.
6
7
  * SMS requires prepaid credits in the Brevo account.
7
8
  */
8
9
  import { loadConfig } from "../../config/config.js";
9
- /** Cached sender name avoids hitting the API on every SMS. */
10
+ import { resolveEmailCredentials } from "./deliver-email.js";
11
+ /** Cached sender name (already truncated to ≤11 chars). */
10
12
  let cachedSenderName = null;
11
13
  /**
12
- * Query Brevo for the first active sender's display name.
13
- * Returns the sender name or "Verify" as a safe fallback.
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.
14
16
  */
15
- async function fetchSenderName(apiKey) {
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) {
16
35
  try {
17
36
  const res = await fetch("https://api.brevo.com/v3/senders", {
18
37
  headers: { "api-key": apiKey, Accept: "application/json" },
@@ -20,8 +39,17 @@ async function fetchSenderName(apiKey) {
20
39
  if (!res.ok)
21
40
  return "Verify";
22
41
  const json = (await res.json());
23
- const active = json.senders?.find((s) => s.active && s.name);
24
- return active?.name ?? json.senders?.[0]?.name ?? "Verify";
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);
25
53
  }
26
54
  catch {
27
55
  return "Verify";
@@ -38,8 +66,10 @@ export function hasSmsApiKey() {
38
66
  /**
39
67
  * Resolve SMS credentials from config.
40
68
  *
41
- * Uses the same Brevo API key as email. The sender name is resolved
42
- * from the cached value or fetched from the Brevo senders API.
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).
43
73
  * Returns null if no API key is configured.
44
74
  */
45
75
  export async function resolveSmsCredentials() {
@@ -47,10 +77,15 @@ export async function resolveSmsCredentials() {
47
77
  const apiKey = cfg.publicChat?.email?.apiKey;
48
78
  if (!apiKey)
49
79
  return null;
50
- if (cachedSenderName && cachedSenderName.apiKey === apiKey) {
80
+ if (cachedSenderName &&
81
+ cachedSenderName.apiKey === apiKey &&
82
+ cachedSenderName.name.length <= 11) {
51
83
  return { apiKey, sender: cachedSenderName.name };
52
84
  }
53
- const name = await fetchSenderName(apiKey);
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);
54
89
  cachedSenderName = { apiKey, name };
55
90
  return { apiKey, sender: name };
56
91
  }
@@ -74,7 +109,7 @@ export async function sendSms(to, body, creds) {
74
109
  });
75
110
  if (!res.ok) {
76
111
  const text = await res.text().catch(() => "");
77
- throw new Error(`Brevo SMS failed (${res.status}): ${text}`);
112
+ throw new Error(`Brevo SMS failed (${res.status}): ${text} [sender=${JSON.stringify(creds.sender)}, recipient=${to}]`);
78
113
  }
79
114
  const json = (await res.json());
80
115
  return { id: String(json.messageId ?? "unknown") };
@@ -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.2",
3
+ "version": "1.12.0",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -2,7 +2,9 @@
2
2
 
3
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
4
 
5
- **Important:** SMS credits are prepaid and never expire. Pricing varies by country (see below). The SMS sender name is auto-detected from the user's verified email sender in Brevo — no manual configuration needed.
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**.
6
8
 
7
9
  ---
8
10
 
@@ -21,38 +23,43 @@ Walk the user through purchasing SMS credits in Brevo. SMS verification uses the
21
23
 
22
24
  ## Step 2: Purchase SMS credits
23
25
 
24
- > "1. Go to **brevo.com** and log in
25
- > 2. In the **top-right toolbar**, click the **gear icon** (⚙️) to open **Settings**
26
- > 3. In the left sidebar, look for **Your plan** or **Billing** under your account settings
27
- > 4. Find the **SMS credits** section and click **Buy credits**
28
- > 5. Choose how many credits you need:
29
- > - **100 credits** is a good starting amount for verification codes
30
- > - Each SMS uses 1 credit
31
- > - Credits never expire
32
- > 6. Complete the purchase
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
33
38
  >
34
39
  > Let me know when that's done."
35
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
+
36
45
  Wait for the user to confirm.
37
46
 
38
47
  ## Step 3: Confirm
39
48
 
40
- > "SMS verification is now enabled. When a visitor verifies their phone number and WhatsApp is unavailable, the verification code will be sent by text message instead. No restart or configuration change needed — it works automatically with your existing Brevo API key."
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."
41
50
 
42
51
  ---
43
52
 
44
53
  ## Pricing reference
45
54
 
46
- Approximate SMS credit costs (varies by country):
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:
47
56
 
48
- | Region | Cost per 100 credits |
49
- |--------|---------------------|
57
+ | Destination | 100 messages |
58
+ |-------------|-------------|
59
+ | UK | ~£2.82 |
50
60
  | US / Canada | ~$1.09 |
51
- | UK | ~$3.45 |
52
- | Australia | ~$5.50 |
53
- | EU (varies) | ~$3.00–$7.00 |
54
61
 
55
- Exact pricing is shown during purchase in the Brevo dashboard.
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.
56
63
 
57
64
  ---
58
65
 
@@ -61,8 +68,9 @@ Exact pricing is shown during purchase in the Brevo dashboard.
61
68
  | Question | Answer |
62
69
  |----------|--------|
63
70
  | "Do credits expire?" | No — prepaid SMS credits never expire. |
64
- | "How many credits per SMS?" | One credit per message. Each verification code is one SMS. |
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). |
65
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. |
66
- | "What name appears on the SMS?" | The sender name from your verified email sender in Brevo (e.g. your business name). Taskmaster detects this automatically. |
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. |
67
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. |
68
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. |
@@ -557,6 +557,31 @@ Your assistant can manage who has admin access — the same as using the Admins
557
557
  - "Who are my current admins?"
558
558
  - "Remove the old number ending in 789 from admins"
559
559
 
560
+ ### System Administration
561
+
562
+ Your assistant can check and manage many system settings directly through chat — the same things you'd normally check in the Setup page on the control panel. Think of it as a shortcut: instead of opening the control panel and navigating to the right setting, just ask.
563
+
564
+ | Capability | Description |
565
+ |------------|-------------|
566
+ | System status | Check gateway health, WhatsApp/iMessage connections, Claude authentication, license, software version |
567
+ | Usage and cost | See how many tokens you've used and what it's costing, broken down by provider |
568
+ | Channel settings | Change the AI model, thinking level, DM policy, or group chat policy for your WhatsApp accounts |
569
+ | Branding | Update your accent or background colour |
570
+ | Public chat | Enable or disable the public chat widget, change the greeting, set visitor verification |
571
+ | Skills | List, create, delete, or toggle skills |
572
+ | Logs | Review system and session logs for troubleshooting |
573
+
574
+ **Try asking:**
575
+ - "How's the system doing?"
576
+ - "What's my usage this month?"
577
+ - "Is WhatsApp connected?"
578
+ - "Switch WhatsApp to a cheaper model"
579
+ - "Change the accent colour to blue"
580
+ - "Enable public chat"
581
+ - "Show me the recent logs"
582
+
583
+ > **Note:** Some things still need the control panel: scanning WhatsApp QR codes, uploading logos, WiFi setup, Tailscale remote access, and PIN management. Your assistant will tell you when to open the control panel and where to go.
584
+
560
585
  ### Web Browsing
561
586
 
562
587
  Your assistant can open and interact with websites in a built-in browser — filling in forms, clicking buttons, and navigating pages. You can watch what it's doing on the **Browser** page in real time, and take over if it needs help (for example, to complete a CAPTCHA or log in to a site).
@@ -1001,6 +1026,21 @@ If the Pi checks pass but the control panel still doesn't load from another devi
1001
1026
  2. **Check you're on the same subnet** — mDNS only works between devices on the same network subnet. It won't work across separate subnets, VLANs, or guest networks. A common cause: the Pi is on Ethernet while your device is on WiFi, and your router assigns them to different subnets. Check by comparing the first three groups of your IP addresses (e.g. if the Pi is `192.168.88.4` and your laptop is `192.168.10.172`, they're on different subnets).
1002
1027
  3. **Contact support** with: what you changed (port/hostname), the output of `hostname`, `grep 127.0.1.1 /etc/hosts`, and `taskmaster daemon status`.
1003
1028
 
1029
+ #### Hostname changed to `taskmaster-2.local` (or similar)
1030
+
1031
+ If your control panel URL suddenly stops working and you notice the hostname has a `-2` (or `-3`) suffix, the Pi detected a name conflict on the network and renamed itself.
1032
+
1033
+ **When this happens:** If you unplug the Ethernet cable while the Pi is already running on WiFi, the Pi briefly had two network connections active at the same time. Its hostname detection saw its own name coming from the Ethernet side and treated it as a conflict, adding a `-2` suffix.
1034
+
1035
+ **Fix:**
1036
+
1037
+ 1. Power the Pi off completely (not just restart — fully power off)
1038
+ 2. Wait at least 2 minutes (this lets the mDNS name cache clear on all devices on the network)
1039
+ 3. Power the Pi back on
1040
+ 4. Access the control panel using your normal URL
1041
+
1042
+ **To avoid this in future:** Connect via WiFi first, then remove the Ethernet cable — don't unplug Ethernet while the Pi is running with both cables connected.
1043
+
1004
1044
  ### Static IP Address
1005
1045
 
1006
1046
  You don't need to assign a static IP address — Taskmaster uses a `.local` hostname that resolves automatically via mDNS, even if the IP changes after a reboot or DHCP renewal. You'll always find your control panel at the same `.local` address.
@@ -1467,16 +1507,19 @@ By default, anyone who opens the public chat can start a conversation immediatel
1467
1507
  1. Go to the **Setup** page
1468
1508
  2. Find the **Public Chat** status card
1469
1509
  3. Click the settings icon to open the verification settings modal
1470
- 4. Choose a verification method:
1510
+ 4. Choose an auth mode: **Anonymous** (no verification), **Verified** (must verify before chatting), or **Visitor chooses** (can chat immediately or verify for cross-device continuity)
1511
+ 5. Select which verification channels to enable — three independent checkboxes:
1512
+
1513
+ | Channel | How it works | What you need |
1514
+ |---------|-------------|---------------|
1515
+ | **WhatsApp** | Verification code sent via WhatsApp | A connected WhatsApp account |
1516
+ | **SMS** | Verification code sent by text message | Same [Brevo](https://brevo.com) API key as email, plus prepaid SMS credits in your Brevo account |
1517
+ | **Email** | Verification code sent by email | A [Brevo](https://brevo.com) API key and a verified sender (free, no credit card) |
1471
1518
 
1472
- | Method | How it works | What you need |
1473
- |--------|-------------|---------------|
1474
- | **Anonymous** | No verification — visitors chat immediately | Nothing (default) |
1475
- | **Email OTP** | Visitors enter their email, receive a one-time code | A [Brevo](https://brevo.com) API key and a verified sender (free, no credit card). The sender address is configured in Brevo, not in Taskmaster. |
1476
- | **SMS OTP** | Visitors enter their phone number, receive a one-time code | Same [Brevo](https://brevo.com) API key as email, plus prepaid SMS credits in your Brevo account |
1519
+ When both WhatsApp and SMS are enabled, phone verification tries WhatsApp first and falls back to SMS. To use SMS only, uncheck WhatsApp.
1477
1520
 
1478
- 5. Enter your provider credentials — add the Brevo API key in the **API Keys** section on the Setup page. The same key serves both email and SMS verification. For SMS, purchase SMS credits in your Brevo dashboard.
1479
- 6. Settings auto-save when you leave each field
1521
+ 6. Enter your provider credentials — add the Brevo API key in the **API Keys** section on the Setup page. The same key serves both email and SMS verification. For SMS, purchase SMS credits in your Brevo dashboard.
1522
+ 7. Settings auto-save when you change a checkbox or leave a field
1480
1523
 
1481
1524
  Once enabled, visitors must authenticate before they can send their first message. Verified contact details are stored and available to the agent via the `verify_contact` tool, so your assistant knows who it's talking to.
1482
1525