@camstack/addon-notifiers 1.2.130 → 1.2.132

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +256 -30
  2. package/dist/addon.mjs +256 -30
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13714,7 +13714,14 @@ var BrokerProviderInfoSchema = object({
13714
13714
  /** Broker kinds this provider can create, with a display label. */
13715
13715
  kinds: array(object({
13716
13716
  kind: string(),
13717
- label: string()
13717
+ label: string(),
13718
+ /**
13719
+ * Relative hub path to start an interactive account link (OAuth popup).
13720
+ * May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
13721
+ */
13722
+ interactiveConnectPath: string().min(1).optional(),
13723
+ /** Relative hub path to disconnect the linked account (same popup pattern). */
13724
+ interactiveDisconnectPath: string().min(1).optional()
13718
13725
  }))
13719
13726
  });
13720
13727
  var ListInputSchema = object({
@@ -51976,18 +51983,18 @@ var HTML_ESCAPES = [
51976
51983
  [/"/g, """],
51977
51984
  [/'/g, "'"]
51978
51985
  ];
51979
- function escapeHtml$1(text) {
51986
+ function escapeHtml$2(text) {
51980
51987
  let out = text;
51981
51988
  for (const [re, rep] of HTML_ESCAPES) out = out.replace(re, rep);
51982
51989
  return out;
51983
51990
  }
51984
51991
  /** Plain text → HTML-lite (escape + newlines to <br>). */
51985
51992
  function textToHtml(text) {
51986
- return escapeHtml$1(text).replace(/\n/g, "<br>");
51993
+ return escapeHtml$2(text).replace(/\n/g, "<br>");
51987
51994
  }
51988
51995
  /** Markdown → HTML-lite (bold/italic/code/links; used e.g. for Pushover). */
51989
51996
  function markdownToHtmlLite(md) {
51990
- return escapeHtml$1(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51997
+ return escapeHtml$2(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51991
51998
  }
51992
51999
  /**
51993
52000
  * Preference order when the source format is NOT supported — first supported
@@ -53346,6 +53353,134 @@ var discordAdapter = {
53346
53353
  }
53347
53354
  };
53348
53355
  //#endregion
53356
+ //#region src/adapters/email.ts
53357
+ var EMAIL_KIND = "email";
53358
+ var BODY_MAX = 1e5;
53359
+ /** Build the email kind descriptor, optionally stamping live provider options. */
53360
+ function buildEmailDescriptor(providers = []) {
53361
+ const options = providers.map((p) => ({
53362
+ value: p.value,
53363
+ label: p.label,
53364
+ ...p.description !== void 0 ? { description: p.description } : {}
53365
+ }));
53366
+ return {
53367
+ kind: EMAIL_KIND,
53368
+ label: "Email",
53369
+ icon: "mail",
53370
+ addonId: "notifiers",
53371
+ supportsDiscovery: false,
53372
+ configSchema: { sections: [{
53373
+ id: "email",
53374
+ title: "Email",
53375
+ description: "Send via a Connectivity → Email provider (Google Services or SMTP). Install and Verify a provider first.",
53376
+ fields: [
53377
+ {
53378
+ type: "text",
53379
+ key: "to",
53380
+ label: "To",
53381
+ description: "Recipient address, or comma-separated list.",
53382
+ required: true
53383
+ },
53384
+ {
53385
+ type: "select",
53386
+ key: "smtpAddonId",
53387
+ label: "Email provider",
53388
+ description: options.length === 0 ? "No ready provider yet. Open Connectivity → Email, install Google Services or SMTP, then Verify." : "Which Connectivity → Email entry delivers this target.",
53389
+ required: true,
53390
+ options
53391
+ },
53392
+ {
53393
+ type: "text",
53394
+ key: "from",
53395
+ label: "From (optional)",
53396
+ description: "Override From when the relay allows it."
53397
+ }
53398
+ ]
53399
+ }] },
53400
+ caps: {
53401
+ attachments: {
53402
+ mediaTypes: ["image"],
53403
+ mode: "url",
53404
+ max: 0
53405
+ },
53406
+ actions: 0,
53407
+ levels: [{
53408
+ id: "normal",
53409
+ label: "Normal",
53410
+ ordinal: 3
53411
+ }, {
53412
+ id: "high",
53413
+ label: "High",
53414
+ ordinal: 4
53415
+ }],
53416
+ format: [
53417
+ "text",
53418
+ "html",
53419
+ "markdown"
53420
+ ],
53421
+ clickUrl: true,
53422
+ sound: false,
53423
+ ttl: false,
53424
+ bodyMaxLen: BODY_MAX
53425
+ }
53426
+ };
53427
+ }
53428
+ function requireTo(config) {
53429
+ const raw = config["to"];
53430
+ if (typeof raw !== "string" || raw.trim() === "") throw new Error("email: missing required config 'to'");
53431
+ const list = raw.split(/[,;\s]+/).map((s) => s.trim()).filter((s) => s.length > 0);
53432
+ if (list.length === 0) throw new Error("email: missing required config 'to'");
53433
+ return list;
53434
+ }
53435
+ function renderBodies(prepared) {
53436
+ const body = prepared.bodyParts[0] ?? prepared.body;
53437
+ const title = prepared.title?.trim() ?? "";
53438
+ const click = prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `\n\n${prepared.clickUrl}` : "";
53439
+ const text = title !== "" ? `${title}\n\n${body}${click}` : `${body}${click}`;
53440
+ if (prepared.format === "html") return {
53441
+ text,
53442
+ html: `${title !== "" ? `<h2>${escapeHtml$1(title)}</h2>` : ""}<div>${body}</div>${prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `<p><a href="${escapeHtml$1(prepared.clickUrl)}">${escapeHtml$1(prepared.clickUrl)}</a></p>` : ""}`
53443
+ };
53444
+ return { text };
53445
+ }
53446
+ function escapeHtml$1(value) {
53447
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
53448
+ }
53449
+ /** Build the email adapter bound to a live smtp-provider client. */
53450
+ function createEmailAdapter(smtp) {
53451
+ return {
53452
+ descriptor: buildEmailDescriptor(),
53453
+ hydrateDescriptor: async () => {
53454
+ return buildEmailDescriptor(await smtp.listReadyProviders?.() ?? []);
53455
+ },
53456
+ destinationKeys: ["to", "smtpAddonId"],
53457
+ async sendDirect(config, prepared) {
53458
+ try {
53459
+ const to = requireTo(config);
53460
+ const bodies = renderBodies(prepared);
53461
+ const addonId = (typeof config["smtpAddonId"] === "string" && config["smtpAddonId"].trim() !== "" ? config["smtpAddonId"].trim() : void 0) ?? await smtp.resolveDefaultAddonId?.();
53462
+ if (addonId === void 0) throw new Error("email: pick an Email provider (Connectivity → Email) — none ready or none selected");
53463
+ const subject = prepared.title?.trim() && prepared.title.trim().length > 0 ? prepared.title.trim() : "CamStack notification";
53464
+ const from = typeof config["from"] === "string" && config["from"].trim() !== "" ? config["from"].trim() : void 0;
53465
+ await smtp.sendEmail({
53466
+ to,
53467
+ subject,
53468
+ text: bodies.text,
53469
+ ...bodies.html !== void 0 ? { html: bodies.html } : {},
53470
+ addonId,
53471
+ ...from !== void 0 ? { from } : {}
53472
+ });
53473
+ return { success: true };
53474
+ } catch (err) {
53475
+ return {
53476
+ success: false,
53477
+ error: err instanceof Error ? err.message : String(err)
53478
+ };
53479
+ }
53480
+ }
53481
+ };
53482
+ }
53483
+ //#endregion
53349
53484
  //#region src/http.ts
53350
53485
  /** Default per-request timeout for notification dispatch. */
53351
53486
  var DEFAULT_OUTPUT_TIMEOUT_MS = 1e4;
@@ -55280,7 +55415,8 @@ function zentikConfigSchema() {
55280
55415
  }
55281
55416
  //#endregion
55282
55417
  //#region src/adapters/registry.ts
55283
- var NOTIFIER_ADAPTERS = [
55418
+ /** HTTP adapters that need no runtime deps. */
55419
+ var HTTP_NOTIFIER_ADAPTERS = [
55284
55420
  ntfyAdapter,
55285
55421
  pushoverAdapter,
55286
55422
  gotifyAdapter,
@@ -55432,6 +55568,17 @@ var NOTIFIER_ADAPTERS = [
55432
55568
  },
55433
55569
  whatsappAdapter
55434
55570
  ];
55571
+ /**
55572
+ * Full catalog. When `smtp` is omitted the Email kind is still listed but
55573
+ * `sendDirect` fails until the addon wires a client (tests use this).
55574
+ */
55575
+ function buildNotifierAdapters(smtp) {
55576
+ const emailSmtp = smtp ?? { async sendEmail() {
55577
+ throw new Error("email: no smtp-provider client — install Google Services or SMTP Email under Connectivity → Email and Verify it");
55578
+ } };
55579
+ return [...HTTP_NOTIFIER_ADAPTERS, createEmailAdapter(emailSmtp)];
55580
+ }
55581
+ buildNotifierAdapters();
55435
55582
  var NOTIFIER_ICONS = {
55436
55583
  telegram: {
55437
55584
  contentType: "image/svg+xml",
@@ -55457,6 +55604,10 @@ var NOTIFIER_ICONS = {
55457
55604
  contentType: "image/svg+xml",
55458
55605
  body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#7C3AED\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4.876 13.61a4 4 0 1 0 6.124 3.39h6\" /><path d=\"M15.066 20.502a4 4 0 1 0 1.934 -7.502c-.706 0 -1.424 .179 -2 .5l-3 -5.5\" /><path d=\"M16 8a4 4 0 1 0 -8 0c0 1.506 .77 2.818 2 3.5l-3 5.5\" /></svg>"
55459
55606
  },
55607
+ email: {
55608
+ contentType: "image/svg+xml",
55609
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"#EA4335\"><path d=\"M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6zm-2 0-8 5-8-5h16zm0 12H4V8l8 5 8-5v10z\"/></svg>"
55610
+ },
55460
55611
  gotify: {
55461
55612
  contentType: "image/svg+xml",
55462
55613
  body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 500 500\" width=\"500\" height=\"500\"><style type=\"text/css\" id=\"style1\"> .st0{fill:#DDCBA2;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st1{fill:#71CAEE;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st2{fill:#FFFFFF;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st3{fill:#888E93;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st4{fill:#F0F0F0;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st5{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st6{fill:url(#SVGID_1_);} .st7{fill:url(#SVGID_2_);} .st8{fill:#FFFFFF;} .st9{fill:url(#SVGID_3_);} .st10{fill:url(#SVGID_4_);} .st11{fill:url(#SVGID_5_);} .st12{fill:none;stroke:#FFFFFF;stroke-width:63;stroke-linecap:round;stroke-miterlimit:10;} .st13{fill:none;stroke:#FFFFFF;stroke-width:56;stroke-linecap:round;stroke-miterlimit:10;} .st14{fill:none;stroke:#A4C639;stroke-width:45.1385;stroke-linecap:round;stroke-miterlimit:10;} .st15{fill:none;stroke:#FFFFFF;stroke-width:16;} .st16{fill:#A4C639;} .st17{fill:#E6E6E6;stroke:#FFFFFF;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;} .st18{fill:none;stroke:#FFFFFF;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;} .st19{fill:url(#SVGID_6_);} .st20{fill:url(#SVGID_7_);} .st21{fill:url(#SVGID_8_);} </style><g transform=\"matrix(2.3327641,0,0,2.3327641,-431.80457,-323.21807)\"><g id=\"g48\" transform=\"translate(-24.881692,25.94805)\"><g id=\"g47\"><path class=\"st1\" d=\"m 258.9,119.7 c -3,-0.9 -6,-1.8 -9,-2.7 -4.6,-1.4 -9.2,-2.8 -14,-2.5 -2.8,0.2 -6.1,1.3 -6.9,4 -0.6,2 -1.6,7.3 -1.3,7.9 1.5,3.4 13.9,6.7 18.3,6.7\" id=\"path2\" /><path d=\"m 392.6,177.9 c -1.4,1.4 -2.2,3.5 -2.5,5.5 -0.2,1.4 -0.1,3 0.5,4.3 0.6,1.3 1.8,2.3 3.1,3 1.3,0.6 2.8,0.9 4.3,0.9 1.1,0 2.3,-0.1 3.1,-0.9 0.6,-0.7 0.8,-1.6 0.9,-2.5 0.2,-2.3 -0.1,-4.7 -0.9,-6.9 -0.4,-1.1 -0.9,-2.3 -1.8,-3.1 -1.7,-1.8 -4.5,-2.2 -6.4,-0.5 -0.1,0 -0.2,0.1 -0.3,0.2 z\" id=\"path3\" /><path class=\"st2\" d=\"m 358.5,164.2 c -1,-1 0,-2.7 1,-3.7 5.8,-5.2 15.1,-4.6 21.8,-0.6 10.9,6.6 15.6,19.9 17.2,32.5 0.6,5.2 0.9,10.6 -0.5,15.7 -1.4,5.1 -4.6,9.9 -9.3,12.1 -1.1,0.5 -2.3,0.9 -3.4,0.5 -1.1,-0.4 -1.9,-1.8 -1.2,-2.8 -9.4,-13.6 -19,-26.8 -20.9,-43.2 -0.5,-4.1 -1.8,-7.4 -4.7,-10.5 z\" id=\"path4\" /><path class=\"st1\" d=\"m 220.05771,133 c 34.58546,-18 79.30862,-19.6 112.16964,-8.7 23.69194,7.9 41.3449,26.7 49.4515,50 7.11442,20.6 7.1441,43.6 3.04307,65.7 -7.4942,40.2 -26.19597,77.9 -48.96683,112.6 -12.56244,19 -24.57093,36 -44.23305,48.5 -38.69347,24.6 -88.90104,22.1 -129.28516,11.5 -19.50745,-5.1 -38.36842,-17.3 -44.32901,-37.3 -3.81479,-12.8 -2.07563,-27.6 4.5788,-40 13.50886,-24.8 46.15565,-38.4 50.82627,-67.9 1.36964,-8.7 -0.26842,-17.3 -1.62187,-25.7 -3.7989,-23.4 -5.37477,-45.8 6.68784,-68.7 9.46237,-17.7 24.28607,-31 41.6788,-40 z\" id=\"path6\" /><path class=\"st2\" d=\"m 264.5,174.9 c -0.5,0.5 -0.9,1 -1.3,1.6 -9,11.6 -12,27.9 -9.3,42.1 1.7,9 5.9,17.9 13.2,23.4 19.3,14.6 51.5,13.5 68.4,-1.5 24.4,-21.7 13,-67.6 -14,-78.8 -17.6,-7.2 -43.7,-1.6 -57,13.2 z\" id=\"path9\" /><g id=\"g11\"><path class=\"st2\" d=\"m 382.1,237.1 c 1.4,-0.1 2.9,-0.1 4.3,0.1 0.3,0 0.7,0.1 1,0.4 0.2,0.3 0.4,0.7 0.5,1.1 1,3.9 0.5,8.2 0.1,12.4 -0.1,0.9 -0.2,1.8 -0.6,2.6 -1,2.1 -3.1,2.7 -4.7,2.7 -0.1,0 -0.2,0 -0.3,-0.1 -0.3,-0.2 -0.3,-0.7 -0.2,-1.2 0.3,-5.9 -0.1,-11.9 -0.1,-18 0,0.1 0,0.1 0,0 z\" id=\"path10\" /><path class=\"st2\" d=\"m 378.7,236.8 c -1.4,0.4 -2.5,2 -2.8,4.4 -0.5,4.4 -0.7,8.9 -0.5,13.4 0,0.9 0.1,1.9 0.5,2.4 0.2,0.3 0.5,0.4 0.8,0.4 1.6,0.3 4.1,-0.6 5.6,-1 0,0 0,-5.2 -0.1,-8 -0.1,-2.8 -0.1,-6.1 -0.2,-8.9 0,-0.6 0,-1.5 0,-2.2 0.1,-0.7 -2.6,-0.7 -3.3,-0.5 z\" id=\"path11\" /></g><path class=\"st0\" d=\"m 358.3,231.8 c -0.3,2.2 0.1,4.7 1.7,7.4 2.6,4.4 7,6.1 11.9,5.8 8.9,-0.6 25.3,-5.4 27.5,-15.7 0.6,-3 -0.3,-6.1 -2.2,-8.5 -6.2,-7.8 -17.8,-5.7 -25.6,-2 -5.9,2.7 -12.4,7 -13.3,13 z\" id=\"path12\" /><path class=\"st3\" d=\"m 386.4,208.6 c 2.2,1.4 3.7,3.8 4,7 0.3,3.6 -1.4,7.5 -5,8.8 -2.9,1.1 -6.2,0.6 -9.1,-0.4 -2.9,-1 -5.8,-2.8 -6.8,-5.7 -0.7,-2 -0.3,-4.3 0.7,-6.1 1.1,-1.8 2.8,-3.2 4.7,-4.1 3.9,-1.8 8.4,-1.6 11.5,0.5 z\" id=\"path13\" /><path class=\"st0\" d=\"m 414.7,262.6 c 2.4,0.6 4.8,2.1 5.6,4.4 0.8,2.3 0.1,4.9 -1.6,6.7 -1.7,1.8 -4.2,2.5 -6.6,2.5 -0.8,0 -1.7,-0.1 -2.4,-0.5 -2.5,-1.1 -3.5,-4 -4.2,-6.6 -1.8,-6.8 3.6,-7.8 9.2,-6.5 z\" id=\"path14\" /><g id=\"g17\"><path class=\"st4\" d=\"m 267.1,284.7 c 2.3,-4.5 141.3,-36.2 144.7,-31.6 3.4,4.5 15.8,88.2 9,90.4 -6.8,2.3 -119.8,37.3 -126.6,35 -6.8,-2.3 -29.4,-89.3 -27.1,-93.8 z\" id=\"path15\" /><path class=\"st5\" d=\"m 294.2,378.5 c 0,0 54.3,-74.6 59.9,-76.9 5.7,-2.3 67.3,41.3 67.3,41.3\" id=\"path16\" /><path class=\"st4\" d=\"m 267,287.7 c 0,0 86,38.8 91.6,36.6 5.7,-2.3 53.1,-71.2 53.1,-71.2\" id=\"path17\" /></g><g id=\"g33\"><linearGradient id=\"SVGID_2_\" gradientUnits=\"userSpaceOnUse\" x1=\"265.03339\" y1=\"280.2951\" x2=\"275.3009\" y2=\"302.31369\"><stop offset=\"5.152266e-07\" style=\"stop-color:#71CAEE\" id=\"stop25\" /><stop offset=\"4.104413e-02\" style=\"stop-color:#83CAE2\" id=\"stop26\" /><stop offset=\"0.1206\" style=\"stop-color:#9FCACE\" id=\"stop27\" /><stop offset=\"0.2107\" style=\"stop-color:#B6CBBE\" id=\"stop28\" /><stop offset=\"0.3139\" style=\"stop-color:#C7CBB1\" id=\"stop29\" /><stop offset=\"0.4384\" style=\"stop-color:#D4CBA8\" id=\"stop30\" /><stop offset=\"0.6058\" style=\"stop-color:#DBCBA3\" id=\"stop31\" /><stop offset=\"1\" style=\"stop-color:#DDCBA2\" id=\"stop32\" /></linearGradient><path class=\"st7\" d=\"m 261.9,283.5 c -0.1,4.2 4.3,7.3 8.4,7.6 4.1,0.3 8.2,-1.3 12.2,-2.6 1.4,-0.4 2.9,-0.8 4.2,-0.2 1.8,0.9 2.7,4.1 1.8,5.9 -0.9,1.8 -3.4,3.5 -5.3,4.4 -6.5,3 -12.9,3.6 -19.9,2 -5.3,-1.2 -11.3,-4.3 -13,-13.5\" id=\"path32\" style=\"fill:url(#SVGID_2_)\" /><path class=\"st5\" d=\"m 261.9,283.5 c -0.1,4.2 4.3,7.3 8.4,7.6 4.1,0.3 8.2,-1.3 12.2,-2.6 1.4,-0.4 2.9,-0.8 4.2,-0.2 1.8,0.9 2.7,4.1 1.8,5.9 -0.9,1.8 -3.4,3.5 -5.3,4.4 -6.5,3 -12.9,3.6 -19.9,2 -5.3,-1.2 -11.3,-4.3 -13,-13.5\" id=\"path33\" /></g><path d=\"m 318.4,198.4 c -2,-0.3 -4.1,0.1 -5.9,1.3 -3.2,2.1 -4.7,6.2 -4.7,9.9 0,1.9 0.4,3.8 1.4,5.3 1.2,1.7 3.1,2.9 5.2,3.4 3.4,0.8 8.2,0.7 10.5,-2.5 1,-1.5 1.4,-3.3 1.5,-5.1 0.5,-5.7 -1.8,-11.4 -8,-12.3 z\" id=\"path34\" /><path class=\"st8\" d=\"m 320.4,203.3 c 0.9,0.3 1.7,0.8 2.1,1.7 0.4,0.8 0.4,1.7 0.3,2.5 -0.1,1 -0.6,2 -1.5,2.7 -0.7,0.5 -1.7,0.7 -2.6,0.5 -0.9,-0.2 -1.7,-0.8 -2.2,-1.6 -1.1,-1.6 -0.9,-4.4 0.9,-5.5 0.9,-0.4 2,-0.6 3,-0.3 z\" id=\"path35\" /></g></g></g></svg>"
@@ -56273,6 +56424,32 @@ function createNotificationOutputProvider(deps) {
56273
56424
  ...prepared,
56274
56425
  attachments
56275
56426
  };
56427
+ if (adapter.sendDirect) try {
56428
+ const result = await adapter.sendDirect(target.config, readyPrepared);
56429
+ return result.error === void 0 ? {
56430
+ ...result,
56431
+ renderedAs: prepared.renderedAs
56432
+ } : {
56433
+ ...result,
56434
+ error: stripUrls(result.error),
56435
+ renderedAs: prepared.renderedAs
56436
+ };
56437
+ } catch (err) {
56438
+ logger?.warn("sendDirect failed", { meta: {
56439
+ kind: target.kind,
56440
+ error: errMsg(err)
56441
+ } });
56442
+ return {
56443
+ success: false,
56444
+ error: `sendDirect failed: ${errMsg(err)}`,
56445
+ renderedAs: prepared.renderedAs
56446
+ };
56447
+ }
56448
+ if (!adapter.buildRequest) return {
56449
+ success: false,
56450
+ error: `No adapter send path for kind '${target.kind}'`,
56451
+ renderedAs: prepared.renderedAs
56452
+ };
56276
56453
  let plan;
56277
56454
  try {
56278
56455
  plan = adapter.buildRequest(target.config, readyPrepared);
@@ -56333,24 +56510,32 @@ function createNotificationOutputProvider(deps) {
56333
56510
  const iconUrlFor = deps.iconUrlFor;
56334
56511
  const iconMediaTypeFor = deps.iconMediaTypeFor;
56335
56512
  return {
56336
- listTargetKinds: async () => deps.adapters.map((a) => {
56337
- const descriptor = {
56338
- ...a.descriptor,
56339
- addonId,
56340
- destinationKeys: [...a.destinationKeys]
56341
- };
56342
- const iconUrl = iconUrlFor?.(a.descriptor.kind);
56343
- if (iconUrl === void 0) return descriptor;
56344
- const iconMediaType = iconMediaTypeFor?.(a.descriptor.kind);
56345
- return iconMediaType !== void 0 ? {
56346
- ...descriptor,
56347
- iconUrl,
56348
- iconMediaType
56349
- } : {
56350
- ...descriptor,
56351
- iconUrl
56352
- };
56353
- }),
56513
+ listTargetKinds: async () => {
56514
+ const kinds = [];
56515
+ for (const a of deps.adapters) {
56516
+ const base = a.hydrateDescriptor !== void 0 ? await a.hydrateDescriptor() : a.descriptor;
56517
+ const descriptor = {
56518
+ ...base,
56519
+ addonId,
56520
+ destinationKeys: [...a.destinationKeys]
56521
+ };
56522
+ const iconUrl = iconUrlFor?.(base.kind);
56523
+ if (iconUrl === void 0) {
56524
+ kinds.push(descriptor);
56525
+ continue;
56526
+ }
56527
+ const iconMediaType = iconMediaTypeFor?.(base.kind);
56528
+ kinds.push(iconMediaType !== void 0 ? {
56529
+ ...descriptor,
56530
+ iconUrl,
56531
+ iconMediaType
56532
+ } : {
56533
+ ...descriptor,
56534
+ iconUrl
56535
+ });
56536
+ }
56537
+ return kinds;
56538
+ },
56354
56539
  listTargets: async () => {
56355
56540
  return (await store.list()).map((target) => {
56356
56541
  const adapter = adapterByKind.get(target.kind);
@@ -56640,11 +56825,12 @@ var TargetStore = class {
56640
56825
  * Notifiers addon — a system `notification-output` collection provider.
56641
56826
  *
56642
56827
  * It hosts per-kind `NotifierAdapter`s (ntfy, pushover, gotify, telegram,
56643
- * discord, webhook, zentik, whatsapp) behind one uniform cap surface. Targets
56644
- * persist via the `settings-store` cap; the canonical notification is degraded
56645
- * per each target's declared capabilities by the pure `prepareNotification`
56646
- * engine in `@camstack/types`. The catalog is `NOTIFIER_ADAPTERS`
56647
- * (`adapters/registry.ts`).
56828
+ * discord, webhook, zentik, whatsapp, email) behind one uniform cap surface.
56829
+ * Targets persist via the `settings-store` cap; the canonical notification is
56830
+ * degraded per each target's declared capabilities by the pure
56831
+ * `prepareNotification` engine in `@camstack/types`. The catalog is
56832
+ * `buildNotifierAdapters` (`adapters/registry.ts`); email uses `sendDirect` →
56833
+ * `smtp-provider`.
56648
56834
  */
56649
56835
  var NotifiersAddon = class extends BaseAddon {
56650
56836
  constructor() {
@@ -56656,9 +56842,49 @@ var NotifiersAddon = class extends BaseAddon {
56656
56842
  if (!api) this.ctx.logger.warn("notifiers: no ctx.api — targets persist in-memory only");
56657
56843
  const store = new TargetStore(port);
56658
56844
  await store.init();
56845
+ const listReadyProviders = async () => {
56846
+ if (api === void 0) return [];
56847
+ const [providers, addons] = await Promise.all([api.addons.listCapabilityProviders.query({ capName: "smtp-provider" }), api.addons.list.query()]);
56848
+ const names = /* @__PURE__ */ new Map();
56849
+ for (const row of addons) {
56850
+ const id = row.manifest.id;
56851
+ names.set(id, row.manifest.name ?? row.manifest.packageDisplayName ?? id);
56852
+ }
56853
+ const out = [];
56854
+ for (const p of providers) {
56855
+ if (!p.isActive) continue;
56856
+ try {
56857
+ const status = await api.smtpProvider.getStatus.query({ addonId: p.addonId });
56858
+ if (!status.ready) continue;
56859
+ const name = names.get(p.addonId) ?? p.addonId;
56860
+ out.push({
56861
+ value: p.addonId,
56862
+ label: status.host !== "" ? `${name} — ${status.host}` : name,
56863
+ description: p.addonId
56864
+ });
56865
+ } catch {}
56866
+ }
56867
+ return out;
56868
+ };
56869
+ const adapters = buildNotifierAdapters(api !== void 0 ? {
56870
+ sendEmail: async (input) => {
56871
+ return { messageId: (await api.smtpProvider.sendEmail.mutate({
56872
+ to: typeof input.to === "string" ? input.to : [...input.to],
56873
+ subject: input.subject,
56874
+ text: input.text,
56875
+ ...input.html !== void 0 ? { html: input.html } : {},
56876
+ ...input.from !== void 0 ? { from: input.from } : {},
56877
+ ...input.addonId !== void 0 ? { addonId: input.addonId } : {}
56878
+ })).messageId };
56879
+ },
56880
+ listReadyProviders,
56881
+ resolveDefaultAddonId: async () => {
56882
+ return (await listReadyProviders())[0]?.value;
56883
+ }
56884
+ } : void 0);
56659
56885
  const provider = createNotificationOutputProvider({
56660
56886
  store,
56661
- adapters: NOTIFIER_ADAPTERS,
56887
+ adapters,
56662
56888
  logger: this.ctx.logger.child("notification-output"),
56663
56889
  iconUrlFor,
56664
56890
  iconMediaTypeFor
@@ -56668,7 +56894,7 @@ var NotifiersAddon = class extends BaseAddon {
56668
56894
  provider,
56669
56895
  logger: this.ctx.logger.child("nc-target-actions")
56670
56896
  });
56671
- this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: NOTIFIER_ADAPTERS.length } });
56897
+ this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: adapters.length } });
56672
56898
  return {
56673
56899
  providers: [{
56674
56900
  capability: notificationOutputCapability,
package/dist/addon.mjs CHANGED
@@ -13687,7 +13687,14 @@ var BrokerProviderInfoSchema = object({
13687
13687
  /** Broker kinds this provider can create, with a display label. */
13688
13688
  kinds: array(object({
13689
13689
  kind: string(),
13690
- label: string()
13690
+ label: string(),
13691
+ /**
13692
+ * Relative hub path to start an interactive account link (OAuth popup).
13693
+ * May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
13694
+ */
13695
+ interactiveConnectPath: string().min(1).optional(),
13696
+ /** Relative hub path to disconnect the linked account (same popup pattern). */
13697
+ interactiveDisconnectPath: string().min(1).optional()
13691
13698
  }))
13692
13699
  });
13693
13700
  var ListInputSchema = object({
@@ -51949,18 +51956,18 @@ var HTML_ESCAPES = [
51949
51956
  [/"/g, "&quot;"],
51950
51957
  [/'/g, "&#39;"]
51951
51958
  ];
51952
- function escapeHtml$1(text) {
51959
+ function escapeHtml$2(text) {
51953
51960
  let out = text;
51954
51961
  for (const [re, rep] of HTML_ESCAPES) out = out.replace(re, rep);
51955
51962
  return out;
51956
51963
  }
51957
51964
  /** Plain text → HTML-lite (escape + newlines to <br>). */
51958
51965
  function textToHtml(text) {
51959
- return escapeHtml$1(text).replace(/\n/g, "<br>");
51966
+ return escapeHtml$2(text).replace(/\n/g, "<br>");
51960
51967
  }
51961
51968
  /** Markdown → HTML-lite (bold/italic/code/links; used e.g. for Pushover). */
51962
51969
  function markdownToHtmlLite(md) {
51963
- return escapeHtml$1(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51970
+ return escapeHtml$2(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51964
51971
  }
51965
51972
  /**
51966
51973
  * Preference order when the source format is NOT supported — first supported
@@ -53319,6 +53326,134 @@ var discordAdapter = {
53319
53326
  }
53320
53327
  };
53321
53328
  //#endregion
53329
+ //#region src/adapters/email.ts
53330
+ var EMAIL_KIND = "email";
53331
+ var BODY_MAX = 1e5;
53332
+ /** Build the email kind descriptor, optionally stamping live provider options. */
53333
+ function buildEmailDescriptor(providers = []) {
53334
+ const options = providers.map((p) => ({
53335
+ value: p.value,
53336
+ label: p.label,
53337
+ ...p.description !== void 0 ? { description: p.description } : {}
53338
+ }));
53339
+ return {
53340
+ kind: EMAIL_KIND,
53341
+ label: "Email",
53342
+ icon: "mail",
53343
+ addonId: "notifiers",
53344
+ supportsDiscovery: false,
53345
+ configSchema: { sections: [{
53346
+ id: "email",
53347
+ title: "Email",
53348
+ description: "Send via a Connectivity → Email provider (Google Services or SMTP). Install and Verify a provider first.",
53349
+ fields: [
53350
+ {
53351
+ type: "text",
53352
+ key: "to",
53353
+ label: "To",
53354
+ description: "Recipient address, or comma-separated list.",
53355
+ required: true
53356
+ },
53357
+ {
53358
+ type: "select",
53359
+ key: "smtpAddonId",
53360
+ label: "Email provider",
53361
+ description: options.length === 0 ? "No ready provider yet. Open Connectivity → Email, install Google Services or SMTP, then Verify." : "Which Connectivity → Email entry delivers this target.",
53362
+ required: true,
53363
+ options
53364
+ },
53365
+ {
53366
+ type: "text",
53367
+ key: "from",
53368
+ label: "From (optional)",
53369
+ description: "Override From when the relay allows it."
53370
+ }
53371
+ ]
53372
+ }] },
53373
+ caps: {
53374
+ attachments: {
53375
+ mediaTypes: ["image"],
53376
+ mode: "url",
53377
+ max: 0
53378
+ },
53379
+ actions: 0,
53380
+ levels: [{
53381
+ id: "normal",
53382
+ label: "Normal",
53383
+ ordinal: 3
53384
+ }, {
53385
+ id: "high",
53386
+ label: "High",
53387
+ ordinal: 4
53388
+ }],
53389
+ format: [
53390
+ "text",
53391
+ "html",
53392
+ "markdown"
53393
+ ],
53394
+ clickUrl: true,
53395
+ sound: false,
53396
+ ttl: false,
53397
+ bodyMaxLen: BODY_MAX
53398
+ }
53399
+ };
53400
+ }
53401
+ function requireTo(config) {
53402
+ const raw = config["to"];
53403
+ if (typeof raw !== "string" || raw.trim() === "") throw new Error("email: missing required config 'to'");
53404
+ const list = raw.split(/[,;\s]+/).map((s) => s.trim()).filter((s) => s.length > 0);
53405
+ if (list.length === 0) throw new Error("email: missing required config 'to'");
53406
+ return list;
53407
+ }
53408
+ function renderBodies(prepared) {
53409
+ const body = prepared.bodyParts[0] ?? prepared.body;
53410
+ const title = prepared.title?.trim() ?? "";
53411
+ const click = prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `\n\n${prepared.clickUrl}` : "";
53412
+ const text = title !== "" ? `${title}\n\n${body}${click}` : `${body}${click}`;
53413
+ if (prepared.format === "html") return {
53414
+ text,
53415
+ html: `${title !== "" ? `<h2>${escapeHtml$1(title)}</h2>` : ""}<div>${body}</div>${prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `<p><a href="${escapeHtml$1(prepared.clickUrl)}">${escapeHtml$1(prepared.clickUrl)}</a></p>` : ""}`
53416
+ };
53417
+ return { text };
53418
+ }
53419
+ function escapeHtml$1(value) {
53420
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
53421
+ }
53422
+ /** Build the email adapter bound to a live smtp-provider client. */
53423
+ function createEmailAdapter(smtp) {
53424
+ return {
53425
+ descriptor: buildEmailDescriptor(),
53426
+ hydrateDescriptor: async () => {
53427
+ return buildEmailDescriptor(await smtp.listReadyProviders?.() ?? []);
53428
+ },
53429
+ destinationKeys: ["to", "smtpAddonId"],
53430
+ async sendDirect(config, prepared) {
53431
+ try {
53432
+ const to = requireTo(config);
53433
+ const bodies = renderBodies(prepared);
53434
+ const addonId = (typeof config["smtpAddonId"] === "string" && config["smtpAddonId"].trim() !== "" ? config["smtpAddonId"].trim() : void 0) ?? await smtp.resolveDefaultAddonId?.();
53435
+ if (addonId === void 0) throw new Error("email: pick an Email provider (Connectivity → Email) — none ready or none selected");
53436
+ const subject = prepared.title?.trim() && prepared.title.trim().length > 0 ? prepared.title.trim() : "CamStack notification";
53437
+ const from = typeof config["from"] === "string" && config["from"].trim() !== "" ? config["from"].trim() : void 0;
53438
+ await smtp.sendEmail({
53439
+ to,
53440
+ subject,
53441
+ text: bodies.text,
53442
+ ...bodies.html !== void 0 ? { html: bodies.html } : {},
53443
+ addonId,
53444
+ ...from !== void 0 ? { from } : {}
53445
+ });
53446
+ return { success: true };
53447
+ } catch (err) {
53448
+ return {
53449
+ success: false,
53450
+ error: err instanceof Error ? err.message : String(err)
53451
+ };
53452
+ }
53453
+ }
53454
+ };
53455
+ }
53456
+ //#endregion
53322
53457
  //#region src/http.ts
53323
53458
  /** Default per-request timeout for notification dispatch. */
53324
53459
  var DEFAULT_OUTPUT_TIMEOUT_MS = 1e4;
@@ -55253,7 +55388,8 @@ function zentikConfigSchema() {
55253
55388
  }
55254
55389
  //#endregion
55255
55390
  //#region src/adapters/registry.ts
55256
- var NOTIFIER_ADAPTERS = [
55391
+ /** HTTP adapters that need no runtime deps. */
55392
+ var HTTP_NOTIFIER_ADAPTERS = [
55257
55393
  ntfyAdapter,
55258
55394
  pushoverAdapter,
55259
55395
  gotifyAdapter,
@@ -55405,6 +55541,17 @@ var NOTIFIER_ADAPTERS = [
55405
55541
  },
55406
55542
  whatsappAdapter
55407
55543
  ];
55544
+ /**
55545
+ * Full catalog. When `smtp` is omitted the Email kind is still listed but
55546
+ * `sendDirect` fails until the addon wires a client (tests use this).
55547
+ */
55548
+ function buildNotifierAdapters(smtp) {
55549
+ const emailSmtp = smtp ?? { async sendEmail() {
55550
+ throw new Error("email: no smtp-provider client — install Google Services or SMTP Email under Connectivity → Email and Verify it");
55551
+ } };
55552
+ return [...HTTP_NOTIFIER_ADAPTERS, createEmailAdapter(emailSmtp)];
55553
+ }
55554
+ buildNotifierAdapters();
55408
55555
  var NOTIFIER_ICONS = {
55409
55556
  telegram: {
55410
55557
  contentType: "image/svg+xml",
@@ -55430,6 +55577,10 @@ var NOTIFIER_ICONS = {
55430
55577
  contentType: "image/svg+xml",
55431
55578
  body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#7C3AED\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4.876 13.61a4 4 0 1 0 6.124 3.39h6\" /><path d=\"M15.066 20.502a4 4 0 1 0 1.934 -7.502c-.706 0 -1.424 .179 -2 .5l-3 -5.5\" /><path d=\"M16 8a4 4 0 1 0 -8 0c0 1.506 .77 2.818 2 3.5l-3 5.5\" /></svg>"
55432
55579
  },
55580
+ email: {
55581
+ contentType: "image/svg+xml",
55582
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"#EA4335\"><path d=\"M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6zm-2 0-8 5-8-5h16zm0 12H4V8l8 5 8-5v10z\"/></svg>"
55583
+ },
55433
55584
  gotify: {
55434
55585
  contentType: "image/svg+xml",
55435
55586
  body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 500 500\" width=\"500\" height=\"500\"><style type=\"text/css\" id=\"style1\"> .st0{fill:#DDCBA2;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st1{fill:#71CAEE;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st2{fill:#FFFFFF;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st3{fill:#888E93;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st4{fill:#F0F0F0;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st5{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st6{fill:url(#SVGID_1_);} .st7{fill:url(#SVGID_2_);} .st8{fill:#FFFFFF;} .st9{fill:url(#SVGID_3_);} .st10{fill:url(#SVGID_4_);} .st11{fill:url(#SVGID_5_);} .st12{fill:none;stroke:#FFFFFF;stroke-width:63;stroke-linecap:round;stroke-miterlimit:10;} .st13{fill:none;stroke:#FFFFFF;stroke-width:56;stroke-linecap:round;stroke-miterlimit:10;} .st14{fill:none;stroke:#A4C639;stroke-width:45.1385;stroke-linecap:round;stroke-miterlimit:10;} .st15{fill:none;stroke:#FFFFFF;stroke-width:16;} .st16{fill:#A4C639;} .st17{fill:#E6E6E6;stroke:#FFFFFF;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;} .st18{fill:none;stroke:#FFFFFF;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;} .st19{fill:url(#SVGID_6_);} .st20{fill:url(#SVGID_7_);} .st21{fill:url(#SVGID_8_);} </style><g transform=\"matrix(2.3327641,0,0,2.3327641,-431.80457,-323.21807)\"><g id=\"g48\" transform=\"translate(-24.881692,25.94805)\"><g id=\"g47\"><path class=\"st1\" d=\"m 258.9,119.7 c -3,-0.9 -6,-1.8 -9,-2.7 -4.6,-1.4 -9.2,-2.8 -14,-2.5 -2.8,0.2 -6.1,1.3 -6.9,4 -0.6,2 -1.6,7.3 -1.3,7.9 1.5,3.4 13.9,6.7 18.3,6.7\" id=\"path2\" /><path d=\"m 392.6,177.9 c -1.4,1.4 -2.2,3.5 -2.5,5.5 -0.2,1.4 -0.1,3 0.5,4.3 0.6,1.3 1.8,2.3 3.1,3 1.3,0.6 2.8,0.9 4.3,0.9 1.1,0 2.3,-0.1 3.1,-0.9 0.6,-0.7 0.8,-1.6 0.9,-2.5 0.2,-2.3 -0.1,-4.7 -0.9,-6.9 -0.4,-1.1 -0.9,-2.3 -1.8,-3.1 -1.7,-1.8 -4.5,-2.2 -6.4,-0.5 -0.1,0 -0.2,0.1 -0.3,0.2 z\" id=\"path3\" /><path class=\"st2\" d=\"m 358.5,164.2 c -1,-1 0,-2.7 1,-3.7 5.8,-5.2 15.1,-4.6 21.8,-0.6 10.9,6.6 15.6,19.9 17.2,32.5 0.6,5.2 0.9,10.6 -0.5,15.7 -1.4,5.1 -4.6,9.9 -9.3,12.1 -1.1,0.5 -2.3,0.9 -3.4,0.5 -1.1,-0.4 -1.9,-1.8 -1.2,-2.8 -9.4,-13.6 -19,-26.8 -20.9,-43.2 -0.5,-4.1 -1.8,-7.4 -4.7,-10.5 z\" id=\"path4\" /><path class=\"st1\" d=\"m 220.05771,133 c 34.58546,-18 79.30862,-19.6 112.16964,-8.7 23.69194,7.9 41.3449,26.7 49.4515,50 7.11442,20.6 7.1441,43.6 3.04307,65.7 -7.4942,40.2 -26.19597,77.9 -48.96683,112.6 -12.56244,19 -24.57093,36 -44.23305,48.5 -38.69347,24.6 -88.90104,22.1 -129.28516,11.5 -19.50745,-5.1 -38.36842,-17.3 -44.32901,-37.3 -3.81479,-12.8 -2.07563,-27.6 4.5788,-40 13.50886,-24.8 46.15565,-38.4 50.82627,-67.9 1.36964,-8.7 -0.26842,-17.3 -1.62187,-25.7 -3.7989,-23.4 -5.37477,-45.8 6.68784,-68.7 9.46237,-17.7 24.28607,-31 41.6788,-40 z\" id=\"path6\" /><path class=\"st2\" d=\"m 264.5,174.9 c -0.5,0.5 -0.9,1 -1.3,1.6 -9,11.6 -12,27.9 -9.3,42.1 1.7,9 5.9,17.9 13.2,23.4 19.3,14.6 51.5,13.5 68.4,-1.5 24.4,-21.7 13,-67.6 -14,-78.8 -17.6,-7.2 -43.7,-1.6 -57,13.2 z\" id=\"path9\" /><g id=\"g11\"><path class=\"st2\" d=\"m 382.1,237.1 c 1.4,-0.1 2.9,-0.1 4.3,0.1 0.3,0 0.7,0.1 1,0.4 0.2,0.3 0.4,0.7 0.5,1.1 1,3.9 0.5,8.2 0.1,12.4 -0.1,0.9 -0.2,1.8 -0.6,2.6 -1,2.1 -3.1,2.7 -4.7,2.7 -0.1,0 -0.2,0 -0.3,-0.1 -0.3,-0.2 -0.3,-0.7 -0.2,-1.2 0.3,-5.9 -0.1,-11.9 -0.1,-18 0,0.1 0,0.1 0,0 z\" id=\"path10\" /><path class=\"st2\" d=\"m 378.7,236.8 c -1.4,0.4 -2.5,2 -2.8,4.4 -0.5,4.4 -0.7,8.9 -0.5,13.4 0,0.9 0.1,1.9 0.5,2.4 0.2,0.3 0.5,0.4 0.8,0.4 1.6,0.3 4.1,-0.6 5.6,-1 0,0 0,-5.2 -0.1,-8 -0.1,-2.8 -0.1,-6.1 -0.2,-8.9 0,-0.6 0,-1.5 0,-2.2 0.1,-0.7 -2.6,-0.7 -3.3,-0.5 z\" id=\"path11\" /></g><path class=\"st0\" d=\"m 358.3,231.8 c -0.3,2.2 0.1,4.7 1.7,7.4 2.6,4.4 7,6.1 11.9,5.8 8.9,-0.6 25.3,-5.4 27.5,-15.7 0.6,-3 -0.3,-6.1 -2.2,-8.5 -6.2,-7.8 -17.8,-5.7 -25.6,-2 -5.9,2.7 -12.4,7 -13.3,13 z\" id=\"path12\" /><path class=\"st3\" d=\"m 386.4,208.6 c 2.2,1.4 3.7,3.8 4,7 0.3,3.6 -1.4,7.5 -5,8.8 -2.9,1.1 -6.2,0.6 -9.1,-0.4 -2.9,-1 -5.8,-2.8 -6.8,-5.7 -0.7,-2 -0.3,-4.3 0.7,-6.1 1.1,-1.8 2.8,-3.2 4.7,-4.1 3.9,-1.8 8.4,-1.6 11.5,0.5 z\" id=\"path13\" /><path class=\"st0\" d=\"m 414.7,262.6 c 2.4,0.6 4.8,2.1 5.6,4.4 0.8,2.3 0.1,4.9 -1.6,6.7 -1.7,1.8 -4.2,2.5 -6.6,2.5 -0.8,0 -1.7,-0.1 -2.4,-0.5 -2.5,-1.1 -3.5,-4 -4.2,-6.6 -1.8,-6.8 3.6,-7.8 9.2,-6.5 z\" id=\"path14\" /><g id=\"g17\"><path class=\"st4\" d=\"m 267.1,284.7 c 2.3,-4.5 141.3,-36.2 144.7,-31.6 3.4,4.5 15.8,88.2 9,90.4 -6.8,2.3 -119.8,37.3 -126.6,35 -6.8,-2.3 -29.4,-89.3 -27.1,-93.8 z\" id=\"path15\" /><path class=\"st5\" d=\"m 294.2,378.5 c 0,0 54.3,-74.6 59.9,-76.9 5.7,-2.3 67.3,41.3 67.3,41.3\" id=\"path16\" /><path class=\"st4\" d=\"m 267,287.7 c 0,0 86,38.8 91.6,36.6 5.7,-2.3 53.1,-71.2 53.1,-71.2\" id=\"path17\" /></g><g id=\"g33\"><linearGradient id=\"SVGID_2_\" gradientUnits=\"userSpaceOnUse\" x1=\"265.03339\" y1=\"280.2951\" x2=\"275.3009\" y2=\"302.31369\"><stop offset=\"5.152266e-07\" style=\"stop-color:#71CAEE\" id=\"stop25\" /><stop offset=\"4.104413e-02\" style=\"stop-color:#83CAE2\" id=\"stop26\" /><stop offset=\"0.1206\" style=\"stop-color:#9FCACE\" id=\"stop27\" /><stop offset=\"0.2107\" style=\"stop-color:#B6CBBE\" id=\"stop28\" /><stop offset=\"0.3139\" style=\"stop-color:#C7CBB1\" id=\"stop29\" /><stop offset=\"0.4384\" style=\"stop-color:#D4CBA8\" id=\"stop30\" /><stop offset=\"0.6058\" style=\"stop-color:#DBCBA3\" id=\"stop31\" /><stop offset=\"1\" style=\"stop-color:#DDCBA2\" id=\"stop32\" /></linearGradient><path class=\"st7\" d=\"m 261.9,283.5 c -0.1,4.2 4.3,7.3 8.4,7.6 4.1,0.3 8.2,-1.3 12.2,-2.6 1.4,-0.4 2.9,-0.8 4.2,-0.2 1.8,0.9 2.7,4.1 1.8,5.9 -0.9,1.8 -3.4,3.5 -5.3,4.4 -6.5,3 -12.9,3.6 -19.9,2 -5.3,-1.2 -11.3,-4.3 -13,-13.5\" id=\"path32\" style=\"fill:url(#SVGID_2_)\" /><path class=\"st5\" d=\"m 261.9,283.5 c -0.1,4.2 4.3,7.3 8.4,7.6 4.1,0.3 8.2,-1.3 12.2,-2.6 1.4,-0.4 2.9,-0.8 4.2,-0.2 1.8,0.9 2.7,4.1 1.8,5.9 -0.9,1.8 -3.4,3.5 -5.3,4.4 -6.5,3 -12.9,3.6 -19.9,2 -5.3,-1.2 -11.3,-4.3 -13,-13.5\" id=\"path33\" /></g><path d=\"m 318.4,198.4 c -2,-0.3 -4.1,0.1 -5.9,1.3 -3.2,2.1 -4.7,6.2 -4.7,9.9 0,1.9 0.4,3.8 1.4,5.3 1.2,1.7 3.1,2.9 5.2,3.4 3.4,0.8 8.2,0.7 10.5,-2.5 1,-1.5 1.4,-3.3 1.5,-5.1 0.5,-5.7 -1.8,-11.4 -8,-12.3 z\" id=\"path34\" /><path class=\"st8\" d=\"m 320.4,203.3 c 0.9,0.3 1.7,0.8 2.1,1.7 0.4,0.8 0.4,1.7 0.3,2.5 -0.1,1 -0.6,2 -1.5,2.7 -0.7,0.5 -1.7,0.7 -2.6,0.5 -0.9,-0.2 -1.7,-0.8 -2.2,-1.6 -1.1,-1.6 -0.9,-4.4 0.9,-5.5 0.9,-0.4 2,-0.6 3,-0.3 z\" id=\"path35\" /></g></g></g></svg>"
@@ -56246,6 +56397,32 @@ function createNotificationOutputProvider(deps) {
56246
56397
  ...prepared,
56247
56398
  attachments
56248
56399
  };
56400
+ if (adapter.sendDirect) try {
56401
+ const result = await adapter.sendDirect(target.config, readyPrepared);
56402
+ return result.error === void 0 ? {
56403
+ ...result,
56404
+ renderedAs: prepared.renderedAs
56405
+ } : {
56406
+ ...result,
56407
+ error: stripUrls(result.error),
56408
+ renderedAs: prepared.renderedAs
56409
+ };
56410
+ } catch (err) {
56411
+ logger?.warn("sendDirect failed", { meta: {
56412
+ kind: target.kind,
56413
+ error: errMsg(err)
56414
+ } });
56415
+ return {
56416
+ success: false,
56417
+ error: `sendDirect failed: ${errMsg(err)}`,
56418
+ renderedAs: prepared.renderedAs
56419
+ };
56420
+ }
56421
+ if (!adapter.buildRequest) return {
56422
+ success: false,
56423
+ error: `No adapter send path for kind '${target.kind}'`,
56424
+ renderedAs: prepared.renderedAs
56425
+ };
56249
56426
  let plan;
56250
56427
  try {
56251
56428
  plan = adapter.buildRequest(target.config, readyPrepared);
@@ -56306,24 +56483,32 @@ function createNotificationOutputProvider(deps) {
56306
56483
  const iconUrlFor = deps.iconUrlFor;
56307
56484
  const iconMediaTypeFor = deps.iconMediaTypeFor;
56308
56485
  return {
56309
- listTargetKinds: async () => deps.adapters.map((a) => {
56310
- const descriptor = {
56311
- ...a.descriptor,
56312
- addonId,
56313
- destinationKeys: [...a.destinationKeys]
56314
- };
56315
- const iconUrl = iconUrlFor?.(a.descriptor.kind);
56316
- if (iconUrl === void 0) return descriptor;
56317
- const iconMediaType = iconMediaTypeFor?.(a.descriptor.kind);
56318
- return iconMediaType !== void 0 ? {
56319
- ...descriptor,
56320
- iconUrl,
56321
- iconMediaType
56322
- } : {
56323
- ...descriptor,
56324
- iconUrl
56325
- };
56326
- }),
56486
+ listTargetKinds: async () => {
56487
+ const kinds = [];
56488
+ for (const a of deps.adapters) {
56489
+ const base = a.hydrateDescriptor !== void 0 ? await a.hydrateDescriptor() : a.descriptor;
56490
+ const descriptor = {
56491
+ ...base,
56492
+ addonId,
56493
+ destinationKeys: [...a.destinationKeys]
56494
+ };
56495
+ const iconUrl = iconUrlFor?.(base.kind);
56496
+ if (iconUrl === void 0) {
56497
+ kinds.push(descriptor);
56498
+ continue;
56499
+ }
56500
+ const iconMediaType = iconMediaTypeFor?.(base.kind);
56501
+ kinds.push(iconMediaType !== void 0 ? {
56502
+ ...descriptor,
56503
+ iconUrl,
56504
+ iconMediaType
56505
+ } : {
56506
+ ...descriptor,
56507
+ iconUrl
56508
+ });
56509
+ }
56510
+ return kinds;
56511
+ },
56327
56512
  listTargets: async () => {
56328
56513
  return (await store.list()).map((target) => {
56329
56514
  const adapter = adapterByKind.get(target.kind);
@@ -56613,11 +56798,12 @@ var TargetStore = class {
56613
56798
  * Notifiers addon — a system `notification-output` collection provider.
56614
56799
  *
56615
56800
  * It hosts per-kind `NotifierAdapter`s (ntfy, pushover, gotify, telegram,
56616
- * discord, webhook, zentik, whatsapp) behind one uniform cap surface. Targets
56617
- * persist via the `settings-store` cap; the canonical notification is degraded
56618
- * per each target's declared capabilities by the pure `prepareNotification`
56619
- * engine in `@camstack/types`. The catalog is `NOTIFIER_ADAPTERS`
56620
- * (`adapters/registry.ts`).
56801
+ * discord, webhook, zentik, whatsapp, email) behind one uniform cap surface.
56802
+ * Targets persist via the `settings-store` cap; the canonical notification is
56803
+ * degraded per each target's declared capabilities by the pure
56804
+ * `prepareNotification` engine in `@camstack/types`. The catalog is
56805
+ * `buildNotifierAdapters` (`adapters/registry.ts`); email uses `sendDirect` →
56806
+ * `smtp-provider`.
56621
56807
  */
56622
56808
  var NotifiersAddon = class extends BaseAddon {
56623
56809
  constructor() {
@@ -56629,9 +56815,49 @@ var NotifiersAddon = class extends BaseAddon {
56629
56815
  if (!api) this.ctx.logger.warn("notifiers: no ctx.api — targets persist in-memory only");
56630
56816
  const store = new TargetStore(port);
56631
56817
  await store.init();
56818
+ const listReadyProviders = async () => {
56819
+ if (api === void 0) return [];
56820
+ const [providers, addons] = await Promise.all([api.addons.listCapabilityProviders.query({ capName: "smtp-provider" }), api.addons.list.query()]);
56821
+ const names = /* @__PURE__ */ new Map();
56822
+ for (const row of addons) {
56823
+ const id = row.manifest.id;
56824
+ names.set(id, row.manifest.name ?? row.manifest.packageDisplayName ?? id);
56825
+ }
56826
+ const out = [];
56827
+ for (const p of providers) {
56828
+ if (!p.isActive) continue;
56829
+ try {
56830
+ const status = await api.smtpProvider.getStatus.query({ addonId: p.addonId });
56831
+ if (!status.ready) continue;
56832
+ const name = names.get(p.addonId) ?? p.addonId;
56833
+ out.push({
56834
+ value: p.addonId,
56835
+ label: status.host !== "" ? `${name} — ${status.host}` : name,
56836
+ description: p.addonId
56837
+ });
56838
+ } catch {}
56839
+ }
56840
+ return out;
56841
+ };
56842
+ const adapters = buildNotifierAdapters(api !== void 0 ? {
56843
+ sendEmail: async (input) => {
56844
+ return { messageId: (await api.smtpProvider.sendEmail.mutate({
56845
+ to: typeof input.to === "string" ? input.to : [...input.to],
56846
+ subject: input.subject,
56847
+ text: input.text,
56848
+ ...input.html !== void 0 ? { html: input.html } : {},
56849
+ ...input.from !== void 0 ? { from: input.from } : {},
56850
+ ...input.addonId !== void 0 ? { addonId: input.addonId } : {}
56851
+ })).messageId };
56852
+ },
56853
+ listReadyProviders,
56854
+ resolveDefaultAddonId: async () => {
56855
+ return (await listReadyProviders())[0]?.value;
56856
+ }
56857
+ } : void 0);
56632
56858
  const provider = createNotificationOutputProvider({
56633
56859
  store,
56634
- adapters: NOTIFIER_ADAPTERS,
56860
+ adapters,
56635
56861
  logger: this.ctx.logger.child("notification-output"),
56636
56862
  iconUrlFor,
56637
56863
  iconMediaTypeFor
@@ -56641,7 +56867,7 @@ var NotifiersAddon = class extends BaseAddon {
56641
56867
  provider,
56642
56868
  logger: this.ctx.logger.child("nc-target-actions")
56643
56869
  });
56644
- this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: NOTIFIER_ADAPTERS.length } });
56870
+ this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: adapters.length } });
56645
56871
  return {
56646
56872
  providers: [{
56647
56873
  capability: notificationOutputCapability,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-notifiers",
3
- "version": "1.2.130",
3
+ "version": "1.2.132",
4
4
  "description": "System notifiers addon for CamStack — a `notification-output` collection provider hosting per-kind notifier adapters (ntfy, pushover, gotify, telegram, discord, webhook, zentik).",
5
5
  "keywords": [
6
6
  "camstack",