@rubytech/taskmaster 1.10.0 → 1.11.1

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,8 +6,8 @@
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-CTFFQkyj.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-n_TWBBTg.css">
9
+ <script type="module" crossorigin src="./assets/index-YAjVyXqJ.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-CpaEIgQy.css">
11
11
  </head>
12
12
  <body>
13
13
  <taskmaster-app></taskmaster-app>
@@ -0,0 +1,92 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { DEFAULT_CRON_DIR } from "./store.js";
4
+ export const DEFAULT_SEED_TRACKER_PATH = path.join(DEFAULT_CRON_DIR, "seeded.json");
5
+ export async function loadSeedTracker(trackerPath) {
6
+ try {
7
+ const raw = await fs.promises.readFile(trackerPath, "utf-8");
8
+ const parsed = JSON.parse(raw);
9
+ return {
10
+ version: 1,
11
+ seeded: Array.isArray(parsed?.seeded) ? parsed.seeded : [],
12
+ };
13
+ }
14
+ catch {
15
+ return { version: 1, seeded: [] };
16
+ }
17
+ }
18
+ export async function saveSeedTracker(trackerPath, tracker) {
19
+ await fs.promises.mkdir(path.dirname(trackerPath), { recursive: true });
20
+ const json = JSON.stringify(tracker, null, 2);
21
+ await fs.promises.writeFile(trackerPath, json, "utf-8");
22
+ }
23
+ export function isTemplateSeeded(tracker, templateId) {
24
+ return tracker.seeded.some((e) => e.templateId === templateId);
25
+ }
26
+ export function markTemplateSeeded(tracker, templateId, jobId) {
27
+ tracker.seeded.push({ templateId, jobId, seededAtMs: Date.now() });
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Template loader — scan bundled skills for cron-template.json files
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Scan the bundled skills directory for cron-template.json files.
34
+ * Returns validated templates. Skips files that can't be parsed or lack a templateId.
35
+ */
36
+ export function loadCronTemplatesFromBundledSkills(bundledSkillsDir) {
37
+ const templates = [];
38
+ let entries;
39
+ try {
40
+ entries = fs.readdirSync(bundledSkillsDir, { withFileTypes: true });
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ for (const entry of entries) {
46
+ if (!entry.isDirectory())
47
+ continue;
48
+ const templatePath = path.join(bundledSkillsDir, entry.name, "cron-template.json");
49
+ try {
50
+ const raw = fs.readFileSync(templatePath, "utf-8");
51
+ const parsed = JSON.parse(raw);
52
+ if (typeof parsed.templateId === "string" && parsed.templateId.trim()) {
53
+ templates.push(parsed);
54
+ }
55
+ }
56
+ catch {
57
+ // No template or invalid JSON — skip silently.
58
+ }
59
+ }
60
+ return templates;
61
+ }
62
+ // ---------------------------------------------------------------------------
63
+ // Seeding function — create cron jobs from un-seeded templates
64
+ // ---------------------------------------------------------------------------
65
+ export async function seedPreloadedCronJobs(params) {
66
+ const { bundledSkillsDir, trackerPath, cronService, defaultAccountId } = params;
67
+ const templates = loadCronTemplatesFromBundledSkills(bundledSkillsDir);
68
+ if (templates.length === 0)
69
+ return 0;
70
+ const tracker = await loadSeedTracker(trackerPath);
71
+ let seeded = 0;
72
+ for (const template of templates) {
73
+ if (isTemplateSeeded(tracker, template.templateId))
74
+ continue;
75
+ const job = await cronService.add({
76
+ name: template.name,
77
+ description: template.description,
78
+ enabled: template.enabled,
79
+ agentId: template.agentId,
80
+ accountId: defaultAccountId,
81
+ schedule: template.schedule,
82
+ sessionTarget: template.sessionTarget,
83
+ wakeMode: template.wakeMode,
84
+ payload: template.payload,
85
+ isolation: template.isolation,
86
+ });
87
+ markTemplateSeeded(tracker, template.templateId, job.id);
88
+ await saveSeedTracker(trackerPath, tracker);
89
+ seeded++;
90
+ }
91
+ return seeded;
92
+ }
@@ -1,39 +1,91 @@
1
1
  /**
2
- * Lightweight Resend email sender — single HTTP POST, no SDK dependency.
2
+ * Lightweight Brevo email sender — single HTTP POST, no SDK dependency.
3
+ *
4
+ * The "from" address is auto-detected from Brevo's senders API when not
5
+ * explicitly set in config. Users only need to provide an API key.
3
6
  */
4
7
  import { loadConfig } from "../../config/config.js";
5
8
  /**
6
- * Read email credentials from `publicChat.email` in config.
7
- * Returns null if any required field is missing.
9
+ * Sync check: is a Brevo API key configured?
10
+ * Used by detectOtpChannels() for capability reporting does not resolve the
11
+ * "from" address, just confirms the key exists.
8
12
  */
9
- export function resolveEmailCredentials() {
13
+ export function hasEmailApiKey() {
14
+ const cfg = loadConfig();
15
+ return !!cfg.publicChat?.email?.apiKey;
16
+ }
17
+ /** Cached verified sender — avoids hitting the API on every email. */
18
+ let cachedSender = null;
19
+ /**
20
+ * Query Brevo for the first active sender address.
21
+ * Returns the sender email or null if none exist.
22
+ */
23
+ async function fetchVerifiedSender(apiKey) {
24
+ try {
25
+ const res = await fetch("https://api.brevo.com/v3/senders", {
26
+ headers: {
27
+ "api-key": apiKey,
28
+ Accept: "application/json",
29
+ },
30
+ });
31
+ if (!res.ok)
32
+ return null;
33
+ const json = (await res.json());
34
+ const active = json.senders?.find((s) => s.active && s.email);
35
+ return active?.email ?? json.senders?.[0]?.email ?? null;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * Resolve email credentials from config.
43
+ *
44
+ * Only `publicChat.email.apiKey` is required. The "from" address is resolved
45
+ * in order: explicit config value → cached sender → live Brevo API lookup.
46
+ * Returns null if no API key is configured.
47
+ */
48
+ export async function resolveEmailCredentials() {
10
49
  const cfg = loadConfig();
11
50
  const email = cfg.publicChat?.email;
12
- if (!email?.apiKey || !email.from)
51
+ if (!email?.apiKey)
52
+ return null;
53
+ // Explicit from address in config — use it directly
54
+ if (email.from)
55
+ return { apiKey: email.apiKey, from: email.from };
56
+ // Cached sender for this API key
57
+ if (cachedSender && cachedSender.apiKey === email.apiKey) {
58
+ return { apiKey: email.apiKey, from: cachedSender.from };
59
+ }
60
+ // Auto-detect from Brevo senders API
61
+ const sender = await fetchVerifiedSender(email.apiKey);
62
+ if (!sender)
13
63
  return null;
14
- return { apiKey: email.apiKey, from: email.from };
64
+ cachedSender = { apiKey: email.apiKey, from: sender };
65
+ return { apiKey: email.apiKey, from: sender };
15
66
  }
16
67
  /**
17
- * Send an email via the Resend REST API.
68
+ * Send an email via the Brevo transactional email API.
18
69
  */
19
70
  export async function sendEmail(to, subject, body, creds) {
20
- const res = await fetch("https://api.resend.com/emails", {
71
+ const res = await fetch("https://api.brevo.com/v3/smtp/email", {
21
72
  method: "POST",
22
73
  headers: {
23
- Authorization: `Bearer ${creds.apiKey}`,
74
+ "api-key": creds.apiKey,
24
75
  "Content-Type": "application/json",
76
+ Accept: "application/json",
25
77
  },
26
78
  body: JSON.stringify({
27
- from: creds.from,
28
- to: [to],
79
+ sender: { email: creds.from },
80
+ to: [{ email: to }],
29
81
  subject,
30
- text: body,
82
+ textContent: body,
31
83
  }),
32
84
  });
33
85
  if (!res.ok) {
34
86
  const text = await res.text().catch(() => "");
35
- throw new Error(`Resend email failed (${res.status}): ${text}`);
87
+ throw new Error(`Brevo email failed (${res.status}): ${text}`);
36
88
  }
37
89
  const json = (await res.json());
38
- return { id: json.id ?? "unknown" };
90
+ return { id: json.messageId ?? "unknown" };
39
91
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Deliver OTP verification codes via WhatsApp (primary), SMS (fallback), or email (Resend).
2
+ * Deliver OTP verification codes via WhatsApp (primary), SMS (fallback), or email (Brevo).
3
3
  */
4
4
  import { getActiveWebListener } from "../../web/active-listener.js";
5
5
  import { sendMessageWhatsApp } from "../../web/outbound.js";
6
6
  import { resolveSmsCredentials, sendSms } from "./deliver-sms.js";
7
- import { resolveEmailCredentials, sendEmail } from "./deliver-email.js";
7
+ import { hasEmailApiKey, resolveEmailCredentials, sendEmail } from "./deliver-email.js";
8
8
  /** Detect whether an identifier is an email address. */
9
9
  function isEmail(identifier) {
10
10
  return identifier.includes("@");
@@ -19,23 +19,23 @@ export function detectOtpChannels(whatsappAccountId) {
19
19
  channels.push("whatsapp");
20
20
  if (resolveSmsCredentials())
21
21
  channels.push("sms");
22
- if (resolveEmailCredentials())
22
+ if (hasEmailApiKey())
23
23
  channels.push("email");
24
24
  return channels;
25
25
  }
26
26
  /**
27
27
  * Deliver a verification code to the given identifier.
28
28
  *
29
- * Email identifiers (containing @) are sent via Resend.
29
+ * Email identifiers (containing @) are sent via Brevo.
30
30
  * Phone identifiers use the existing WhatsApp -> SMS fallback chain.
31
31
  */
32
32
  export async function deliverOtp(identifier, code, accountId) {
33
33
  const message = `Your verification code is: ${code}`;
34
- // Email identifiers — deliver via Resend only
34
+ // Email identifiers — deliver via Brevo only
35
35
  if (isEmail(identifier)) {
36
- const emailCreds = resolveEmailCredentials();
36
+ const emailCreds = await resolveEmailCredentials();
37
37
  if (!emailCreds) {
38
- throw new Error("Email verification is not configured. Set publicChat.email credentials.");
38
+ throw new Error("Email verification is not configured. Add a Brevo API key and verify a sender in the Brevo console.");
39
39
  }
40
40
  await sendEmail(identifier, "Your verification code", message, emailCreds);
41
41
  return { channel: "email" };
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Endpoints:
7
7
  * POST /session — create an anonymous session (returns sessionKey)
8
- * POST /otp/request — request an OTP code (phone via WhatsApp/SMS, or email via Resend)
8
+ * POST /otp/request — request an OTP code (phone via WhatsApp/SMS, or email via Brevo)
9
9
  * POST /otp/verify — verify OTP and get a verified session (returns sessionKey)
10
10
  * POST /chat — send a message, receive the agent reply (sync or SSE stream)
11
11
  * GET /chat/history — retrieve past messages for a session
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Authentication mirrors the public chat widget: anonymous sessions use a
16
16
  * client-provided identity string; verified sessions use OTP sent via phone
17
- * (WhatsApp with SMS fallback) or email (Resend). The sessionKey returned
17
+ * (WhatsApp with SMS fallback) or email (Brevo). The sessionKey returned
18
18
  * from /session or /otp/verify is passed via the X-Session-Key header on
19
19
  * subsequent requests.
20
20
  *
@@ -26,7 +26,7 @@ const PROVIDER_CATALOG = [
26
26
  { id: "hume", name: "Hume", category: "Voice" },
27
27
  { id: "brave", name: "Brave", category: "Web Search" },
28
28
  { id: "elevenlabs", name: "ElevenLabs", category: "Voice" },
29
- { id: "resend", name: "Resend", category: "Email" },
29
+ { id: "brevo", name: "Brevo", category: "Email" },
30
30
  ];
31
31
  const VALID_PROVIDER_IDS = new Set(PROVIDER_CATALOG.map((p) => p.id));
32
32
  export const apikeysHandlers = {
@@ -31,7 +31,7 @@ function validateAccountId(raw) {
31
31
  export const publicChatHandlers = {
32
32
  /**
33
33
  * Request an OTP code — sends a 6-digit code via WhatsApp/SMS (phone) or
34
- * Resend (email).
34
+ * Brevo (email).
35
35
  * Params: { identifier: string } or { phone: string } (backward compat)
36
36
  */
37
37
  "public.otp.request": async ({ params, respond, context }) => {
@@ -0,0 +1,19 @@
1
+ import { ErrorCodes, errorShape } from "../protocol/index.js";
2
+ import { renderQrPngBase64 } from "../../web/qr-image.js";
3
+ export const qrHandlers = {
4
+ "qr.generate": async ({ params, respond, context }) => {
5
+ const url = typeof params.url === "string" ? params.url : "";
6
+ if (!url) {
7
+ respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "missing url param"));
8
+ return;
9
+ }
10
+ try {
11
+ const base64 = await renderQrPngBase64(url, { scale: 6, marginModules: 2 });
12
+ respond(true, { dataUrl: `data:image/png;base64,${base64}` });
13
+ }
14
+ catch (err) {
15
+ context.logGateway.warn(`qr.generate failed: ${err instanceof Error ? err.message : String(err)}`);
16
+ respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "failed to generate QR code"));
17
+ }
18
+ },
19
+ };
@@ -33,6 +33,7 @@ import { voicewakeHandlers } from "./server-methods/voicewake.js";
33
33
  import { webHandlers } from "./server-methods/web.js";
34
34
  import { wizardHandlers } from "./server-methods/wizard.js";
35
35
  import { publicChatHandlers } from "./server-methods/public-chat.js";
36
+ import { qrHandlers } from "./server-methods/qr.js";
36
37
  import { tailscaleHandlers } from "./server-methods/tailscale.js";
37
38
  import { networkHandlers } from "./server-methods/network.js";
38
39
  import { wifiHandlers } from "./server-methods/wifi.js";
@@ -105,6 +106,7 @@ const READ_METHODS = new Set([
105
106
  "workspaces.scan",
106
107
  "memory.status",
107
108
  "memory.audit",
109
+ "qr.generate",
108
110
  ]);
109
111
  const WRITE_METHODS = new Set([
110
112
  "send",
@@ -240,6 +242,7 @@ export const coreGatewayHandlers = {
240
242
  ...workspacesHandlers,
241
243
  ...brandHandlers,
242
244
  ...publicChatHandlers,
245
+ ...qrHandlers,
243
246
  ...networkHandlers,
244
247
  ...tailscaleHandlers,
245
248
  ...wifiHandlers,
@@ -34,6 +34,9 @@ import { createExecApprovalForwarder } from "../infra/exec-approval-forwarder.js
34
34
  import { createChannelManager } from "./server-channels.js";
35
35
  import { createAgentEventHandler } from "./server-chat.js";
36
36
  import { createGatewayCloseHandler } from "./server-close.js";
37
+ import { resolveBundledSkillsDir } from "../agents/skills/bundled-dir.js";
38
+ import { DEFAULT_SEED_TRACKER_PATH, seedPreloadedCronJobs } from "../cron/preloaded.js";
39
+ import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
37
40
  import { buildGatewayCronService } from "./server-cron.js";
38
41
  import { applyGatewayLaneConcurrency } from "./server-lanes.js";
39
42
  import { startGatewayMaintenanceTimers } from "./server-maintenance.js";
@@ -395,7 +398,29 @@ export async function startGatewayServer(port = 18789, opts = {}) {
395
398
  broadcast("notification", evt);
396
399
  });
397
400
  let heartbeatRunner = startHeartbeatRunner({ cfg: cfgAtStart });
398
- void cron.start().catch((err) => logCron.error(`failed to start: ${String(err)}`));
401
+ // Start cron, then seed preloaded cron jobs from bundled skills (one-time per template).
402
+ void cron
403
+ .start()
404
+ .then(async () => {
405
+ const bundledSkillsDir = resolveBundledSkillsDir();
406
+ if (!bundledSkillsDir)
407
+ return;
408
+ try {
409
+ const seeded = await seedPreloadedCronJobs({
410
+ bundledSkillsDir,
411
+ trackerPath: DEFAULT_SEED_TRACKER_PATH,
412
+ cronService: cron,
413
+ defaultAccountId: DEFAULT_ACCOUNT_ID,
414
+ });
415
+ if (seeded > 0) {
416
+ logCron.info(`cron: seeded ${seeded} preloaded job(s)`);
417
+ }
418
+ }
419
+ catch (err) {
420
+ logCron.error(`cron: failed to seed preloaded jobs: ${String(err)}`);
421
+ }
422
+ })
423
+ .catch((err) => logCron.error(`failed to start: ${String(err)}`));
399
424
  const execApprovalManager = new ExecApprovalManager();
400
425
  const execApprovalForwarder = createExecApprovalForwarder();
401
426
  const execApprovalHandlers = createExecApprovalHandlers(execApprovalManager, {
@@ -346,6 +346,13 @@ export async function runOnboardingWizard(opts, runtime = defaultRuntime, prompt
346
346
  }
347
347
  // Setup hooks (session memory on /new)
348
348
  nextConfig = await setupInternalHooks(nextConfig, runtime, prompter);
349
+ // Inform user about the preloaded Daily Strategic Review cron job.
350
+ await prompter.note([
351
+ "A Daily Strategic Review is pre-configured (paused).",
352
+ "It scans your conversations, audits your materials, and suggests improvements.",
353
+ "",
354
+ "To enable it: Control Panel → Advanced → Events → enable 🔭 Daily Strategic Review.",
355
+ ].join("\n"), "Daily Strategic Review");
349
356
  nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
350
357
  await writeConfigFile(nextConfig);
351
358
  await finalizeOnboardingWizard({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.10.0",
3
+ "version": "1.11.1",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -84,8 +84,6 @@
84
84
  "prepack": "pnpm build && pnpm ui:build",
85
85
  "docs:list": "node scripts/docs-list.js",
86
86
  "docs:bin": "node scripts/build-docs-list.mjs",
87
- "docs:dev": "cd docs && mint dev",
88
- "docs:build": "cd docs && pnpm dlx --reporter append-only mint broken-links",
89
87
  "build": "tsc -p tsconfig.json && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts",
90
88
  "plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
91
89
  "release:check": "node --import tsx scripts/release-check.ts",
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: brevo
3
+ description: Guide users through getting a free Brevo API key for email OTP verification.
4
+ metadata: {"taskmaster":{"emoji":"🔑"}}
5
+ ---
6
+
7
+ # Brevo Setup
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.
10
+
11
+ ## When to activate
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
16
+
17
+ ## What it unlocks
18
+
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
22
+
23
+ ## References
24
+
25
+ | Task | When to use | Reference |
26
+ |------|-------------|-----------|
27
+ | Guided setup | User wants help getting the key | `references/browser-setup.md` |
28
+
29
+ Load the reference and follow its instructions.
@@ -0,0 +1,161 @@
1
+ # Brevo Email — Guided Setup
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.
4
+
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
+
7
+ ---
8
+
9
+ ## Prerequisites
10
+
11
+ - User has an email address they want OTP emails to come from (e.g. their business email)
12
+ - User has a mobile phone number (required by Brevo for account activation)
13
+ - Chat channel for sending instructions
14
+
15
+ ---
16
+
17
+ ## Step 1: Explain
18
+
19
+ Tell the user what Brevo is and why they need it:
20
+
21
+ > "To send verification codes by email, we need a Brevo account. It's completely free — you get 300 emails a day, which is way more than enough for verification codes. No credit card required.
22
+ >
23
+ > I'll walk you through the setup step by step. The whole thing takes about 5 minutes."
24
+
25
+ ## Step 2: Create a Brevo account
26
+
27
+ > "Go to **brevo.com** in your browser and click **Sign Up Free** in the top-right corner.
28
+ >
29
+ > You'll need to enter:
30
+ > - Your **email address**
31
+ > - A **password**
32
+ >
33
+ > You can also sign up with Google or Apple if that's easier.
34
+ >
35
+ > After clicking **Create an account**, Brevo will send a confirmation email. Click the link in it to verify your account.
36
+ >
37
+ > **Next, Brevo will ask for your mobile phone number.** This is for account activation only — they send a text to verify you're a real person. Enter your number and the code they text you.
38
+ >
39
+ > Let me know when you're past that and logged in."
40
+
41
+ Wait for the user to confirm they've completed signup and mobile verification.
42
+
43
+ ### Getting past the onboarding
44
+
45
+ After signup, Brevo lands on a **"Getting Started"** page that tries to walk you through creating contacts, campaigns, and other marketing features. The user does NOT need any of this.
46
+
47
+ If the user mentions being on a "Getting Started" page, or asks about contacts/campaigns/templates:
48
+
49
+ > "You can skip all of that — it's for email marketing campaigns, which we don't need. We just need two things from Brevo: a verified sender address and an API key. Both are in the account settings. Let me show you where."
50
+
51
+ If the user is asked onboarding questions (company name, team size, etc.):
52
+
53
+ > "Just fill in your business name and pick whatever options feel right — those are for Brevo's records and don't affect anything we're doing."
54
+
55
+ ## Step 3: Verify a sender email address
56
+
57
+ This determines what email address OTP codes come from. Brevo sends a 6-digit code to the email — no DNS records needed.
58
+
59
+ **Navigation: Settings page (gear icon) → Organization settings → Senders, domains, IPs.**
60
+
61
+ > "Now we need to verify the email address that verification codes will be sent from. This is what your visitors will see in their inbox.
62
+ >
63
+ > 1. In the **top-right toolbar**, click the **gear icon** (⚙️) to open **Settings**
64
+ > 2. In the Settings left sidebar, under **Organization settings**, click **Senders, domains, IPs**
65
+ > 3. Click the **Add sender** button (top-right of the page)
66
+ > 4. Fill in:
67
+ > - **From Name** — your business name (e.g. "Acme Plumbing")
68
+ > - **From Email** — the email you want codes to come from (your business email works)
69
+ > 5. Click **Add sender**
70
+ > 6. A popup will appear saying **"Authenticate your domain now?"** — you have two options:
71
+ > - **"Do it later"** — skip domain authentication. Sender verification alone is enough to send verification codes. Choose this if you don't own the email domain or want the simplest setup.
72
+ > - **"Authenticate domain"** — lets Brevo's partner (Entri) automatically add DKIM and DMARC records to your domain. This improves email deliverability and makes your emails fully compliant with Gmail/Yahoo/Microsoft requirements. Choose this if you own the domain and want more robust delivery. Entri will ask you to log in to your domain provider and it handles the DNS records automatically — no technical knowledge needed.
73
+ >
74
+ > Either way, Brevo will send a **6-digit code** to your email address. Check your inbox (and spam folder), enter the code, and click **Verify**. Let me know when it's done."
75
+
76
+ If the user says they can't find it:
77
+
78
+ > "In the top-right of the Brevo dashboard, there's a toolbar with several small icons. Click the **gear icon** (⚙️) — it should be between the help icon and the notification bell. That opens the Settings page. Then look in the left sidebar for **Senders, domains, IPs** under the **Organization settings** section."
79
+
80
+ **Do NOT direct users to "Transactional" in the main dashboard sidebar or to "Campaigns".** Those are marketing features, not what they need.
81
+
82
+ Wait for the user to confirm.
83
+
84
+ ## Step 4: Get an API key
85
+
86
+ **Navigation: same Settings page → Organization settings → SMTP & API.**
87
+
88
+ > "Almost done. Now we need an API key:
89
+ >
90
+ > 1. You should still be on the Settings page. In the left sidebar under **Organization settings**, click **SMTP & API**
91
+ > 2. Click the **API keys & MCP** tab (not the SMTP tab)
92
+ > 3. Click **Generate a new API key**
93
+ > 4. Give it a name like **Taskmaster**
94
+ > 5. Click **Generate**
95
+ >
96
+ > You'll see the API key — it starts with **xkeysib-** and is quite long. **Copy it now** — Brevo only shows it once. If you lose it, you'll need to generate a new one. Send me the key."
97
+
98
+ If the user navigated away from Settings:
99
+
100
+ > "Click the **gear icon** (⚙️) in the top-right toolbar to get back to Settings, then click **SMTP & API** in the left sidebar."
101
+
102
+ Wait for the user to send the API key.
103
+
104
+ Verify it looks right (starts with `xkeysib-`, usually 60+ characters). If not:
105
+
106
+ > "That doesn't look like a Brevo API key — it should start with **xkeysib-** and be about 64 characters long. Can you check you copied the full key?"
107
+
108
+ ## Step 5: Save to Taskmaster
109
+
110
+ Once you have the API key, store it using the `api_keys` tool:
111
+
112
+ ```
113
+ api_keys({ action: "set", provider: "brevo", apiKey: "<the key>" })
114
+ ```
115
+
116
+ The key is applied immediately — no restart needed. Confirm to the user:
117
+
118
+ > "Done — I've saved your Brevo API key. Email verification is now enabled. When visitors use your public chat, they can verify their identity with an email code. The code will come from the address you verified in Brevo."
119
+
120
+ That's it. No further configuration is needed in Taskmaster — the verified sender address is detected automatically from Brevo.
121
+
122
+ ---
123
+
124
+ ## If the user already has a Brevo account
125
+
126
+ Check if they have a verified sender:
127
+
128
+ > "Do you already have a verified sender in Brevo? If so, I just need your API key (starts with **xkeysib-**)."
129
+
130
+ If yes, skip to Step 4. If they're unsure:
131
+
132
+ > "Click the **gear icon** (⚙️) in the top-right toolbar to open Settings. Then click **Senders, domains, IPs** in the left sidebar. Check if there's a verified sender listed. If not, we'll set one up."
133
+
134
+ ---
135
+
136
+ ## Troubleshooting
137
+
138
+ | Problem | Solution |
139
+ |---------|----------|
140
+ | Can't find Senders or SMTP & API | Click the **gear icon** (⚙️) in the top-right toolbar to open the Settings page. Both options are in the left sidebar under **Organization settings**. |
141
+ | Landed on "Getting Started" or campaign builder | Ignore it — click the **gear icon** (⚙️) in the top-right toolbar to go straight to Settings. |
142
+ | "Transactional" page shows SMTP settings | Wrong place — that's in the main dashboard, not Settings. Click the **gear icon** (⚙️) in the top-right toolbar, then **SMTP & API** in the Settings sidebar. |
143
+ | Clicked "Authenticate domain" by mistake | If you end up on a Domains page or see DNS/Entri prompts, click **Cancel** or close the modal, then click the **Senders** tab to get back to sender verification. |
144
+ | Mobile verification code not arriving | Wait a minute and try again. Check you entered the right country code. Some VoIP numbers don't work — use a real mobile number. |
145
+ | Can't find the free plan | On brevo.com/pricing, the free tier is the green banner at the top of the page ("Free forever, no credit card needed"). Click **Sign up free** there. |
146
+
147
+ ---
148
+
149
+ ## Common questions
150
+
151
+ | Question | Answer |
152
+ |----------|--------|
153
+ | "Is it free?" | Yes — 300 emails per day, forever. No credit card required. For verification codes, that's more than enough. |
154
+ | "Why do they need my phone number?" | Brevo requires a mobile number for account activation — it's a one-time verification to prevent spam accounts. They won't call or text you after that. |
155
+ | "Can I use my Gmail/Outlook address?" | Yes, as long as you verify it by entering the 6-digit code Brevo sends to it. |
156
+ | "Will emails go to spam?" | OTP emails are solicited (the user just clicked 'send me a code'), so they almost always reach the inbox. If a visitor doesn't see it, tell them to check spam. |
157
+ | "I lost my API key" | Click the gear icon (⚙️) in the top-right toolbar → SMTP & API → API keys & MCP tab, and generate a new one. |
158
+ | "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
+ | "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
+ | "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. |
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: strategic-proactivity
3
+ description: "Periodic strategic review — scans recent conversations, audits materials against reality, identifies gaps and opportunities, drafts improvements, and sends a prioritised summary to admins. Triggered by cron job or on-demand."
4
+ metadata: {"taskmaster":{"emoji":"🔭"}}
5
+ ---
6
+
7
+ # Strategic Proactivity
8
+
9
+ ## When to Activate
10
+
11
+ - Triggered by a scheduled review (cron job)
12
+ - Admin asks to review conversations, materials, opportunities, or gaps
13
+ - Admin asks "what needs my attention?", "what should we be working on?", or similar strategic questions
14
+
15
+ ## Behaviour
16
+
17
+ Load `references/daily-review.md` for the full review protocol and action framework.
18
+
19
+ **Core principle:** Surface insights and draft improvements — don't just report. Always send findings to admin for approval before publishing anything.
20
+
21
+ ### Rules
22
+
23
+ 1. **Scan recent conversations** — all channels, all agent sessions (public, admin, group)
24
+ 2. **Compare against existing materials** — search memory for current documents, then check whether they still reflect reality
25
+ 3. **Identify gaps and opportunities** — questions that couldn't be answered, recurring themes, new verticals, unaddressed objections
26
+ 4. **Draft improvements** — don't just flag problems, write the fix
27
+ 5. **Track opportunities** — leads, partnership signals, market intel, feature requests
28
+ 6. **Send a concise summary to admins** — bullet points, prioritised by impact, with drafts attached where relevant
29
+
30
+ ### Output Format
31
+
32
+ The summary should follow this structure:
33
+
34
+ - **New Insights** — what emerged from conversations
35
+ - **Drafts Ready** — what you've written or updated (saved to memory for review)
36
+ - **Opportunities** — leads, partnerships, verticals worth pursuing
37
+ - **Gaps Found** — materials that need updating or creating
38
+ - **No Action Needed** — if genuinely nothing surfaced, say so briefly
39
+
40
+ ### Constraints
41
+
42
+ - Keep total execution under 2 minutes
43
+ - Don't publish anything to shared/ or public/ without admin approval
44
+ - Save all drafts to memory for admin review before acting on them
45
+ - Be honest — if nothing meaningful surfaced, say "quiet period" rather than padding the report