agent-relay-server 0.101.1 → 0.102.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/docs/openapi.json +1 -1
- package/package.json +3 -2
- package/public/assets/{MessageBubble-BEjK1-bi.js → MessageBubble-Dppo7pil.js} +2 -2
- package/public/assets/{MessageBubble-BEjK1-bi.js.map → MessageBubble-Dppo7pil.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-AqFEg7Yg.js → PlanGraphPanel-DO5OY2qa.js} +2 -2
- package/public/assets/{PlanGraphPanel-AqFEg7Yg.js.map → PlanGraphPanel-DO5OY2qa.js.map} +1 -1
- package/public/assets/{activity-D5EZw4bj.js → activity-BhuOs5ha.js} +2 -2
- package/public/assets/{activity-D5EZw4bj.js.map → activity-BhuOs5ha.js.map} +1 -1
- package/public/assets/{agent-profiles-CSISRm_L.js → agent-profiles-CT-vbfzd.js} +2 -2
- package/public/assets/{agent-profiles-CSISRm_L.js.map → agent-profiles-CT-vbfzd.js.map} +1 -1
- package/public/assets/{agents-szyYnkgD.js → agents-CLcgi8AD.js} +2 -2
- package/public/assets/{agents-szyYnkgD.js.map → agents-CLcgi8AD.js.map} +1 -1
- package/public/assets/{analytics-KzLQfGMO.js → analytics-BxGRljQc.js} +2 -2
- package/public/assets/{analytics-KzLQfGMO.js.map → analytics-BxGRljQc.js.map} +1 -1
- package/public/assets/{automation-DVSq4A1b.js → automation-CE1i1zpw.js} +2 -2
- package/public/assets/{automation-DVSq4A1b.js.map → automation-CE1i1zpw.js.map} +1 -1
- package/public/assets/{branch-state-badge-DgwkJ2rN.js → branch-state-badge-olhanssg.js} +2 -2
- package/public/assets/{branch-state-badge-DgwkJ2rN.js.map → branch-state-badge-olhanssg.js.map} +1 -1
- package/public/assets/channels-yHkm1dux.js +2 -0
- package/public/assets/channels-yHkm1dux.js.map +1 -0
- package/public/assets/{chat-patMKjsi.js → chat-DHdFZvAg.js} +2 -2
- package/public/assets/chat-DHdFZvAg.js.map +1 -0
- package/public/assets/{connectors-DVKBHNRV.js → connectors-DpR0V2hy.js} +2 -2
- package/public/assets/{connectors-DVKBHNRV.js.map → connectors-DpR0V2hy.js.map} +1 -1
- package/public/assets/{formatted-body-impl-Csr2DuEk.js → formatted-body-impl-BTeCfK4I.js} +2 -2
- package/public/assets/{formatted-body-impl-Csr2DuEk.js.map → formatted-body-impl-BTeCfK4I.js.map} +1 -1
- package/public/assets/index-CKhh9XRZ.css +2 -0
- package/public/assets/{index-D3woTUwp.js → index-DeDosfVE.js} +6 -6
- package/public/assets/{index-D3woTUwp.js.map → index-DeDosfVE.js.map} +1 -1
- package/public/assets/{insights-D2_Qry06.js → insights-C4aYlgsY.js} +2 -2
- package/public/assets/{insights-D2_Qry06.js.map → insights-C4aYlgsY.js.map} +1 -1
- package/public/assets/integrations-CEmYa5mW.js +2 -0
- package/public/assets/integrations-CEmYa5mW.js.map +1 -0
- package/public/assets/{maintenance-Dm56u8F9.js → maintenance-DVx3SiC_.js} +2 -2
- package/public/assets/{maintenance-Dm56u8F9.js.map → maintenance-DVx3SiC_.js.map} +1 -1
- package/public/assets/{managed-agents-DpX-cHsm.js → managed-agents-DUyOTdz1.js} +2 -2
- package/public/assets/{managed-agents-DpX-cHsm.js.map → managed-agents-DUyOTdz1.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-Df6EWwbg.js → markdown-preview-impl-D3hYfzGD.js} +2 -2
- package/public/assets/{markdown-preview-impl-Df6EWwbg.js.map → markdown-preview-impl-D3hYfzGD.js.map} +1 -1
- package/public/assets/{memory-b2AgvV7H.js → memory-2IfPgcnN.js} +2 -2
- package/public/assets/{memory-b2AgvV7H.js.map → memory-2IfPgcnN.js.map} +1 -1
- package/public/assets/{messages-C5yDD_oq.js → messages-CkOwABhw.js} +2 -2
- package/public/assets/{messages-C5yDD_oq.js.map → messages-CkOwABhw.js.map} +1 -1
- package/public/assets/{orchestrators-D3LecmEw.js → orchestrators-CEKO3eQ1.js} +2 -2
- package/public/assets/{orchestrators-D3LecmEw.js.map → orchestrators-CEKO3eQ1.js.map} +1 -1
- package/public/assets/{overview-CnGkrBNe.js → overview-D9BxevAq.js} +2 -2
- package/public/assets/{overview-CnGkrBNe.js.map → overview-D9BxevAq.js.map} +1 -1
- package/public/assets/{pairs-DXUdZOqn.js → pairs-CqT4D1uL.js} +2 -2
- package/public/assets/{pairs-DXUdZOqn.js.map → pairs-CqT4D1uL.js.map} +1 -1
- package/public/assets/{projects-1h41atqJ.js → projects-C_zwR-Fn.js} +2 -2
- package/public/assets/{projects-1h41atqJ.js.map → projects-C_zwR-Fn.js.map} +1 -1
- package/public/assets/{registry-BeGP37ot.js → registry-BLjwVXCW.js} +2 -2
- package/public/assets/{registry-BeGP37ot.js.map → registry-BLjwVXCW.js.map} +1 -1
- package/public/assets/{security-BMh_y43L.js → security-8a5_4U3S.js} +2 -2
- package/public/assets/{security-BMh_y43L.js.map → security-8a5_4U3S.js.map} +1 -1
- package/public/assets/{select-Cvi5OHz0.js → select-s4BE-T62.js} +2 -2
- package/public/assets/{select-Cvi5OHz0.js.map → select-s4BE-T62.js.map} +1 -1
- package/public/assets/{settings-DKSEwIVG.js → settings-BR30UpbJ.js} +2 -2
- package/public/assets/{settings-DKSEwIVG.js.map → settings-BR30UpbJ.js.map} +1 -1
- package/public/assets/{store-Ch-QzxPq.js → store-DNbkrqbU.js} +2 -2
- package/public/assets/store-DNbkrqbU.js.map +1 -0
- package/public/assets/{tasks-BybIAX-y.js → tasks-Bcr17zR3.js} +2 -2
- package/public/assets/{tasks-BybIAX-y.js.map → tasks-Bcr17zR3.js.map} +1 -1
- package/public/assets/{teams-CMteOToR.js → teams-B_HSkU9T.js} +2 -2
- package/public/assets/{teams-CMteOToR.js.map → teams-B_HSkU9T.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-DanOi7lh.js → terminal-viewer-impl-Ctp4csJ3.js} +2 -2
- package/public/assets/{terminal-viewer-impl-DanOi7lh.js.map → terminal-viewer-impl-Ctp4csJ3.js.map} +1 -1
- package/public/assets/{user-avatar-DG629icI.js → user-avatar-RliaJcaG.js} +2 -2
- package/public/assets/{user-avatar-DG629icI.js.map → user-avatar-RliaJcaG.js.map} +1 -1
- package/public/assets/{work-queue-DqW80DIy.js → work-queue-C0hpmlv8.js} +2 -2
- package/public/assets/{work-queue-DqW80DIy.js.map → work-queue-C0hpmlv8.js.map} +1 -1
- package/public/assets/{workspaces-CccVI6lA.js → workspaces-QXbQx_AG.js} +2 -2
- package/public/assets/{workspaces-CccVI6lA.js.map → workspaces-QXbQx_AG.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/src/adapter.ts +5 -0
- package/src/db/channels.ts +18 -2
- package/src/mcp/index.ts +5 -3
- package/src/mcp-egress-tools.ts +101 -0
- package/src/routes/integrations.ts +47 -13
- package/public/assets/channels-Dh_r7SnM.js +0 -2
- package/public/assets/channels-Dh_r7SnM.js.map +0 -1
- package/public/assets/chat-patMKjsi.js.map +0 -1
- package/public/assets/index-B95vV-dd.css +0 -2
- package/public/assets/integrations-AW8b4h7T.js +0 -2
- package/public/assets/integrations-AW8b4h7T.js.map +0 -1
- package/public/assets/store-Ch-QzxPq.js.map +0 -1
- package/src/mcp-ntfy-tools.ts +0 -35
- package/src/ntfy.ts +0 -222
package/src/mcp-ntfy-tools.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { MAX_BODY_BYTES } from "./config";
|
|
2
|
-
import { sendNtfyNotification, NTFY_PRIORITIES, type NtfyNotificationResult, type NtfyPriority } from "./ntfy";
|
|
3
|
-
import { optionalEnum } from "./validation";
|
|
4
|
-
import type { ToolDefinition } from "./mcp/types";
|
|
5
|
-
import { optionalString, optionalStringArray, stringField } from "./mcp/validation";
|
|
6
|
-
|
|
7
|
-
export const NTFY_TOOLS: ToolDefinition[] = [
|
|
8
|
-
{
|
|
9
|
-
name: "relay_notify_ntfy",
|
|
10
|
-
description: "Send an egress-only ntfy notification to the configured operator topic. Supports title, body, priority min→urgent, optional tags, and click-through link.",
|
|
11
|
-
requiredScopes: ["notifications:write"],
|
|
12
|
-
inputSchema: {
|
|
13
|
-
type: "object",
|
|
14
|
-
properties: {
|
|
15
|
-
title: { type: "string" },
|
|
16
|
-
body: { type: "string" },
|
|
17
|
-
priority: { type: "string", enum: NTFY_PRIORITIES },
|
|
18
|
-
tags: { type: "array", items: { type: "string" } },
|
|
19
|
-
click: { type: "string" },
|
|
20
|
-
},
|
|
21
|
-
required: ["title", "body"],
|
|
22
|
-
additionalProperties: false,
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
export async function relayNotifyNtfy(args: Record<string, unknown>): Promise<NtfyNotificationResult> {
|
|
28
|
-
return sendNtfyNotification({
|
|
29
|
-
title: stringField(args.title, "title", { required: true, max: 200 }),
|
|
30
|
-
body: stringField(args.body, "body", { required: true, maxBytes: MAX_BODY_BYTES }),
|
|
31
|
-
priority: optionalEnum(args.priority, "priority", NTFY_PRIORITIES) as NtfyPriority | undefined,
|
|
32
|
-
tags: optionalStringArray(args.tags, "tags"),
|
|
33
|
-
click: optionalString(args.click, "click", 1000),
|
|
34
|
-
});
|
|
35
|
-
}
|
package/src/ntfy.ts
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import { MAX_BODY_BYTES } from "./config";
|
|
3
|
-
import { getConfig } from "./config-store";
|
|
4
|
-
import { upsertAgent, ValidationError } from "./db";
|
|
5
|
-
import { registerChannelTypeSetting } from "./settings/modules";
|
|
6
|
-
import { cleanString, cleanStringArray } from "./validation";
|
|
7
|
-
import { errMessage, isRecord, type JSONSchema } from "agent-relay-sdk";
|
|
8
|
-
|
|
9
|
-
export const NTFY_CONFIG_NAMESPACE = "channel-type";
|
|
10
|
-
export const NTFY_CONFIG_KEY = "ntfy";
|
|
11
|
-
export const NTFY_CHANNEL_ID = "ntfy:default";
|
|
12
|
-
|
|
13
|
-
export const NTFY_PRIORITIES = ["min", "low", "default", "high", "urgent"] as const;
|
|
14
|
-
export type NtfyPriority = (typeof NTFY_PRIORITIES)[number];
|
|
15
|
-
|
|
16
|
-
export interface NtfyChannelConfig {
|
|
17
|
-
enabled: boolean;
|
|
18
|
-
serverUrl: string;
|
|
19
|
-
topic: string;
|
|
20
|
-
tokenEnv?: string;
|
|
21
|
-
username?: string;
|
|
22
|
-
passwordEnv?: string;
|
|
23
|
-
defaultTags: string[];
|
|
24
|
-
defaultClick?: string;
|
|
25
|
-
timeoutMs: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface NtfyNotificationInput {
|
|
29
|
-
title: string;
|
|
30
|
-
body: string;
|
|
31
|
-
priority?: NtfyPriority;
|
|
32
|
-
tags?: string[];
|
|
33
|
-
click?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface NtfyNotificationResult {
|
|
37
|
-
ok: true;
|
|
38
|
-
status: number;
|
|
39
|
-
channelId: string;
|
|
40
|
-
topic: string;
|
|
41
|
-
priority: NtfyPriority;
|
|
42
|
-
tags: string[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const DEFAULT_NTFY_CONFIG: NtfyChannelConfig = {
|
|
46
|
-
enabled: false,
|
|
47
|
-
serverUrl: "",
|
|
48
|
-
topic: "",
|
|
49
|
-
defaultTags: [],
|
|
50
|
-
timeoutMs: 15_000,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const NTFY_CONFIG_SCHEMA: JSONSchema = {
|
|
54
|
-
type: "object",
|
|
55
|
-
additionalProperties: false,
|
|
56
|
-
required: ["enabled", "serverUrl", "topic"],
|
|
57
|
-
properties: {
|
|
58
|
-
enabled: { type: "boolean" },
|
|
59
|
-
serverUrl: { type: "string", maxLength: 500 },
|
|
60
|
-
topic: { type: "string", maxLength: 200 },
|
|
61
|
-
tokenEnv: { type: "string", maxLength: 120, pattern: "^[A-Z0-9_]+$" },
|
|
62
|
-
username: { type: "string", maxLength: 120 },
|
|
63
|
-
passwordEnv: { type: "string", maxLength: 120, pattern: "^[A-Z0-9_]+$" },
|
|
64
|
-
defaultTags: { type: "array", maxItems: 20, items: { type: "string", maxLength: 40 } },
|
|
65
|
-
defaultClick: { type: "string", maxLength: 1000 },
|
|
66
|
-
timeoutMs: { type: "integer", minimum: 1_000, maximum: 60_000 },
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
registerChannelTypeSetting({
|
|
71
|
-
type: NTFY_CONFIG_KEY,
|
|
72
|
-
schema: NTFY_CONFIG_SCHEMA,
|
|
73
|
-
defaults: DEFAULT_NTFY_CONFIG,
|
|
74
|
-
display: {
|
|
75
|
-
label: "ntfy outgoing channel",
|
|
76
|
-
description: "Configures the built-in egress-only ntfy notification channel.",
|
|
77
|
-
order: 121,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export function getNtfyConfig(): NtfyChannelConfig {
|
|
82
|
-
const stored = getConfig<Partial<NtfyChannelConfig>>(NTFY_CONFIG_NAMESPACE, NTFY_CONFIG_KEY)?.value;
|
|
83
|
-
return normalizeNtfyConfig({ ...DEFAULT_NTFY_CONFIG, ...(isRecord(stored) ? stored : {}) });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function sendNtfyNotification(input: NtfyNotificationInput): Promise<NtfyNotificationResult> {
|
|
87
|
-
const config = getNtfyConfig();
|
|
88
|
-
validateSendConfig(config);
|
|
89
|
-
const title = cleanString(input.title, "title", { required: true, max: 200 })!;
|
|
90
|
-
const body = cleanString(input.body, "body", { required: true, max: MAX_BODY_BYTES })!;
|
|
91
|
-
const priority = input.priority ?? "default";
|
|
92
|
-
const tags = [...new Set([...(config.defaultTags ?? []), ...(input.tags ?? [])].map((tag) => tag.trim()).filter(Boolean))];
|
|
93
|
-
const click = cleanString(input.click ?? config.defaultClick, "click", { max: 1000 });
|
|
94
|
-
const url = ntfyTopicUrl(config);
|
|
95
|
-
const headers = ntfyHeaders({ config, title, priority, tags, click });
|
|
96
|
-
|
|
97
|
-
ensureNtfyChannelRegistered(config);
|
|
98
|
-
|
|
99
|
-
const controller = new AbortController();
|
|
100
|
-
const timeout = setTimeout(() => controller.abort(), config.timeoutMs);
|
|
101
|
-
let response: Response;
|
|
102
|
-
try {
|
|
103
|
-
response = await fetch(url, {
|
|
104
|
-
method: "POST",
|
|
105
|
-
headers,
|
|
106
|
-
body,
|
|
107
|
-
signal: controller.signal,
|
|
108
|
-
});
|
|
109
|
-
} catch (error) {
|
|
110
|
-
if (controller.signal.aborted) throw new Error(`ntfy notify timed out after ${config.timeoutMs}ms`);
|
|
111
|
-
throw new Error(`ntfy notify failed: ${errMessage(error)}`);
|
|
112
|
-
} finally {
|
|
113
|
-
clearTimeout(timeout);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!response.ok) {
|
|
117
|
-
const text = await response.text().catch(() => "");
|
|
118
|
-
throw new Error(`ntfy notify failed: HTTP ${response.status}${text ? `: ${text.slice(0, 300)}` : ""}`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return { ok: true, status: response.status, channelId: NTFY_CHANNEL_ID, topic: config.topic, priority, tags };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function normalizeNtfyConfig(value: unknown): NtfyChannelConfig {
|
|
125
|
-
if (!isRecord(value)) throw new ValidationError("ntfy channel config must be an object");
|
|
126
|
-
const timeoutMs = value.timeoutMs === undefined ? DEFAULT_NTFY_CONFIG.timeoutMs : value.timeoutMs;
|
|
127
|
-
if (typeof value.enabled !== "boolean") throw new ValidationError("ntfy.enabled must be a boolean");
|
|
128
|
-
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1_000 || timeoutMs > 60_000) {
|
|
129
|
-
throw new ValidationError("ntfy.timeoutMs must be an integer between 1000 and 60000");
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
enabled: value.enabled,
|
|
133
|
-
serverUrl: cleanString(value.serverUrl, "ntfy.serverUrl", { max: 500 }) ?? "",
|
|
134
|
-
topic: cleanString(value.topic, "ntfy.topic", { max: 200 }) ?? "",
|
|
135
|
-
tokenEnv: cleanString(value.tokenEnv, "ntfy.tokenEnv", { max: 120 }),
|
|
136
|
-
username: cleanString(value.username, "ntfy.username", { max: 120 }),
|
|
137
|
-
passwordEnv: cleanString(value.passwordEnv, "ntfy.passwordEnv", { max: 120 }),
|
|
138
|
-
defaultTags: cleanStringArray(value.defaultTags, "ntfy.defaultTags", { itemMax: 40, maxItems: 20 }) ?? [],
|
|
139
|
-
defaultClick: cleanString(value.defaultClick, "ntfy.defaultClick", { max: 1000 }),
|
|
140
|
-
timeoutMs,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function validateSendConfig(config: NtfyChannelConfig): void {
|
|
145
|
-
if (!config.enabled) throw new ValidationError("ntfy channel is disabled");
|
|
146
|
-
if (!config.serverUrl || !config.topic) {
|
|
147
|
-
throw new ValidationError("ntfy channel requires channel-type/ntfy serverUrl and topic");
|
|
148
|
-
}
|
|
149
|
-
if (config.topic.includes("/") || config.topic.includes("?") || config.topic.includes("#")) {
|
|
150
|
-
throw new ValidationError("ntfy.topic must be a single topic name, not a path or URL");
|
|
151
|
-
}
|
|
152
|
-
if (config.tokenEnv && (config.username || config.passwordEnv)) {
|
|
153
|
-
throw new ValidationError("ntfy auth must use tokenEnv or username/passwordEnv, not both");
|
|
154
|
-
}
|
|
155
|
-
if (config.passwordEnv && !config.username) throw new ValidationError("ntfy.username required when passwordEnv is set");
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function ntfyTopicUrl(config: NtfyChannelConfig): string {
|
|
159
|
-
let base: URL;
|
|
160
|
-
try {
|
|
161
|
-
base = new URL(config.serverUrl.endsWith("/") ? config.serverUrl : `${config.serverUrl}/`);
|
|
162
|
-
} catch {
|
|
163
|
-
throw new ValidationError("ntfy.serverUrl must be a valid URL");
|
|
164
|
-
}
|
|
165
|
-
return new URL(encodeURIComponent(config.topic), base).toString();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function ntfyHeaders(input: {
|
|
169
|
-
config: NtfyChannelConfig;
|
|
170
|
-
title: string;
|
|
171
|
-
priority: NtfyPriority;
|
|
172
|
-
tags: string[];
|
|
173
|
-
click?: string;
|
|
174
|
-
}): Record<string, string> {
|
|
175
|
-
const headers: Record<string, string> = {
|
|
176
|
-
Title: input.title,
|
|
177
|
-
Priority: String(priorityValue(input.priority)),
|
|
178
|
-
};
|
|
179
|
-
if (input.tags.length) headers.Tags = input.tags.join(",");
|
|
180
|
-
if (input.click) headers.Click = input.click;
|
|
181
|
-
const authorization = authHeader(input.config);
|
|
182
|
-
if (authorization) headers.Authorization = authorization;
|
|
183
|
-
return headers;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function authHeader(config: NtfyChannelConfig): string | undefined {
|
|
187
|
-
if (config.tokenEnv) {
|
|
188
|
-
const token = cleanString(process.env[config.tokenEnv], config.tokenEnv, { required: true, max: 4_000 })!;
|
|
189
|
-
return `Bearer ${token}`;
|
|
190
|
-
}
|
|
191
|
-
if (config.username && config.passwordEnv) {
|
|
192
|
-
const password = cleanString(process.env[config.passwordEnv], config.passwordEnv, { required: true, max: 4_000 })!;
|
|
193
|
-
return `Basic ${Buffer.from(`${config.username}:${password}`).toString("base64")}`;
|
|
194
|
-
}
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function priorityValue(priority: NtfyPriority): number {
|
|
199
|
-
return NTFY_PRIORITIES.indexOf(priority) + 1;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function ensureNtfyChannelRegistered(config: NtfyChannelConfig): void {
|
|
203
|
-
upsertAgent({
|
|
204
|
-
id: NTFY_CHANNEL_ID,
|
|
205
|
-
name: "ntfy",
|
|
206
|
-
kind: "channel",
|
|
207
|
-
tags: ["channel", "ntfy", "channel:ntfy"],
|
|
208
|
-
capabilities: ["channel", "ntfy", "notify"],
|
|
209
|
-
status: "online",
|
|
210
|
-
ready: true,
|
|
211
|
-
meta: {
|
|
212
|
-
kind: "channel",
|
|
213
|
-
provider: "ntfy",
|
|
214
|
-
accountId: "default",
|
|
215
|
-
channelType: "ntfy",
|
|
216
|
-
transport: "ntfy",
|
|
217
|
-
direction: "outbound",
|
|
218
|
-
displayName: "ntfy",
|
|
219
|
-
topicChannels: [config.topic],
|
|
220
|
-
},
|
|
221
|
-
});
|
|
222
|
-
}
|