Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b890d65
feat: add Slack bot app for AI-powered Cal.com scheduling
dhairyashiil Feb 26, 2026
94e4677
add oauth
dhairyashiil Feb 26, 2026
b759e03
update vercel url
dhairyashiil Feb 26, 2026
ad61b00
add callback deeplink for oauth flow
dhairyashiil Feb 27, 2026
741b109
refactor: rename slack/ directory to chat/ for multi-platform support
dhairyashiil Mar 6, 2026
1604cdc
feat: install @chat-adapter/telegram package
dhairyashiil Mar 6, 2026
dbb7ba9
feat: add Telegram support and generalize multi-platform abstractions
dhairyashiil Mar 6, 2026
9cfaec5
fix: resolve type errors in bot.ts (adapter type, extractTeamId refs)
dhairyashiil Mar 6, 2026
9fa38e6
fix: filter bookings to current user only to prevent admin data leak
dhairyashiil Mar 7, 2026
6c32d54
fix: resolve intermittent bot silence on Vercel
dhairyashiil Mar 7, 2026
ee7d65f
better code
dhairyashiil Mar 7, 2026
1d2f1b9
improve loggers
dhairyashiil Mar 7, 2026
965f0fc
duplicate message id fix
dhairyashiil Mar 7, 2026
413adfc
fix ai rate limit error and handle fallback
dhairyashiil Mar 7, 2026
073583f
cancel booking error
dhairyashiil Mar 8, 2026
5949d77
admin bot gives replies checkpoint
dhairyashiil Mar 9, 2026
69e95f5
telegram bot group message fix
dhairyashiil Mar 9, 2026
1f38ded
make slack app redirect ready
dhairyashiil Mar 10, 2026
2065b72
address cubic review
dhairyashiil Mar 10, 2026
65bb78a
update skill md
dhairyashiil Mar 10, 2026
4667b30
typecheck and lint
dhairyashiil Mar 10, 2026
6f6dd11
Merge remote-tracking branch 'upstream/main' into feat/slack-app
dhairyashiil Mar 10, 2026
db79628
update readme, typecheck & lint command, and address review point
dhairyashiil Mar 10, 2026
1287040
remove eslint from chat folder
dhairyashiil Mar 10, 2026
1f141cb
cubic review points
dhairyashiil Mar 10, 2026
1d39a94
fix failing typecheck
dhairyashiil Mar 10, 2026
fa1da8d
lock versions
dhairyashiil Mar 10, 2026
21b0fc3
address earlier cubic reviews
dhairyashiil Mar 10, 2026
f83135a
address cubic review
dhairyashiil Mar 10, 2026
4b47722
merge: resolve conflicts with main (package.json workspaces + bun.lock)
devin-ai-integration[bot] Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions .agents/skills/chat-sdk/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
name: chat-sdk
description: >
Build multi-platform chat bots with Chat SDK (`chat` npm package). Use when developers want to
(1) Build a Slack, Teams, Google Chat, Discord, GitHub, or Linear bot,
(2) Use the Chat SDK to handle mentions, messages, reactions, slash commands, cards, modals, or streaming,
(3) Set up webhook handlers for chat platforms,
(4) Send interactive cards or stream AI responses to chat platforms.
Triggers on "chat sdk", "chat bot", "slack bot", "teams bot", "discord bot", "@chat-adapter",
building bots that work across multiple chat platforms.
---

# Chat SDK

Unified TypeScript SDK for building chat bots across Slack, Teams, Google Chat, Discord, GitHub, and Linear. Write bot logic once, deploy everywhere.

## Critical: Read the bundled docs

The `chat` package ships with full documentation in `chat/node_modules/chat/docs/` and TypeScript source types. **Always read these before writing code:**

```
chat/node_modules/chat/docs/ # Full documentation (MDX files)
chat/node_modules/chat/dist/ # Built types (.d.ts files)
```

Key docs to read based on task:
- `docs/getting-started.mdx` — setup guides
- `docs/usage.mdx` — event handlers, threads, messages, channels
- `docs/streaming.mdx` — AI streaming with AI SDK
- `docs/cards.mdx` — JSX interactive cards
- `docs/actions.mdx` — button/dropdown handlers
- `docs/modals.mdx` — form dialogs (Slack only)
- `docs/adapters/*.mdx` — platform-specific adapter setup
- `docs/state/*.mdx` — state adapter config (Redis, ioredis, memory)

Also read the TypeScript types from `node_modules/chat/dist/` to understand the full API surface.

## Quick start

```typescript
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createRedisState } from "@chat-adapter/state-redis";

const bot = new Chat({
userName: "mybot",
adapters: {
slack: createSlackAdapter({
botToken: process.env.SLACK_BOT_TOKEN!,
signingSecret: process.env.SLACK_SIGNING_SECRET!,
}),
},
state: createRedisState({ url: process.env.REDIS_URL! }),
});

bot.onNewMention(async (thread) => {
await thread.subscribe();
await thread.post("Hello! I'm listening to this thread.");
});

bot.onSubscribedMessage(async (thread, message) => {
await thread.post(`You said: ${message.text}`);
});
```

## Core concepts

- **Chat** — main entry point, coordinates adapters and routes events
- **Adapters** — platform-specific (Slack, Teams, GChat, Discord, GitHub, Linear)
- **State** — pluggable persistence (Redis for prod, memory for dev)
- **Thread** — conversation thread with `post()`, `subscribe()`, `startTyping()`
- **Message** — normalized format with `text`, `formatted` (mdast AST), `raw`
- **Channel** — container for threads, supports listing and posting

## Event handlers

| Handler | Trigger |
|---------|---------|
| `onNewMention` | Bot @-mentioned in unsubscribed thread |
| `onSubscribedMessage` | Any message in subscribed thread |
| `onNewMessage(regex)` | Messages matching pattern in unsubscribed threads |
| `onSlashCommand("/cmd")` | Slash command invocations |
| `onReaction(emojis)` | Emoji reactions added/removed |
| `onAction(actionId)` | Button clicks and dropdown selections |
| `onAssistantThreadStarted` | Slack Assistants API thread opened |
| `onAppHomeOpened` | Slack App Home tab opened |

## Streaming

Pass any `AsyncIterable<string>` to `thread.post()`. Works with AI SDK's `textStream`:

```typescript
import { ToolLoopAgent } from "ai";
const agent = new ToolLoopAgent({ model: "anthropic/claude-4.5-sonnet" });

bot.onNewMention(async (thread, message) => {
const result = await agent.stream({ prompt: message.text });
await thread.post(result.textStream);
});
```

## Cards (JSX)

Set `jsxImportSource: "chat"` in tsconfig. Components: `Card`, `CardText`, `Button`, `Actions`, `Fields`, `Field`, `Select`, `SelectOption`, `Image`, `Divider`, `LinkButton`, `Section`, `RadioSelect`.

```tsx
await thread.post(
<Card title="Order #1234">
<CardText>Your order has been received!</CardText>
<Actions>
<Button id="approve" style="primary">Approve</Button>
<Button id="reject" style="danger">Reject</Button>
</Actions>
</Card>
);
```

## Packages

| Package | Purpose |
|---------|---------|
| `chat` | Core SDK |
| `@chat-adapter/slack` | Slack |
| `@chat-adapter/teams` | Microsoft Teams |
| `@chat-adapter/gchat` | Google Chat |
| `@chat-adapter/discord` | Discord |
| `@chat-adapter/github` | GitHub Issues |
| `@chat-adapter/linear` | Linear Issues |
| `@chat-adapter/state-redis` | Redis state (production) |
| `@chat-adapter/state-ioredis` | ioredis state (alternative) |
| `@chat-adapter/state-memory` | In-memory state (development) |

## Webhook setup

Each adapter exposes a webhook handler via `bot.webhooks.{platform}`. Wire these to your HTTP framework's routes (e.g. Next.js API routes, Hono, Express).
1 change: 1 addition & 0 deletions .claude/skills/chat-sdk
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,44 @@ bun run check:ci
bun run typecheck:all
```

## Chat Bot — Telegram Setup

The `chat/` directory contains a multi-platform chat bot. Slack is the primary adapter; Telegram is optional.

### Prerequisites

1. Create a bot with [BotFather](https://t.me/BotFather) on Telegram (`/newbot`)
2. Copy the bot token and username

### Environment Variables

Add to your `chat/.env`:

```
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
TELEGRAM_BOT_USERNAME=YourBotName
```

### Register the Webhook

Point Telegram at your deployed chat app:

```sh
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook" \
-d "url=https://your-domain.com/api/webhooks/telegram"
```

### Supported Commands

| Command | Description |
|---------|-------------|
| `/start` | Show help card |
| `/help` | Show help card |
| `/link` | Connect your Cal.com account |
| `/unlink` | Disconnect your Cal.com account |

Any other message mentioning the bot triggers the AI scheduling assistant.

## Links

- [Cal.com](https://cal.com)
Expand Down
3 changes: 2 additions & 1 deletion apps/extension/entrypoints/background/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="chrome" />
import type { OAuthTokens } from "../../types/oauth";

import type { Booking } from "../../types/bookings.types";
import type { OAuthTokens } from "../../types/oauth";

const DEV_API_KEY = import.meta.env.EXPO_PUBLIC_CAL_API_KEY as string | undefined;
const IS_DEV_MODE = Boolean(DEV_API_KEY && DEV_API_KEY.length > 0);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useColorScheme } from "react-native";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { useCallback, useRef } from "react";
import { useColorScheme } from "react-native";
import {
AvailabilityDetailScreen,
type AvailabilityDetailScreenHandle,
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/app/(tabs)/(availability)/availability-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Ionicons } from "@expo/vector-icons";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { useCallback, useRef } from "react";
import { Text, useColorScheme } from "react-native";
import { AppPressable } from "@/components/AppPressable";
import { HeaderButtonWrapper } from "@/components/HeaderButtonWrapper";
import {
AvailabilityDetailScreen,
type AvailabilityDetailScreenHandle,
} from "@/components/screens/AvailabilityDetailScreen";
import { HeaderButtonWrapper } from "@/components/HeaderButtonWrapper";
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -15,7 +16,6 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { getColors } from "@/constants/colors";
import { AppPressable } from "@/components/AppPressable";

// Type for action handlers exposed by AvailabilityDetailScreen
type ActionHandlers = {
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app/(tabs)/(bookings)/booking-detail.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { BookingDetailScreen } from "@/components/screens/BookingDetailScreen";
import { useAuth } from "@/contexts/AuthContext";
import { useBookingByUid } from "@/hooks/useBookings";
import { showErrorAlert, showInfoAlert, showSuccessAlert } from "@/utils/alerts";
import { type BookingActionsResult, getBookingActions } from "@/utils/booking-actions";
import { getMeetingUrl } from "@/utils/booking";
import { type BookingActionsResult, getBookingActions } from "@/utils/booking-actions";
import { openInDefaultBrowser } from "@/utils/browser";

// Empty actions result for when no booking is loaded
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app/(tabs)/(bookings)/booking-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
import { useAuth } from "@/contexts/AuthContext";
import { useBookingByUid } from "@/hooks/useBookings";
import { showErrorAlert, showInfoAlert, showSuccessAlert } from "@/utils/alerts";
import { type BookingActionsResult, getBookingActions } from "@/utils/booking-actions";
import { getMeetingUrl } from "@/utils/booking";
import { type BookingActionsResult, getBookingActions } from "@/utils/booking-actions";
import { openInDefaultBrowser } from "@/utils/browser";

// Empty actions result for when no booking is loaded
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app/(tabs)/(bookings)/index.ios.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NativeStackHeaderItemMenuAction } from "@react-navigation/native-stack";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import { Stack, useLocalSearchParams } from "expo-router";
import { useState, useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { useColorScheme } from "react-native";

import { BookingListScreen } from "@/components/booking-list-screen/BookingListScreen";
Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/app/(tabs)/(bookings)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Ionicons } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { useState, useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { Text, TextInput, useColorScheme, View } from "react-native";
import { AppPressable } from "@/components/AppPressable";
import { BookingListScreen } from "@/components/booking-list-screen/BookingListScreen";
import { Header } from "@/components/Header";
import {
Expand All @@ -10,10 +11,9 @@ import {
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { AppPressable } from "@/components/AppPressable";
import { type BookingFilter, useActiveBookingFilter } from "@/hooks/useActiveBookingFilter";
import { useEventTypes } from "@/hooks";
import { getColors } from "@/constants/colors";
import { useEventTypes } from "@/hooks";
import { type BookingFilter, useActiveBookingFilter } from "@/hooks/useActiveBookingFilter";

const VALID_FILTERS: BookingFilter[] = [
"upcoming",
Expand Down
10 changes: 5 additions & 5 deletions apps/mobile/app/(tabs)/(event-types)/event-type-detail.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Ionicons } from "@expo/vector-icons";
import * as Clipboard from "expo-clipboard";
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
import { Stack, useLocalSearchParams, useNavigation, useRouter } from "expo-router";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
Expand All @@ -16,27 +17,27 @@ import {
View,
} from "react-native";
import { AppPressable } from "@/components/AppPressable";
import { HeaderButtonWrapper } from "@/components/HeaderButtonWrapper";
import { AdvancedTab } from "@/components/event-type-detail/tabs/AdvancedTab";
import { AvailabilityTab } from "@/components/event-type-detail/tabs/AvailabilityTab";
import { BasicsTab } from "@/components/event-type-detail/tabs/BasicsTab";
import { LimitsTab } from "@/components/event-type-detail/tabs/LimitsTab";
import { RecurringTab } from "@/components/event-type-detail/tabs/RecurringTab";
import { truncateTitle } from "@/components/event-type-detail/utils";
import { buildPartialUpdatePayload } from "@/components/event-type-detail/utils/buildPartialUpdatePayload";
import { HeaderButtonWrapper } from "@/components/HeaderButtonWrapper";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { truncateTitle } from "@/components/event-type-detail/utils";
import { buildPartialUpdatePayload } from "@/components/event-type-detail/utils/buildPartialUpdatePayload";
import { useCreateEventType, useDeleteEventType, useUpdateEventType } from "@/hooks";
import {
CalComAPIService,
type ConferencingOption,
type EventType,
type Schedule,
} from "@/services/calcom";
import { useCreateEventType, useDeleteEventType, useUpdateEventType } from "@/hooks";
import type { LocationItem, LocationOptionGroup } from "@/types/locations";
import {
showErrorAlert,
Expand All @@ -52,7 +53,6 @@ import {
validateLocationItem,
} from "@/utils/locationHelpers";
import { safeLogError } from "@/utils/safeLogger";
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";

// Type definitions for extended EventType fields not in the base type
interface EventTypeExtended {
Expand Down
5 changes: 2 additions & 3 deletions apps/mobile/app/(tabs)/(event-types)/index.ios.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Haptics from "expo-haptics";
import { Ionicons } from "@expo/vector-icons";
import * as Clipboard from "expo-clipboard";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import * as Haptics from "expo-haptics";
import { Image } from "expo-image";
import { Stack, useRouter } from "expo-router";
import { useMemo, useState } from "react";
Expand All @@ -19,6 +19,7 @@ import {
import { EmptyScreen } from "@/components/EmptyScreen";
import { EventTypeListItem } from "@/components/event-type-list-item/EventTypeListItem";
import { EventTypeListSkeleton } from "@/components/event-type-list-item/EventTypeListItemSkeleton";
import { getColors } from "@/constants/colors";
import {
useCreateEventType,
useDeleteEventType,
Expand All @@ -35,8 +36,6 @@ import { getEventDuration } from "@/utils/getEventDuration";
import { offlineAwareRefresh } from "@/utils/network";
import { slugify } from "@/utils/slugify";

import { getColors } from "@/constants/colors";

export default function EventTypesIOS() {
const router = useRouter();
const [searchQuery, setSearchQuery] = useState("");
Expand Down
3 changes: 1 addition & 2 deletions apps/mobile/app/(tabs)/(event-types)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Text as AlertDialogText } from "@/components/ui/text";
import { getColors } from "@/constants/colors";
import {
useCreateEventType,
useDeleteEventType,
Expand All @@ -47,8 +48,6 @@ import { normalizeMarkdown } from "@/utils/normalizeMarkdown";
import { shadows } from "@/utils/shadows";
import { slugify } from "@/utils/slugify";

import { getColors } from "@/constants/colors";

export default function EventTypes() {
const router = useRouter();
const [searchQuery, setSearchQuery] = useState("");
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/app/(tabs)/(more)/index.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import {
} from "react-native";
import { LandingPagePicker } from "@/components/LandingPagePicker";
import { LogoutConfirmModal } from "@/components/LogoutConfirmModal";
import { getColors } from "@/constants/colors";
import { useAuth } from "@/contexts/AuthContext";
import { useQueryContext } from "@/contexts/QueryContext";
import { useUserProfile } from "@/hooks";
import { type LandingPage, useUserPreferences } from "@/hooks/useUserPreferences";
import { showErrorAlert, showSuccessAlert, showNotAvailableAlert } from "@/utils/alerts";
import { showErrorAlert, showNotAvailableAlert, showSuccessAlert } from "@/utils/alerts";
import { openInAppBrowser } from "@/utils/browser";
import { getAvatarUrl } from "@/utils/getAvatarUrl";
import { getColors } from "@/constants/colors";

interface MoreMenuItem {
name: string;
Expand Down
Loading
Loading