import type { ChannelMeta } from "./plugins/types.js"; import type { ChannelId } from "./plugins/types.js"; import { getActivePluginRegistry } from "../plugins/runtime.js"; // Channel docking: add new channels here (order + meta + aliases), then // register the plugin in src/channels/plugins/index.ts and keep protocol IDs in sync. export const CHAT_CHANNEL_ORDER = [ "telegram", "whatsapp", "discord", "slack", "signal", "imessage", ] as const; export type ChatChannelId = (typeof CHAT_CHANNEL_ORDER)[number]; export const CHANNEL_IDS = [...CHAT_CHANNEL_ORDER] as const; export const DEFAULT_CHAT_CHANNEL: ChatChannelId = "whatsapp"; export type ChatChannelMeta = ChannelMeta; const WEBSITE_URL = "https://clawd.bot"; const CHAT_CHANNEL_META: Record = { telegram: { id: "telegram", label: "Telegram", selectionLabel: "Telegram (Bot API)", docsPath: "/channels/telegram", docsLabel: "telegram", blurb: "simplest way to get started — register a bot with @BotFather and get going.", selectionDocsPrefix: "", selectionDocsOmitLabel: true, selectionExtras: [WEBSITE_URL], }, whatsapp: { id: "whatsapp", label: "WhatsApp", selectionLabel: "WhatsApp (QR link)", docsPath: "/channels/whatsapp", docsLabel: "whatsapp", blurb: "works with your own number; recommend a separate phone + eSIM.", }, discord: { id: "discord", label: "Discord", selectionLabel: "Discord (Bot API)", docsPath: "/channels/discord", docsLabel: "discord", blurb: "very well supported right now.", }, slack: { id: "slack", label: "Slack", selectionLabel: "Slack (Socket Mode)", docsPath: "/channels/slack", docsLabel: "slack", blurb: "supported (Socket Mode).", }, signal: { id: "signal", label: "Signal", selectionLabel: "Signal (signal-cli)", docsPath: "/channels/signal", docsLabel: "signal", blurb: 'signal-cli linked device; more setup (David Reagans: "Hop on Discord.").', }, imessage: { id: "imessage", label: "iMessage", selectionLabel: "iMessage (imsg)", docsPath: "/channels/imessage", docsLabel: "imessage", blurb: "this is still a work in progress.", }, }; export const CHAT_CHANNEL_ALIASES: Record = { imsg: "imessage", }; const normalizeChannelKey = (raw?: string | null): string | undefined => { const normalized = raw?.trim().toLowerCase(); return normalized || undefined; }; export function listChatChannels(): ChatChannelMeta[] { return CHAT_CHANNEL_ORDER.map((id) => CHAT_CHANNEL_META[id]); } export function listChatChannelAliases(): string[] { return Object.keys(CHAT_CHANNEL_ALIASES); } export function getChatChannelMeta(id: ChatChannelId): ChatChannelMeta { return CHAT_CHANNEL_META[id]; } export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null { const normalized = normalizeChannelKey(raw); if (!normalized) return null; const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized; return CHAT_CHANNEL_ORDER.includes(resolved as ChatChannelId) ? (resolved as ChatChannelId) : null; } // Channel docking: prefer this helper in shared code. Importing from // `src/channels/plugins/*` can eagerly load channel implementations. export function normalizeChannelId(raw?: string | null): ChatChannelId | null { return normalizeChatChannelId(raw); } // Normalizes core chat channels plus any *already-loaded* plugin channels. // // Keep this light: we do not import core channel plugins here (those are "heavy" and can pull in // monitors, web login, etc). If plugins are not loaded (e.g. in many tests), only core channel IDs // resolve. export function normalizeAnyChannelId(raw?: string | null): ChannelId | null { const core = normalizeChatChannelId(raw); if (core) return core; const key = normalizeChannelKey(raw); if (!key) return null; const registry = getActivePluginRegistry(); if (!registry) return null; const hit = registry.channels.find((entry) => { const id = String(entry.plugin.id ?? "") .trim() .toLowerCase(); if (id && id === key) return true; return (entry.plugin.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === key); }); return (hit?.plugin.id as ChannelId | undefined) ?? null; } export function formatChannelPrimerLine(meta: ChatChannelMeta): string { return `${meta.label}: ${meta.blurb}`; } export function formatChannelSelectionLine( meta: ChatChannelMeta, docsLink: (path: string, label?: string) => string, ): string { const docsPrefix = meta.selectionDocsPrefix ?? "Docs:"; const docsLabel = meta.docsLabel ?? meta.id; const docs = meta.selectionDocsOmitLabel ? docsLink(meta.docsPath) : docsLink(meta.docsPath, docsLabel); const extras = (meta.selectionExtras ?? []).filter(Boolean).join(" "); return `${meta.label} — ${meta.blurb} ${docsPrefix ? `${docsPrefix} ` : ""}${docs}${extras ? ` ${extras}` : ""}`; }