@rubytech/taskmaster 1.12.0 → 1.12.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,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-DpMaqt-b.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-4h8fLLNN.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-CpaEIgQy.css">
11
11
  </head>
12
12
  <body>
@@ -527,7 +527,9 @@ export function handlePublicChatHttpRequest(req, res, opts) {
527
527
  const avatarValue = resolvedAvatar && /^(https?:\/\/|data:image\/|\/)/i.test(resolvedAvatar)
528
528
  ? resolvedAvatar
529
529
  : undefined;
530
- const authMode = config.publicChat.auth ?? "anonymous";
530
+ const authMode = (config.publicChat?.accountSettings?.[accountId]?.auth ??
531
+ config.publicChat?.auth ??
532
+ "anonymous");
531
533
  const cookieTtlDays = config.publicChat.cookieTtlDays ?? 30;
532
534
  const brandName = config.ui?.brand?.name;
533
535
  const brandIconUrl = resolveBrandIconUrl(config.ui?.brand?.icon, root);
@@ -570,7 +572,7 @@ export function handlePublicChatHttpRequest(req, res, opts) {
570
572
  // Inject public-chat globals before </head>
571
573
  const publicScript = `<script>` +
572
574
  `window.__TASKMASTER_PUBLIC_CHAT__=true;` +
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 })};` +
575
+ `window.__TASKMASTER_PUBLIC_CHAT_CONFIG__=${JSON.stringify({ accountId, auth: authMode, cookieTtlDays, otpChannels, verifyMethods: normalizeVerifyMethods(config.publicChat?.accountSettings?.[accountId]?.verifyMethods ?? config.publicChat?.verifyMethods ?? ["whatsapp", "sms"]), greeting: config.publicChat?.greetings?.[accountId] || undefined })};` +
574
576
  `</script>`;
575
577
  const headClose = baseInjected.indexOf("</head>");
576
578
  const withPublic = headClose !== -1
@@ -584,24 +586,37 @@ export function handlePublicChatHttpRequest(req, res, opts) {
584
586
  /** Widget script content — self-contained JS for embedding. */
585
587
  const WIDGET_SCRIPT = `(function(){
586
588
  "use strict";
587
- var cfg={server:"",accountId:"",color:"#1a1a2e",bgColor:"#1a1a2e"};
589
+ var cfg={server:"",accountId:"",color:"",bgColor:"#1a1a2e",btnColor:""};
588
590
  var isOpen=false;
589
591
  var btn,overlay,iframe;
592
+ var SVG_NS="http://www.w3.org/2000/svg";
593
+ function svgEl(tag,attrs){var e=document.createElementNS(SVG_NS,tag);for(var k in attrs)e.setAttribute(k,attrs[k]);return e;}
594
+ function makeChatIcon(){var s=svgEl("svg",{width:"22",height:"22",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"});s.appendChild(svgEl("path",{d:"M7.9 20A9 9 0 1 0 4 16.1L2 22Z"}));return s;}
595
+ function makeCloseIcon(){var s=svgEl("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2.5","stroke-linecap":"round","stroke-linejoin":"round"});s.appendChild(svgEl("line",{x1:"18",y1:"6",x2:"6",y2:"18"}));s.appendChild(svgEl("line",{x1:"6",y1:"6",x2:"18",y2:"18"}));return s;}
596
+ function setIcon(el,fn){while(el.firstChild)el.removeChild(el.firstChild);el.appendChild(fn());}
590
597
 
591
598
  function init(opts){
592
599
  if(opts&&opts.server) cfg.server=opts.server.replace(/\\/$/,"");
593
600
  if(opts&&opts.accountId) cfg.accountId=opts.accountId;
594
- if(opts&&opts.color) cfg.color=opts.color;
601
+ if(opts&&opts.color) cfg.btnColor=opts.color;
595
602
  if(opts&&opts.bgColor) cfg.bgColor=opts.bgColor;
596
- build();
603
+ if(!cfg.btnColor){
604
+ fetch(cfg.server+"/public/api/v1/"+encodeURIComponent(cfg.accountId)+"/capabilities")
605
+ .then(function(r){return r.json()})
606
+ .then(function(d){if(d.background_color) cfg.btnColor=d.background_color; else if(d.accent_color) cfg.btnColor=d.accent_color; build();})
607
+ .catch(function(){build();});
608
+ } else {
609
+ build();
610
+ }
597
611
  }
598
612
 
599
613
  function build(){
614
+ if(!cfg.btnColor) cfg.btnColor="#1a1a2e";
600
615
  var css=document.createElement("style");
601
616
  css.textContent=[
602
617
  ".tm-widget-btn{position:fixed;bottom:20px;right:20px;width:48px;height:48px;",
603
- "border-radius:50%;background:"+cfg.color+";color:#fff;border:none;cursor:pointer;",
604
- "box-shadow:0 2px 8px rgba(0,0,0,.3);z-index:999999;font-size:22px;",
618
+ "border-radius:50%;background:"+cfg.btnColor+";color:#fff;border:none;cursor:pointer;",
619
+ "box-shadow:0 2px 8px rgba(0,0,0,.3);z-index:999999;",
605
620
  "display:flex;align-items:center;justify-content:center;transition:transform .2s}",
606
621
  ".tm-widget-btn:hover{transform:scale(1.08)}",
607
622
  ".tm-widget-overlay{position:fixed;bottom:78px;right:20px;width:400px;height:600px;",
@@ -618,7 +633,7 @@ const WIDGET_SCRIPT = `(function(){
618
633
 
619
634
  btn=document.createElement("button");
620
635
  btn.className="tm-widget-btn";
621
- btn.textContent="\\uD83D\\uDCAC";
636
+ setIcon(btn,makeChatIcon);
622
637
  btn.setAttribute("aria-label","Chat");
623
638
  btn.onclick=toggle;
624
639
  document.body.appendChild(btn);
@@ -635,7 +650,7 @@ const WIDGET_SCRIPT = `(function(){
635
650
  function toggle(){
636
651
  isOpen=!isOpen;
637
652
  overlay.classList.toggle("open",isOpen);
638
- btn.textContent=isOpen?"\\u2715":"\\uD83D\\uDCAC";
653
+ setIcon(btn,isOpen?makeCloseIcon:makeChatIcon);
639
654
  }
640
655
 
641
656
  window.Taskmaster={init:init};
@@ -45,24 +45,24 @@ async function fetchVerifiedSender(apiKey) {
45
45
  * in order: explicit config value → cached sender → live Brevo API lookup.
46
46
  * Returns null if no API key is configured.
47
47
  */
48
- export async function resolveEmailCredentials() {
48
+ export async function resolveEmailCredentials(fromName) {
49
49
  const cfg = loadConfig();
50
50
  const email = cfg.publicChat?.email;
51
51
  if (!email?.apiKey)
52
52
  return null;
53
53
  // Explicit from address in config — use it directly
54
54
  if (email.from)
55
- return { apiKey: email.apiKey, from: email.from };
55
+ return { apiKey: email.apiKey, from: email.from, fromName };
56
56
  // Cached sender for this API key
57
57
  if (cachedSender && cachedSender.apiKey === email.apiKey) {
58
- return { apiKey: email.apiKey, from: cachedSender.from };
58
+ return { apiKey: email.apiKey, from: cachedSender.from, fromName };
59
59
  }
60
60
  // Auto-detect from Brevo senders API
61
61
  const sender = await fetchVerifiedSender(email.apiKey);
62
62
  if (!sender)
63
63
  return null;
64
64
  cachedSender = { apiKey: email.apiKey, from: sender };
65
- return { apiKey: email.apiKey, from: sender };
65
+ return { apiKey: email.apiKey, from: sender, fromName };
66
66
  }
67
67
  /**
68
68
  * Send an email via the Brevo transactional email API.
@@ -76,7 +76,7 @@ export async function sendEmail(to, subject, body, creds) {
76
76
  Accept: "application/json",
77
77
  },
78
78
  body: JSON.stringify({
79
- sender: { email: creds.from },
79
+ sender: creds.fromName ? { email: creds.from, name: creds.fromName } : { email: creds.from },
80
80
  to: [{ email: to }],
81
81
  subject,
82
82
  textContent: body,
@@ -55,12 +55,12 @@ export function detectOtpChannels(whatsappAccountId) {
55
55
  * Phone identifiers try WhatsApp then SMS, but only channels that
56
56
  * are enabled in `enabledMethods` are attempted.
57
57
  */
58
- export async function deliverOtp(identifier, code, accountId, enabledMethods) {
58
+ export async function deliverOtp(identifier, code, accountId, enabledMethods, brandName) {
59
59
  const message = `Your verification code is: ${code}`;
60
60
  const methods = enabledMethods ?? ["whatsapp", "sms", "email"];
61
61
  // Email identifiers — deliver via Brevo email API
62
62
  if (isEmail(identifier)) {
63
- const emailCreds = await resolveEmailCredentials();
63
+ const emailCreds = await resolveEmailCredentials(brandName);
64
64
  if (!emailCreds) {
65
65
  throw new Error("Email verification is not configured. Add a Brevo API key and verify a sender in the Brevo console.");
66
66
  }
@@ -80,7 +80,7 @@ export async function deliverOtp(identifier, code, accountId, enabledMethods) {
80
80
  }
81
81
  }
82
82
  if (trySms) {
83
- const smsCreds = await resolveSmsCredentials();
83
+ const smsCreds = await resolveSmsCredentials(brandName);
84
84
  if (smsCreds) {
85
85
  await sendSms(identifier, message, smsCreds);
86
86
  return { channel: "sms" };
@@ -72,11 +72,15 @@ export function hasSmsApiKey() {
72
72
  * brand identity. The name is truncated to 11 characters (GSM limit).
73
73
  * Returns null if no API key is configured.
74
74
  */
75
- export async function resolveSmsCredentials() {
75
+ export async function resolveSmsCredentials(brandName) {
76
76
  const cfg = loadConfig();
77
77
  const apiKey = cfg.publicChat?.email?.apiKey;
78
78
  if (!apiKey)
79
79
  return null;
80
+ // Brand name override takes precedence — use it directly (truncated to GSM limit)
81
+ if (brandName) {
82
+ return { apiKey, sender: truncateSender(brandName) };
83
+ }
80
84
  if (cachedSenderName &&
81
85
  cachedSenderName.apiKey === apiKey &&
82
86
  cachedSenderName.name.length <= 11) {
@@ -98,14 +98,23 @@ function isValidEmail(email) {
98
98
  function normalizePhone(raw) {
99
99
  return raw.replace(/[\s\-()]/g, "");
100
100
  }
101
+ /** Resolve the auth mode for a specific account, falling back to the global setting. */
102
+ function resolveAccountAuthMode(cfg, accountId) {
103
+ return cfg.publicChat?.accountSettings?.[accountId]?.auth ?? cfg.publicChat?.auth ?? "anonymous";
104
+ }
105
+ /** Resolve the verify methods for a specific account, falling back to the global setting. */
106
+ function resolveAccountVerifyMethods(cfg, accountId) {
107
+ return (cfg.publicChat?.accountSettings?.[accountId]?.verifyMethods ??
108
+ cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
109
+ }
101
110
  /** Check whether the auth mode allows anonymous sessions. */
102
- function allowsAnonymous(cfg) {
103
- const mode = cfg.publicChat?.auth ?? "anonymous";
111
+ function allowsAnonymous(cfg, accountId) {
112
+ const mode = resolveAccountAuthMode(cfg, accountId);
104
113
  return mode === "anonymous" || mode === "choice";
105
114
  }
106
115
  /** Check whether the auth mode allows OTP verification. */
107
- function allowsVerified(cfg) {
108
- const mode = cfg.publicChat?.auth ?? "anonymous";
116
+ function allowsVerified(cfg, accountId) {
117
+ const mode = resolveAccountAuthMode(cfg, accountId);
109
118
  return mode === "verified" || mode === "choice";
110
119
  }
111
120
  function writeSse(res, data) {
@@ -184,7 +193,7 @@ async function handleSession(req, res, accountId, cfg, maxBodyBytes) {
184
193
  sendMethodNotAllowed(res);
185
194
  return;
186
195
  }
187
- if (!allowsAnonymous(cfg)) {
196
+ if (!allowsAnonymous(cfg, accountId)) {
188
197
  sendForbidden(res, "anonymous sessions are disabled — use OTP verification");
189
198
  return;
190
199
  }
@@ -214,7 +223,7 @@ async function handleOtpRequest(req, res, accountId, cfg, maxBodyBytes) {
214
223
  sendMethodNotAllowed(res);
215
224
  return;
216
225
  }
217
- if (!allowsVerified(cfg)) {
226
+ if (!allowsVerified(cfg, accountId)) {
218
227
  sendForbidden(res, "OTP verification is disabled for this account");
219
228
  return;
220
229
  }
@@ -229,7 +238,7 @@ async function handleOtpRequest(req, res, accountId, cfg, maxBodyBytes) {
229
238
  ? payload.phone.trim()
230
239
  : "";
231
240
  const isEmailId = rawIdentifier.includes("@");
232
- const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
241
+ const verifyMethods = normalizeVerifyMethods(resolveAccountVerifyMethods(cfg, accountId));
233
242
  let identifier;
234
243
  if (isEmailId) {
235
244
  if (!verifyMethods.includes("email")) {
@@ -270,8 +279,9 @@ async function handleOtpRequest(req, res, accountId, cfg, maxBodyBytes) {
270
279
  const deliveryMethods = preferredChannel && verifyMethods.includes(preferredChannel)
271
280
  ? [preferredChannel]
272
281
  : verifyMethods;
282
+ const brandName = accountId || undefined;
273
283
  try {
274
- const delivery = await deliverOtp(identifier, result.code, whatsappAccountId, deliveryMethods);
284
+ const delivery = await deliverOtp(identifier, result.code, whatsappAccountId, deliveryMethods, brandName);
275
285
  sendJson(res, 200, { ok: true, channel: delivery.channel });
276
286
  }
277
287
  catch (err) {
@@ -287,7 +297,7 @@ async function handleOtpVerify(req, res, accountId, cfg, maxBodyBytes) {
287
297
  sendMethodNotAllowed(res);
288
298
  return;
289
299
  }
290
- if (!allowsVerified(cfg)) {
300
+ if (!allowsVerified(cfg, accountId)) {
291
301
  sendForbidden(res, "OTP verification is disabled for this account");
292
302
  return;
293
303
  }
@@ -755,14 +765,19 @@ async function handleCapabilities(req, res, accountId, cfg) {
755
765
  sendMethodNotAllowed(res, "GET");
756
766
  return;
757
767
  }
758
- const authMode = cfg.publicChat?.auth ?? "anonymous";
759
- const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
768
+ const authMode = resolveAccountAuthMode(cfg, accountId);
769
+ const verifyMethods = normalizeVerifyMethods(resolveAccountVerifyMethods(cfg, accountId));
760
770
  const agentId = resolvePublicAgentId(cfg, accountId);
761
771
  const whatsappAccountId = resolveAgentBoundAccountId(cfg, agentId, "whatsapp") ?? undefined;
762
772
  const channels = detectOtpChannels(whatsappAccountId);
773
+ const wsBrand = cfg.workspaces?.[accountId]?.brand;
774
+ const accentColor = wsBrand?.accentColor ?? cfg.ui?.seamColor ?? null;
775
+ const backgroundColor = wsBrand?.backgroundColor ?? null;
763
776
  sendJson(res, 200, {
764
777
  auth: authMode,
765
778
  verify_methods: verifyMethods,
779
+ accent_color: accentColor,
780
+ background_color: backgroundColor,
766
781
  otp: {
767
782
  available: channels.length > 0,
768
783
  channels,
@@ -47,7 +47,8 @@ export const publicChatHandlers = {
47
47
  return;
48
48
  }
49
49
  const isEmailId = rawIdentifier.includes("@");
50
- const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
50
+ const verifyMethods = normalizeVerifyMethods(cfg.publicChat?.accountSettings?.[accountId ?? ""]?.verifyMethods ??
51
+ cfg.publicChat?.verifyMethods ?? ["whatsapp", "sms"]);
51
52
  let identifier;
52
53
  if (isEmailId) {
53
54
  if (!verifyMethods.includes("email")) {
@@ -87,8 +88,9 @@ export const publicChatHandlers = {
87
88
  const deliveryMethods = preferredChannel && verifyMethods.includes(preferredChannel)
88
89
  ? [preferredChannel]
89
90
  : verifyMethods;
91
+ const brandName = accountId || undefined;
90
92
  try {
91
- const delivery = await deliverOtp(identifier, result.code, whatsappAccountId, deliveryMethods);
93
+ const delivery = await deliverOtp(identifier, result.code, whatsappAccountId, deliveryMethods, brandName);
92
94
  respond(true, { ok: true, channel: delivery.channel });
93
95
  }
94
96
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.12.0",
3
+ "version": "1.12.1",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"