export declare const GOODVIBES_NTFY_ORIGIN = "goodvibes-sdk"; export declare const GOODVIBES_NTFY_ORIGIN_HEADER = "X-Goodvibes-Origin"; export declare const GOODVIBES_NTFY_OUTBOUND_TAG = "goodvibes-sdk-outbound"; export declare const GOODVIBES_NTFY_CHAT_TOPIC = "goodvibes-chat"; export declare const GOODVIBES_NTFY_AGENT_TOPIC = "goodvibes-agent"; export declare const GOODVIBES_NTFY_REMOTE_TOPIC = "goodvibes-ntfy"; export declare const GOODVIBES_NTFY_DEFAULT_TOPICS: readonly ["goodvibes-chat", "goodvibes-agent", "goodvibes-ntfy"]; export interface GoodVibesNtfyTopicConfig { readonly chatTopic?: string | null | undefined; readonly agentTopic?: string | null | undefined; readonly remoteTopic?: string | null | undefined; } export interface GoodVibesNtfyTopics { readonly chatTopic: string; readonly agentTopic: string; readonly remoteTopic: string; readonly all: readonly string[]; } export declare function resolveGoodVibesNtfyTopics(config?: GoodVibesNtfyTopicConfig): GoodVibesNtfyTopics; export declare function createNtfyLiveSubscriptionSince(nowMs?: number): string; export interface NtfyPublishOptions { readonly title?: string | undefined; readonly priority?: 1 | 2 | 3 | 4 | 5 | undefined; readonly tags?: readonly string[] | undefined; readonly click?: string | undefined; readonly attach?: string | undefined; readonly actions?: readonly string[] | undefined; readonly markGoodVibesOrigin?: boolean | undefined; /** * Publish even if the identical body just went to this topic. For the rare * caller whose repeat IS the message (a heartbeat, a re-ask). Everything * else is deduplicated, see `ntfyOutboundDedup`. */ readonly allowDuplicate?: boolean | undefined; } /** * Last-line-of-defense suppression for identical outbound notifications. * * The reply pipeline already sends deltas and never republishes a body it just * sent, but that memory is per AGENT. A phone shows one topic, and several * publishers converge on it: the reply pipeline, surface delivery, automation * delivery, and every agent in a chain. Nothing above this point can see that * two of them are about to send byte-identical text seconds apart, which is * how one workstream produced nine copies of one line. * * Scope, stated plainly: this is per PROCESS. It cannot suppress a duplicate * published by a second, older client process still attached to the same * daemon, that is what the client compatibility floor is for. */ declare class NtfyOutboundDedup { private readonly seen; /** True when this exact body may be published now. */ claim(topic: string, title: string | undefined, body: string, now?: number): boolean; private prune; /** Test seam: forget everything. */ reset(): void; } export declare const ntfyOutboundDedup: NtfyOutboundDedup; /** * Drop a click target whose host is a bind address rather than a destination. * Returns the original string for every usable URL, including loopback (which * is genuinely correct when the notification is opened on the host itself). */ export declare function dropUnreachableClickTarget(click: string | undefined): string | undefined; /** True when the text is (or contains) a URL whose host is a wildcard bind address. */ export declare function containsWildcardHostUrl(value: unknown): boolean; export interface NtfyMessage { readonly id?: string | undefined; readonly time?: number | undefined; readonly event: 'open' | 'keepalive' | 'message' | 'message_delete' | 'message_clear' | 'poll_request' | string; readonly topic?: string | undefined; readonly message?: string | undefined; readonly title?: string | undefined; readonly priority?: number | undefined; readonly tags?: readonly string[] | undefined; readonly click?: string | undefined; readonly actions?: readonly unknown[] | undefined; readonly [key: string]: unknown; } export interface NtfySubscribeOptions { readonly since?: string | undefined; readonly scheduled?: boolean | undefined; readonly poll?: boolean | undefined; readonly filters?: Record | undefined; readonly signal?: AbortSignal | undefined; readonly reconnect?: boolean | undefined; readonly reconnectDelayMs?: number | undefined; } export interface NtfyWebSocketOptions extends NtfySubscribeOptions { readonly WebSocketImpl?: typeof WebSocket | undefined; } /** * Reduce a title to something an HTTP header can actually carry. * * Replaces the punctuation that shows up in generated titles with ASCII * equivalents, drops anything still outside printable ASCII (control bytes and * header separators included, so a title can never inject a header), and caps * the length. Returns undefined when nothing usable is left, which publishes * the notification untitled rather than not at all. */ export declare function toHeaderSafeTitle(title: string | undefined): string | undefined; export declare class NtfyIntegration { private readonly baseUrl; private readonly token?; constructor(baseUrl?: string, token?: string | undefined); publish(topic: string, message: string, options?: NtfyPublishOptions): Promise; buildSubscribeUrl(topic: string, mode?: 'json' | 'sse' | 'raw' | 'ws', options?: NtfySubscribeOptions): string; poll(topic: string, options?: Omit): Promise; subscribeJsonStream(topic: string, onMessage: (message: NtfyMessage) => void | Promise, options?: NtfySubscribeOptions): Promise; connectWebSocket(topic: string, onMessage: (message: NtfyMessage) => void | Promise, options?: NtfyWebSocketOptions): WebSocket; private buildAuthHeaders; } export declare function isGoodVibesNtfyDeliveryEcho(message: Record): boolean; export {}; //# sourceMappingURL=ntfy.d.ts.map