feat: Slack platform + Claude-powered chat support widget

- Add Slack as customer-facing messaging platform (client, 4 MCP tools, dashboard card)
- Add /api/chat endpoint powered by Claude Haiku with SquareMCP system prompt
- Add embeddable chat-widget.js injected into all 3 sites (docs, app, www)
- Add ANTHROPIC_API_KEY, serve product/ as static files
- Update Platform type to include slack

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Garfield
2026-05-15 10:44:24 -04:00
parent 05b4a30759
commit 4bf93d6763
15 changed files with 547 additions and 4 deletions

View File

@@ -14,6 +14,7 @@ import { searchTweets, getUserProfile, getUserTweets, createTweet, uploadVideoAn
import { getUserProfile as getTikTokProfile, getCreatorInfo, createVideo, getVideoStatus } from './clients/tiktok.js';
import { getMe as getSnapchatMe, createSnap, getAdAccounts } from './clients/snapchat.js';
import { getPage, getPosts, createPost as createFacebookPost, createPhotoPost, createVideoPost as createFacebookVideoPost } from './clients/facebook.js';
import { getMe as getSlackMe, getChannels as getSlackChannels, sendMessage as sendSlackMessage, getMessages as getSlackMessages } from './clients/slack.js';
const ACCOUNT_PARAM = {
account: {
@@ -443,6 +444,59 @@ export const tools: Tool[] = [
},
},
// ── Slack tools ──────────────────────────────────────────────
{
name: 'slack_get_me',
description:
'Verify the Slack bot is connected and return workspace info. Use to confirm credentials are working.',
inputSchema: {
type: 'object',
properties: {
account: { type: 'string', description: 'Which Slack account to use (default: "default")' },
},
},
},
{
name: 'slack_get_channels',
description:
'List Slack channels the bot has access to. Use to find channel IDs before sending messages.',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Max channels to return (default: 100)' },
account: { type: 'string', description: 'Which Slack account to use (default: "default")' },
},
},
},
{
name: 'slack_send_message',
description:
'Send a message to a Slack channel. Uses the default channel if channel_id is omitted.',
inputSchema: {
type: 'object',
required: ['text'],
properties: {
channel_id: { type: 'string', description: 'Slack channel ID (e.g. C0123456). Uses default channel if omitted.' },
text: { type: 'string', description: 'Message text. Supports Slack mrkdwn formatting.' },
account: { type: 'string', description: 'Which Slack account to use (default: "default")' },
},
},
},
{
name: 'slack_get_messages',
description:
'Get recent messages from a Slack channel.',
inputSchema: {
type: 'object',
required: ['channel_id'],
properties: {
channel_id: { type: 'string', description: 'Slack channel ID' },
limit: { type: 'number', description: 'Max messages to return (default: 10)' },
account: { type: 'string', description: 'Which Slack account to use (default: "default")' },
},
},
},
// ── Instagram tools ──────────────────────────────────────────
{
name: 'instagram_get_profile',
@@ -738,7 +792,7 @@ async function resolveEmailCtx(args: Record<string, unknown>, customer?: Custome
const PLATFORM_PREFIXES = [
'linkedin', 'obsidian', 'whatsapp', 'telegram', 'discord',
'instagram', 'twitter', 'tiktok', 'snapchat', 'facebook',
'instagram', 'twitter', 'tiktok', 'snapchat', 'facebook', 'slack',
];
function toolPlatform(name: string): string {
@@ -984,6 +1038,36 @@ export async function handleToolCall(
}, customer);
break;
// ── Slack ───────────────────────────────────────────────────
case 'slack_get_me':
result = await getSlackMe({
account: args.account as string | undefined,
}, customer);
break;
case 'slack_get_channels':
result = await getSlackChannels({
limit: args.limit as number | undefined,
account: args.account as string | undefined,
}, customer);
break;
case 'slack_send_message':
result = await sendSlackMessage({
channel_id: args.channel_id as string | undefined,
text: args.text as string,
account: args.account as string | undefined,
}, customer);
break;
case 'slack_get_messages':
result = await getSlackMessages({
channel_id: args.channel_id as string,
limit: args.limit as number | undefined,
account: args.account as string | undefined,
}, customer);
break;
// ── Instagram ───────────────────────────────────────────────
case 'instagram_get_profile':
result = await getInstagramProfile({