Skip to main content

stacks-chat

Use when implementing chat messaging in Stacks - sending messages to Slack (webhooks, bot tokens, block kit), Discord (webhooks, bot tokens, embeds), Microsoft Teams (adaptive cards, webhooks), the BaseChatDriver abstraction, retry logic, multi-channel chat routing, or READING a person's conversations from iMessage, WhatsApp, Slack and Discord and archiving them there (the inbox drivers). Covers @stacksjs/chat.

ソース情報

リポジトリ
stacksjs/stacks
ソースの最終更新活動
2026年10月6日 00:03
検出された SKILL.md の言語
英語
スター
631
フォーク
20

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
stacks-chat
description
Use when implementing chat messaging in Stacks - sending messages to Slack (webhooks, bot tokens, block kit), Discord (webhooks, bot tokens, embeds), Microsoft Teams (adaptive cards, webhooks), the BaseChatDriver abstraction, retry logic, multi-channel chat routing, or READING a person's conversations from iMessage, WhatsApp, Slack and Discord and archiving them there (the inbox drivers). Covers @stacksjs/chat.
license
MIT
compatibility
Bun >= 1.3.0, TypeScript
allowed-tools
Read Edit Write Bash Grep Glob
# Stacks Chat Multi-driver chat messaging with Slack, Discord, and Microsoft Teams support. Each driver supports both webhook and bot token modes, with retry logic and structured message formats. ## Key Paths - Core package: `storage/framework/core/chat/src/` - Types: `storage/framework/core/types/src/chat.ts` ## Source Files ``` chat/src/ ├── index.ts # router, configure functions, type re-exports └── drivers/ ├── base.ts # BaseChatDriver abstract class ├── slack.ts # SlackDriver + sendWebhook() ├── discord.ts # DiscordDriver + sendWebhook() + sendEmbed() └── teams.ts # TeamsDriver + sendWebhook() + sendCard() ``` ## Router (index.ts) The main `send()` function routes to the specified driver. Default driver is `'slack'`: ```typescript import { send, sendToSlack, sendToDiscord, sendToTeams } from '@stacksjs/chat' // Route to driver (default: 'slack') await send(message, { driver: 'slack' }) await send(message, { driver: 'discord' }) await send(message, { driver: 'teams' }) // Direct webhook helpers await sendToSlack(webhookUrl, text, options?) await sendToDiscord(webhookUrl, content, options?) await sendToTeams(webhookUrl, text) ``` Settings come from `config/services.ts` by default, read on each send after the app's config has loaded: `SLACK_WEBHOOK_URL` / `SLACK_BOT_TOKEN`, `DISCORD_WEBHOOK_URL` / `DISCORD_BOT_TOKEN`, `TEAMS_WEBHOOK_URL`, and each driver's `*_MAX_RETRIES` / `*_RETRY_TIMEOUT`. A webhook URL from config passes the same HTTPS and host checks as one given to `configure*()`. The configure functions override config, for an app that keeps these elsewhere: ```typescript import { configureSlack, configureDiscord, configureTeams } from '@stacksjs/chat' configureSlack({ webhookUrl: '...', botToken: '...' }) configureDiscord({ webhookUrl: '...', botToken: '...' }) configureTeams({ webhookUrl: '...' }) ``` Exported types: `ChatDriver` (union), `SendOptions`, `SlackConfig`, `SlackMessage`, `SlackBlock`, `SlackAttachment`, `DiscordConfig`, `DiscordMessage`, `DiscordEmbed`, `TeamsConfig`, `TeamsMessage`, `TeamsAdaptiveCard`, `TeamsCardElement`, `TeamsCardAction` ## BaseChatDriver (base.ts) Abstract base class all drivers extend. Implements `ChatDriver` interface from `@stacksjs/types`: ```typescript abstract class BaseChatDriver implements ChatDriver { public abstract name: string protected config: Required<ChatDriverConfig> constructor(config?: ChatDriverConfig) // defaults: maxRetries=3, retryTimeout=1000 configure(config: ChatDriverConfig): void abstract send(message: ChatMessage, options?: RenderOptions): Promise<ChatResult> // Protected helpers protected validateMessage(message: ChatMessage): boolean // throws if no `to` or no `content`/`template` protected async handleError(error: unknown, message: ChatMessage): Promise<ChatResult> protected async handleSuccess(message: ChatMessage, messageId?: string): Promise<ChatResult> } ``` - `handleError()` logs the error, then calls `message.onError()` if defined, returns `ChatResult` with `success: false` - `handleSuccess()` calls `message.handle()` first, then `message.onSuccess()` if defined, returns `ChatResult` with `success: true` - Both handlers merge any partial result from callbacks into the final `ChatResult` ## ChatMessage Interface (from @stacksjs/types) ```typescript interface ChatMessage { to: string | string[] // recipient (channel ID, user ID, etc.) from?: { id?: string, name?: string, avatar?: string } subject?: string // title for rich messages content?: string // plain text content template?: string // template for rich message rendering data?: Record<string, any> // template data attachments?: ChatAttachment[] onSuccess?: () => void | Promise<void> | Partial<ChatResult> onError?: (error: Error) => void | Promise<void> | Partial<ChatResult> handle?: () => void | Promise<void> | Partial<ChatResult> [key: string]: any // custom platform-specific fields } ``` ## ChatResult Interface (from @stacksjs/types) ```typescript interface ChatResult { success: boolean message: string provider: string // 'slack' | 'discord' | 'teams' messageId?: string data?: Record<string, any> } ``` ## Slack Driver ### SlackConfig ```typescript interface SlackConfig { webhookUrl?: string botToken?: string maxRetries?: number // default: 3 retryTimeout?: number // default: 1000ms } ``` ### SlackMessage (for webhook payloads) ```typescript interface SlackMessage { channel?: string text?: string blocks?: SlackBlock[] attachments?: SlackAttachment[] username?: string iconEmoji?: string iconUrl?: string threadTs?: string mrkdwn?: boolean } ``` ### SlackBlock ```typescript interface SlackBlock { type: 'section' | 'divider' | 'header' | 'context' | 'actions' | 'image' text?: { type: 'plain_text' | 'mrkdwn', text: string, emoji?: boolean } accessory?: any elements?: any[] block_id?: string } ``` ### SlackAttachment ```typescript interface SlackAttachment { color?: string pretext?: string author_name?: string author_link?: string author_icon?: string title?: string title_link?: string text?: string fields?: Array<{ title: string, value: string, short?: boolean }> image_url?: string thumb_url?: string footer?: string footer_icon?: string ts?: number } ``` ### Slack Sending Modes 1. **Webhook mode**: If `config.webhookUrl` is set, sends via `POST` to webhook URL. Payload includes `text`, `username`, `mrkdwn: true`. If `message.template` exists, builds Block Kit blocks. 2. **Bot token mode**: If `config.botToken` is set, sends via Slack API `https://slack.com/api/chat.postMessage` with `Authorization: Bearer <token>`. Returns `ts` (timestamp) as message ID. 3. Throws `Error('Slack not configured: provide webhookUrl or botToken')` if neither is set. ### Direct Webhook Function ```typescript async function sendWebhook(webhookUrl: string, text: string, options?: Partial<SlackMessage>): Promise<ChatResult> ``` Merges `options` into payload, sends POST to the provided webhook URL. ### Block Building When `message.template` is set, `buildBlocks()` creates: - A `header` block from `message.subject` (if present) - A `section` block from `message.content` with `mrkdwn` type ### Exports - `SlackDriver` class (also as `Driver`) - `driver` -- pre-instantiated `SlackDriver` singleton - `send()`, `sendWebhook()`, `configure()` functions ## Discord Driver ### DiscordConfig ```typescript interface DiscordConfig { webhookUrl?: string botToken?: string maxRetries?: number // default: 3 retryTimeout?: number // default: 1000ms } ``` ### DiscordEmbed ```typescript interface DiscordEmbed { title?: string description?: string url?: string color?: number // integer color value (e.g. 0x5865F2) timestamp?: string // ISO 8601 footer?: { text: string, icon_url?: string } image?: { url: string } thumbnail?: { url: string } author?: { name: string, url?: string, icon_url?: string } fields?: Array<{ name: string, value: string, inline?: boolean }> } ``` ### DiscordMessage ```typescript interface DiscordMessage { content?: string username?: string avatarUrl?: string tts?: boolean embeds?: DiscordEmbed[] allowedMentions?: { parse?: Array<'roles' | 'users' | 'everyone'> roles?: string[] users?: string[] } } ``` ### Discord Sending Modes 1. **Webhook mode**: POST to `config.webhookUrl`. Handles `204` (empty success) responses. Returns `id` from response. 2. **Bot token mode**: POST to `https://discord.com/api/v10/channels/{channelId}/messages` with `Authorization: Bot <token>`. Uses `message.to` as the channel ID. ### Embed Building When `message.subject` or `message.template` is set, `buildEmbed()` creates an embed with: - `title` from `message.subject` - `description` from `message.content` - Default color: `0x5865F2` (Discord blurple) - `timestamp`: current ISO date ### Extra Functions ```typescript async function sendWebhook(webhookUrl: string, content: string, options?: Partial<DiscordMessage>): Promise<ChatResult> async function sendEmbed(webhookUrl: string, embed: DiscordEmbed, options?: Partial<DiscordMessage>): Promise<ChatResult> ``` `sendEmbed()` wraps the embed inside `sendWebhook()` with empty content. ### Exports - `DiscordDriver` class (also as `Driver`) - `driver` -- pre-instantiated singleton - `send()`, `sendWebhook()`, `sendEmbed()`, `configure()` functions ## Teams Driver ### TeamsConfig ```typescript interface TeamsConfig { webhookUrl?: string maxRetries?: number // default: 3 retryTimeout?: number // default: 1000ms } ``` ### TeamsAdaptiveCard ```typescript interface TeamsAdaptiveCard { type: 'AdaptiveCard' version: string // '1.4' body: TeamsCardElement[] actions?: TeamsCardAction[] $schema?: string // 'http://adaptivecards.io/schemas/adaptive-card.json' } ``` ### TeamsCardElement ```typescript interface TeamsCardElement { type: 'TextBlock' | 'Image' | 'Container' | 'ColumnSet' | 'Column' | 'FactSet' | 'ImageSet' text?: string size?: 'Small' | 'Default' | 'Medium' | 'Large' | 'ExtraLarge' weight?: 'Lighter' | 'Default' | 'Bolder' color?: 'Default' | 'Dark' | 'Light' | 'Accent' | 'Good' | 'Warning' | 'Attention' wrap?: boolean url?: string altText?: string items?: TeamsCardElement[] columns?: TeamsCardElement[] width?: string facts?: Array<{ title: string, value: string }> images?: Array<{ type: 'Image', url: string, size?: string }> } ``` ### TeamsCardAction ```typescript interface TeamsCardAction { type: 'Action.OpenUrl' | 'Action.Submit' | 'Action.ShowCard' title: string url?: string data?: Record<string, any> card?: TeamsAdaptiveCard } ``` ### TeamsMessage ```typescript interface TeamsMessage { type?: 'message' summary?: string text?: string attachments?: Array<{ contentType: 'application/vnd.microsoft.card.adaptive' content: TeamsAdaptiveCard }> } ``` ### Teams Sending - Uses `config.webhookUrl` or falls back to `message.to` as the webhook URL - Validates the URL as a Teams Workflows (Power Automate) webhook on https: `*.environment.api.powerplatform.com`, `*.api.powerautomate.com`, `flow.microsoft.com` or `*.logic.azure.com` (`.us` / `.cn` too). An Office 365 connector URL (`*.webhook.office.com`) is refused with a migration message - Microsoft retired those connectors in May 2026 - Every message is an Adaptive Card in `attachments`, which the Workflows webhook template requires; a plain message is a card holding its text - Rich messages (with subject or template): the Adaptive Card v1.4 has: - Title as `TextBlock` (size: Large, weight: Bolder) - Content as `TextBlock` (wrap: true) - Timestamp as `TextBlock` (size: Small, color: Dark) ### Extra Functions ```typescript async function sendWebhook(webhookUrl: string, text: string): Promise<ChatResult> async function sendCard(webhookUrl: string, card: TeamsAdaptiveCard, summary?: string): Promise<ChatResult> ``` ### Exports - `TeamsDriver` class (also as `Driver`) - `driver` -- pre-instantiated singleton - `send()`, `sendWebhook()`, `sendCard()`, `configure()` functions ## Inbox drivers (reading and archiving) `send()` pushes messages out. The inbox drivers go the other way: they list a person's conversations, read messages, notice new ones (a per-conversation `cursor`), and archive a conversation *in the real app* as far as the provider allows. Source: `chat/src/inbox/`. One interface, `InboxDriver`: ```typescript
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る