@rubytech/taskmaster 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/skills/workspace.js +2 -0
- package/dist/agents/tools/apikeys-tool.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/config/defaults.js +1 -1
- package/dist/control-ui/assets/{index-n_TWBBTg.css → index-CpaEIgQy.css} +1 -1
- package/dist/control-ui/assets/{index-CTFFQkyj.js → index-YAjVyXqJ.js} +705 -704
- package/dist/control-ui/assets/index-YAjVyXqJ.js.map +1 -0
- package/dist/control-ui/index.html +2 -2
- package/dist/cron/preloaded.js +92 -0
- package/dist/gateway/public-chat/deliver-email.js +66 -14
- package/dist/gateway/public-chat/deliver-otp.js +7 -7
- package/dist/gateway/public-chat-api.js +2 -2
- package/dist/gateway/server-methods/apikeys.js +1 -1
- package/dist/gateway/server-methods/public-chat.js +1 -1
- package/dist/gateway/server-methods/qr.js +19 -0
- package/dist/gateway/server-methods.js +3 -0
- package/dist/gateway/server.impl.js +26 -1
- package/dist/wizard/onboarding.js +7 -0
- package/package.json +1 -3
- package/skills/brevo/SKILL.md +29 -0
- package/skills/brevo/references/browser-setup.md +113 -0
- package/skills/strategic-proactivity/SKILL.md +45 -0
- package/skills/strategic-proactivity/cron-template.json +21 -0
- package/skills/strategic-proactivity/references/daily-review.md +64 -0
- package/taskmaster-docs/USER-GUIDE.md +49 -4
- package/dist/control-ui/assets/index-CTFFQkyj.js.map +0 -1
|
@@ -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-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
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
|
|
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
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
64
|
+
cachedSender = { apiKey: email.apiKey, from: sender };
|
|
65
|
+
return { apiKey: email.apiKey, from: sender };
|
|
15
66
|
}
|
|
16
67
|
/**
|
|
17
|
-
* Send an email via the
|
|
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.
|
|
71
|
+
const res = await fetch("https://api.brevo.com/v3/smtp/email", {
|
|
21
72
|
method: "POST",
|
|
22
73
|
headers: {
|
|
23
|
-
|
|
74
|
+
"api-key": creds.apiKey,
|
|
24
75
|
"Content-Type": "application/json",
|
|
76
|
+
Accept: "application/json",
|
|
25
77
|
},
|
|
26
78
|
body: JSON.stringify({
|
|
27
|
-
|
|
28
|
-
to: [to],
|
|
79
|
+
sender: { email: creds.from },
|
|
80
|
+
to: [{ email: to }],
|
|
29
81
|
subject,
|
|
30
|
-
|
|
82
|
+
textContent: body,
|
|
31
83
|
}),
|
|
32
84
|
});
|
|
33
85
|
if (!res.ok) {
|
|
34
86
|
const text = await res.text().catch(() => "");
|
|
35
|
-
throw new Error(`
|
|
87
|
+
throw new Error(`Brevo email failed (${res.status}): ${text}`);
|
|
36
88
|
}
|
|
37
89
|
const json = (await res.json());
|
|
38
|
-
return { id: json.
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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 (
|
|
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: "
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.11.0",
|
|
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,113 @@
|
|
|
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
|
+
---
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- User has an email address they want OTP emails to come from (e.g. their business email)
|
|
10
|
+
- Chat channel for sending instructions
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Step 1: Explain
|
|
15
|
+
|
|
16
|
+
Tell the user what Brevo is and why they need it:
|
|
17
|
+
|
|
18
|
+
> "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.
|
|
19
|
+
>
|
|
20
|
+
> I'll walk you through the setup step by step. The whole thing takes about 5 minutes."
|
|
21
|
+
|
|
22
|
+
## Step 2: Create a Brevo account
|
|
23
|
+
|
|
24
|
+
> "Go to **brevo.com** in your browser and click **Sign up free**.
|
|
25
|
+
>
|
|
26
|
+
> You'll need to enter:
|
|
27
|
+
> - Your email address
|
|
28
|
+
> - A password
|
|
29
|
+
>
|
|
30
|
+
> Brevo will send a confirmation email. Click the link in it to verify your account. Let me know when you're logged in."
|
|
31
|
+
|
|
32
|
+
Wait for the user to confirm they've completed signup.
|
|
33
|
+
|
|
34
|
+
If the user is asked onboarding questions (company name, team size, etc.), tell them:
|
|
35
|
+
|
|
36
|
+
> "Just fill in your business name and pick whatever options feel right — those are for Brevo's records and don't affect anything."
|
|
37
|
+
|
|
38
|
+
## Step 3: Verify a sender email address
|
|
39
|
+
|
|
40
|
+
This determines what email address OTP codes come from. Brevo sends a 6-digit code to the email — no DNS records needed.
|
|
41
|
+
|
|
42
|
+
> "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.
|
|
43
|
+
>
|
|
44
|
+
> 1. In the Brevo dashboard, click your **name/icon** in the top-right
|
|
45
|
+
> 2. Click **Senders, Domains & Dedicated IPs** (or go to **Settings → Senders**)
|
|
46
|
+
> 3. Click **Add a sender**
|
|
47
|
+
> 4. Fill in:
|
|
48
|
+
> - **From Name** — your business name (e.g. "Acme Plumbing")
|
|
49
|
+
> - **From Email** — the email you want codes to come from (your business email works)
|
|
50
|
+
> 5. Click **Save**
|
|
51
|
+
>
|
|
52
|
+
> Brevo will send a **6-digit code** to that email address. Check your inbox (and spam folder), enter the code, and click **Verify**. Let me know when it's done."
|
|
53
|
+
|
|
54
|
+
Wait for the user to confirm.
|
|
55
|
+
|
|
56
|
+
## Step 4: Get an API key
|
|
57
|
+
|
|
58
|
+
> "Almost done. Now we need an API key:
|
|
59
|
+
>
|
|
60
|
+
> 1. Click your **name/icon** in the top-right again
|
|
61
|
+
> 2. Click **SMTP & API**
|
|
62
|
+
> 3. Click the **API Keys** tab
|
|
63
|
+
> 4. Click **Generate a new API key**
|
|
64
|
+
> 5. Give it a name like **Taskmaster**
|
|
65
|
+
> 6. Click **Generate**
|
|
66
|
+
>
|
|
67
|
+
> You'll see the API key — it starts with **xkeysib-** and is quite long. **Copy it now**. Send it to me."
|
|
68
|
+
|
|
69
|
+
Wait for the user to send the API key.
|
|
70
|
+
|
|
71
|
+
Verify it looks right (starts with `xkeysib-`, usually 60+ characters). If not:
|
|
72
|
+
|
|
73
|
+
> "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?"
|
|
74
|
+
|
|
75
|
+
## Step 5: Save to Taskmaster
|
|
76
|
+
|
|
77
|
+
Once you have the API key, store it using the `api_keys` tool:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
api_keys({ action: "set", provider: "brevo", apiKey: "<the key>" })
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The key is applied immediately — no restart needed. Confirm to the user:
|
|
84
|
+
|
|
85
|
+
> "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."
|
|
86
|
+
|
|
87
|
+
That's it. No further configuration is needed in Taskmaster — the verified sender address is detected automatically from Brevo.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## If the user already has a Brevo account
|
|
92
|
+
|
|
93
|
+
Check if they have a verified sender:
|
|
94
|
+
|
|
95
|
+
> "Do you already have a verified sender in Brevo? If so, I just need your API key (starts with **xkeysib-**)."
|
|
96
|
+
|
|
97
|
+
If yes, skip to Step 4. If they're unsure:
|
|
98
|
+
|
|
99
|
+
> "Go to **Settings → Senders** in Brevo and check if there's a verified sender listed. If not, we'll set one up."
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Common questions
|
|
104
|
+
|
|
105
|
+
| Question | Answer |
|
|
106
|
+
|----------|--------|
|
|
107
|
+
| "Is it free?" | Yes — 300 emails per day, forever. No credit card required. For verification codes, that's more than enough. |
|
|
108
|
+
| "Can I use my Gmail/Outlook address?" | Yes, as long as you verify it by entering the 6-digit code Brevo sends to it. |
|
|
109
|
+
| "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. |
|
|
110
|
+
| "I lost my API key" | Go to SMTP & API → API Keys in Brevo and generate a new one. |
|
|
111
|
+
| "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. |
|
|
112
|
+
| "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. |
|
|
113
|
+
| "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. |
|
|
@@ -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
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"templateId": "strategic-proactivity-daily-review",
|
|
3
|
+
"name": "🔭 Daily Strategic Review",
|
|
4
|
+
"description": "Scans recent conversations, audits materials, identifies opportunities and gaps",
|
|
5
|
+
"enabled": false,
|
|
6
|
+
"agentId": "admin",
|
|
7
|
+
"schedule": { "kind": "cron", "expr": "0 7 * * *" },
|
|
8
|
+
"sessionTarget": "isolated",
|
|
9
|
+
"wakeMode": "next-heartbeat",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "agentTurn",
|
|
12
|
+
"message": "Run the daily strategic proactivity review. Use the strategic-proactivity skill.",
|
|
13
|
+
"deliver": true,
|
|
14
|
+
"to": "admins",
|
|
15
|
+
"bestEffortDeliver": true
|
|
16
|
+
},
|
|
17
|
+
"isolation": {
|
|
18
|
+
"postToMainMode": "summary",
|
|
19
|
+
"postToMainPrefix": "🔭 Daily Review"
|
|
20
|
+
}
|
|
21
|
+
}
|