---
name: slack-automation
description: "Automate Slack workspace operations including messaging, search, channel management, and reaction workflows through Composio's Slack toolkit."
risk: critical
source: community
date_added: "2026-02-27"
---

# Slack Automation via Rube MCP

Automate Slack workspace operations including messaging, search, channel management, and reaction workflows through Composio's Slack toolkit.

## Prerequisites

- Rube MCP must be connected (RUBE_SEARCH_TOOLS available)
- Active Slack connection via `RUBE_MANAGE_CONNECTIONS` with toolkit `slack`
- Always call `RUBE_SEARCH_TOOLS` first to get current tool schemas

## Setup

**Get Rube MCP**: Add `https://rube.app/mcp` as an MCP server in your client configuration. No API keys needed — just add the endpoint and it works.

1. Verify Rube MCP is available by confirming `RUBE_SEARCH_TOOLS` responds
2. Call `RUBE_MANAGE_CONNECTIONS` with toolkit `slack`
3. If connection is not ACTIVE, follow the returned auth link to complete Slack OAuth
4. Confirm connection status shows ACTIVE before running any workflows

## Core Workflows

### 1. Send Messages to Channels

**When to use**: User wants to post a message to a Slack channel or DM

**Tool sequence**:
1. `SLACK_FIND_CHANNELS` - Resolve channel name to channel ID [Prerequisite]
2. `SLACK_LIST_ALL_CHANNELS` - Fallback if FIND_CHANNELS returns empty/ambiguous results [Fallback]
3. `SLACK_FIND_USERS` - Resolve user for DMs or @mentions [Optional]
4. `SLACK_OPEN_DM` - Open/reuse a DM channel if messaging a user directly [Optional]
5. `SLACK_SEND_MESSAGE` - Post the message with resolved channel ID [Required]
6. `SLACK_UPDATES_A_SLACK_MESSAGE` - Edit the posted message if corrections needed [Optional]

**Key parameters**:
- `channel`: Channel ID or name (without '#' prefix)
- `markdown_text`: Preferred field for formatted messages (supports headers, bold, italic, code blocks)
- `text`: Raw text fallback (deprecated in favor of markdown_text)
- `thread_ts`: Timestamp of parent message to reply in a thread
- `blocks`: Block Kit layout blocks (deprecated, use markdown_text)

**Pitfalls**:
- `SLACK_FIND_CHANNELS` requires `query` param; missing it errors with "Invalid request data provided"
- `SLACK_SEND_MESSAGE` requires valid channel plus one of markdown_text/text/blocks/attachments
- Invalid block payloads return error=invalid_blocks (max 50 blocks)
- Replies become top-level posts if `thread_ts` is omitted
- Persist `response.data.channel` and `response.data.message.ts` from SEND_MESSAGE for edit/thread operations

### 2. Search Messages and Conversations

**When to use**: User wants to find specific messages across the workspace

**Tool sequence**:
1. `SLACK_FIND_CHANNELS` - Resolve channel for scoped search with `in:#channel` [Optional]
2. `SLACK_FIND_USERS` - Resolve user for author filter with `from:@user` [Optional]
3. `SLACK_SEARCH_MESSAGES` - Run keyword search across accessible conversations [Required]
4. `SLACK_FETCH_MESSAGE_THREAD_FROM_A_CONVERSATION` - Expand threads for relevant hits [Required]

**Key parameters**:
- `query`: Search string supporting modifiers (`in:#channel`, `from:@user`, `before:YYYY-MM-DD`, `after:YYYY-MM-DD`, `has:link`, `has:file`)
- `count`: Results per page (max 100), or total with auto_paginate=true
- `sort`: 'score' (relevance) or 'timestamp' (chronological)
- `sort_dir`: 'asc' or 'desc'

**Pitfalls**:
- Validation fails if `query` is missing/empty
- `ok=true` can still mean no hits (`response.data.messages.total=0`)
- Matches are under `response.data.messages.matches` (sometimes also `response.data_preview.messages.matches`)
- `match.text` may be empty/truncated; key info can appear in `matches[].attachments[]`
- Thread expansion via FETCH_MESSAGE_THREAD can truncate when `response.data.has_more=true`; paginate via `response_metadata.next_cursor`

### 3. Manage Channels and Users

**When to use**: User wants to list channels, users, or workspace info

**Tool sequence**:
1. `SLACK_FETCH_TEAM_INFO` - Validate connectivity and get workspace identity [Required]
2. `SLACK_LIST_ALL_CHANNELS` - Enumerate public channels [Required]
3. `SLACK_LIST_CONVERSATIONS` - Include private channels and DMs [Optional]
4. `SLACK_LIST_ALL_USERS` - List workspace members [Required]
5. `SLACK_RETRIEVE_CONVERSATION_INFORMATION` - Get detailed channel metadata [Optional]
6. `SLACK_LIST_USER_GROUPS_FOR_TEAM_WITH_OPTIONS` - List user groups [Optional]

**Key parameters**:
- `cursor`: Pagination cursor from `response_metadata.next_cursor`
- `limit`: Results per page (default varies; set explicitly for large workspaces)
- `types`: Channel types filter ('public_channel', 'private_channel', 'im', 'mpim')

**Pitfalls**:
- Workspace metadata is nested under `response.data.team`, not top-level
- `SLACK_LIST_ALL_CHANNELS` returns public channels only; use `SLACK_LIST_CONVERSATIONS` for private/IM coverage
- `SLACK_LIST_ALL_USERS` can hit HTTP 429 rate limits; honor Retry-After header
- Always paginate via `response_metadata.next_cursor` until empty; de-duplicate by `id`

### 4. React to and Thread Messages

**When to use**: User wants to add reactions or manage threaded conversations

**Tool sequence**:
1. `SLACK_SEARCH_MESSAGES` or `SLACK_FETCH_CONVERSATION_HISTORY` - Find the target message [Prerequisite]
2. `SLACK_ADD_REACTION_TO_AN_ITEM` - Add an emoji reaction [Required]
3. `SLACK_FETCH_ITEM_REACTIONS` - List reactions on a message [Optional]
4. `SLACK_REMOVE_REACTION_FROM_ITEM` - Remove a reaction [Optional]
5. `SLACK_SEND_MESSAGE` - Reply in thread using `thread_ts` [Optional]
6. `SLACK_FETCH_MESSAGE_THREAD_FROM_A_CONVERSATION` - Read full thread [Optional]

**Key parameters**:
- `channel`: Channel ID where the message lives
- `timestamp` / `ts`: Message timestamp (unique identifier like '1234567890.123456')
- `name`: Emoji name without colons (e.g., 'thumbsup', 'wave::skin-tone-3')
- `thread_ts`: Parent message timestamp for threaded replies

**Pitfalls**:
- Reactions require exact channel ID + message timestamp pair
- Emoji names use Slack's naming convention without colons
- `SLACK_FETCH_CONVERSATION_HISTORY` only returns main channel timeline, NOT threaded replies
- Use `SLACK_FETCH_MESSAGE_THREAD_FROM_A_CONVERSATION` with parent's `thread_ts` to get thread replies

### 5. Schedule Messages

**When to use**: User wants to schedule a message for future delivery

**Tool sequence**:
1. `SLACK_FIND_CHANNELS` - Resolve channel ID [Prerequisite]
2. `SLACK_SCHEDULE_MESSAGE` - Schedule the message with `post_at` timestamp [Required]

**Key parameters**:
- `channel`: Resolved channel ID
- `post_at`: Unix timestamp for delivery (up to 120 days in advance)
- `text` / `blocks`: Message content

**Pitfalls**:
- Scheduling is limited to 120 days in advance
- `post_at` must be a Unix timestamp, not ISO 8601

## Common Patterns

### ID Resolution
Always resolve display names to IDs before operations:
- **Channel name -> Channel ID**: `SLACK_FIND_CHANNELS` with `query` param
- **User name -> User ID**: `SLACK_FIND_USERS` with `search_query` or `email`
- **DM channel**: `SLACK_OPEN_DM` with resolved user IDs

### Pagination
Most list endpoints use cursor-based pagination:
- Follow `response_metadata.next_cursor` until empty
- Set explicit `limit` values (e.g., 100-200) for reliable paging
- De-duplicate results by `id` across pages

### Message Formatting
- Prefer `markdown_text` over `text` or `blocks` for formatted messages
- Use `<@USER_ID>` format to mention users (not @username)
- Use `\n` for line breaks in markdown_text

## Known Pitfalls

- **Channel resolution**: `SLACK_FIND_CHANNELS` can return empty results if channel is private and bot hasn't been invited
- **Rate limits**: `SLACK_LIST_ALL_USERS` and other list endpoints can hit HTTP 429; honor Retry-After header
- **Nested responses**: Results may be nested under `response.data.results[0].response.data` in wrapped executions
- **Thread vs channel**: `SLACK_FETCH_CONVERSATION_HISTORY` returns main timeline only; use `SLACK_FETCH_MESSAGE_THREAD_FROM_A_CONVERSATION` for thread replies
- **Message editing**: Requires both `channel` and original message `ts`; persist these from SEND_MESSAGE response
- **Search delays**: Recently posted messages may not appear in search results immediately
- **Scope limitations**: Missing OAuth scopes can cause 403 errors; check with `SLACK_GET_APP_PERMISSION_SCOPES`

## Quick Reference

| Task | Tool Slug | Key Params |
|------|-----------|------------|
| Find channels | `SLACK_FIND_CHANNELS` | `query` |
| List all channels | `SLACK_LIST_ALL_CHANNELS` | `limit`, `cursor`, `types` |
| Send message | `SLACK_SEND_MESSAGE` | `channel`, `markdown_text` |
| Edit message | `SLACK_UPDATES_A_SLACK_MESSAGE` | `channel`, `ts`, `markdown_text` |
| Search messages | `SLACK_SEARCH_MESSAGES` | `query`, `count`, `sort` |
| Get thread | `SLACK_FETCH_MESSAGE_THREAD_FROM_A_CONVERSATION` | `channel`, `ts` |
| Add reaction | `SLACK_ADD_REACTION_TO_AN_ITEM` | `channel`, `name`, `timestamp` |
| Find users | `SLACK_FIND_USERS` | `search_query` or `email` |
| List users | `SLACK_LIST_ALL_USERS` | `limit`, `cursor` |
| Open DM | `SLACK_OPEN_DM` | user IDs |
| Schedule message | `SLACK_SCHEDULE_MESSAGE` | `channel`, `post_at`, `text` |
| Get channel info | `SLACK_RETRIEVE_CONVERSATION_INFORMATION` | channel ID |
| Channel history | `SLACK_FETCH_CONVERSATION_HISTORY` | `channel`, `oldest`, `latest` |
| Workspace info | `SLACK_FETCH_TEAM_INFO` | (none) |

## When to Use
This skill is applicable to execute the workflow or actions described in the overview.

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: slack-bot-builder
description: Build Slack apps using the Bolt framework across Python,
  JavaScript, and Java. Covers Block Kit for rich UIs, interactive components,
  slash commands, event handling, OAuth installation flows, and Workflow Builder
  integration.
risk: critical
source: vibeship-spawner-skills (Apache 2.0)
date_added: 2026-02-27
---

# Slack Bot Builder

Build Slack apps using the Bolt framework across Python, JavaScript, and Java.
Covers Block Kit for rich UIs, interactive components, slash commands,
event handling, OAuth installation flows, and Workflow Builder integration.
Focus on best practices for production-ready Slack apps.

## Patterns

### Bolt App Foundation Pattern

The Bolt framework is Slack's recommended approach for building apps.
It handles authentication, event routing, request verification, and
HTTP request processing so you can focus on app logic.

Key benefits:
- Event handling in a few lines of code
- Security checks and payload validation built-in
- Organized, consistent patterns
- Works for experiments and production

Available in: Python, JavaScript (Node.js), Java

**When to use**: Starting any new Slack app,Migrating from legacy Slack APIs,Building production Slack integrations

# Python Bolt App
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import os

# Initialize with tokens from environment
app = App(
    token=os.environ["SLACK_BOT_TOKEN"],
    signing_secret=os.environ["SLACK_SIGNING_SECRET"]
)

# Handle messages containing "hello"
@app.message("hello")
def handle_hello(message, say):
    """Respond to messages containing 'hello'."""
    user = message["user"]
    say(f"Hey there <@{user}>!")

# Handle slash command
@app.command("/ticket")
def handle_ticket_command(ack, body, client):
    """Handle /ticket slash command."""
    # Acknowledge immediately (within 3 seconds)
    ack()

    # Open a modal for ticket creation
    client.views_open(
        trigger_id=body["trigger_id"],
        view={
            "type": "modal",
            "callback_id": "ticket_modal",
            "title": {"type": "plain_text", "text": "Create Ticket"},
            "submit": {"type": "plain_text", "text": "Submit"},
            "blocks": [
                {
                    "type": "input",
                    "block_id": "title_block",
                    "element": {
                        "type": "plain_text_input",
                        "action_id": "title_input"
                    },
                    "label": {"type": "plain_text", "text": "Title"}
                },
                {
                    "type": "input",
                    "block_id": "desc_block",
                    "element": {
                        "type": "plain_text_input",
                        "multiline": True,
                        "action_id": "desc_input"
                    },
                    "label": {"type": "plain_text", "text": "Description"}
                },
                {
                    "type": "input",
                    "block_id": "priority_block",
                    "element": {
                        "type": "static_select",
                        "action_id": "priority_select",
                        "options": [
                            {"text": {"type": "plain_text", "text": "Low"}, "value": "low"},
                            {"text": {"type": "plain_text", "text": "Medium"}, "value": "medium"},
                            {"text": {"type": "plain_text", "text": "High"}, "value": "high"}
                        ]
                    },
                    "label": {"type": "plain_text", "text": "Priority"}
                }
            ]
        }
    )

# Handle modal submission
@app.view("ticket_modal")
def handle_ticket_submission(ack, body, client, view):
    """Handle ticket modal submission."""
    ack()

    # Extract values from the view
    values = view["state"]["values"]
    title = values["title_block"]["title_input"]["value"]
    desc = values["desc_block"]["desc_input"]["value"]
    priority = values["priority_block"]["priority_select"]["selected_option"]["value"]
    user_id = body["user"]["id"]

    # Create ticket in your system
    ticket_id = create_ticket(title, desc, priority, user_id)

    # Notify user
    client.chat_postMessage(
        channel=user_id,
        text=f"Ticket #{ticket_id} created: {title}"
    )

# Handle button clicks
@app.action("approve_button")
def handle_approval(ack, body, client):
    """Handle approval button click."""
    ack()

    # Get context from the action
    user = body["user"]["id"]
    action_value = body["actions"][0]["value"]

    # Update the message to remove interactive elements
    # (Best practice: prevent double-clicks)
    client.chat_update(
        channel=body["channel"]["id"],
        ts=body["message"]["ts"],
        text=f"Approved by <@{user}>",
        blocks=[]  # Remove interactive blocks
    )

# Listen for app_home_opened events
@app.event("app_home_opened")
def update_home_tab(client, event):
    """Update the Home tab when user opens it."""
    client.views_publish(
        user_id=event["user"],
        view={
            "type": "home",
            "blocks": [
                {
                    "type": "section",
                    "text": {
                        "type": "mrkdwn",
                        "text": "*Welcome to the Ticket Bot!*"
                    }
                },
                {
                    "type": "actions",
                    "elements": [
                        {
                            "type": "button",
                            "text": {"type": "plain_text", "text": "Create Ticket"},
                            "action_id": "create_ticket_button"
                        }
                    ]
                }
            ]
        }
    )

# Socket Mode for development (no public URL needed)
if __name__ == "__main__":
    handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
    handler.start()

# For production, use HTTP mode with a web server
# from flask import Flask, request
# from slack_bolt.adapter.flask import SlackRequestHandler
#
# flask_app = Flask(__name__)
# handler = SlackRequestHandler(app)
#
# @flask_app.route("/slack/events", methods=["POST"])
# def slack_events():
#     return handler.handle(request)

### Anti_patterns

- Not acknowledging requests within 3 seconds
- Blocking operations in the ack handler
- Hardcoding tokens in source code
- Not using Socket Mode for development

### Block Kit UI Pattern

Block Kit is Slack's UI framework for building rich, interactive messages.
Compose messages using blocks (sections, actions, inputs) and elements
(buttons, menus, text inputs).

Limits:
- Up to 50 blocks per message
- Up to 100 blocks in modals/Home tabs
- Block text limited to 3000 characters

Use Block Kit Builder to prototype: https://app.slack.com/block-kit-builder

**When to use**: Building rich message layouts,Adding interactive components to messages,Creating forms in modals,Building Home tab experiences

from slack_bolt import App
import os

app = App(token=os.environ["SLACK_BOT_TOKEN"])

def build_notification_blocks(incident: dict) -> list:
    """Build Block Kit blocks for incident notification."""
    severity_emoji = {
        "critical": ":red_circle:",
        "high": ":large_orange_circle:",
        "medium": ":large_yellow_circle:",
        "low": ":white_circle:"
    }

    return [
        # Header
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": f"{severity_emoji.get(incident['severity'], '')} Incident Alert"
            }
        },
        # Details section
        {
            "type": "section",
            "fields": [
                {
                    "type": "mrkdwn",
                    "text": f"*Incident:*\n{incident['title']}"
                },
                {
                    "type": "mrkdwn",
                    "text": f"*Severity:*\n{incident['severity'].upper()}"
                },
                {
                    "type": "mrkdwn",
                    "text": f"*Service:*\n{incident['service']}"
                },
                {
                    "type": "mrkdwn",
                    "text": f"*Reported:*\n<!date^{incident['timestamp']}^{date_short} {time}|{incident['timestamp']}>"
                }
            ]
        },
        # Description
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"*Description:*\n{incident['description'][:2000]}"
            }
        },
        # Divider
        {"type": "divider"},
        # Action buttons
        {
            "type": "actions",
            "block_id": f"incident_actions_{incident['id']}",
            "elements": [
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "Acknowledge"},
                    "style": "primary",
                    "action_id": "acknowledge_incident",
                    "value": incident['id']
                },
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "Resolve"},
                    "style": "danger",
                    "action_id": "resolve_incident",
                    "value": incident['id'],
                    "confirm": {
                        "title": {"type": "plain_text", "text": "Resolve Incident?"},
                        "text": {"type": "mrkdwn", "text": "Are you sure this incident is resolved?"},
                        "confirm": {"type": "plain_text", "text": "Yes, Resolve"},
                        "deny": {"type": "plain_text", "text": "Cancel"}
                    }
                },
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "View Details"},
                    "action_id": "view_incident",
                    "value": incident['id'],
                    "url": f"https://incidents.example.com/{incident['id']}"
                }
            ]
        },
        # Context footer
        {
            "type": "context",
            "elements": [
                {
                    "type": "mrkdwn",
                    "text": f"Incident ID: {incident['id']} | <https://runbook.example.com/{incident['service']}|View Runbook>"
                }
            ]
        }
    ]

def send_incident_notification(channel: str, incident: dict):
    """Send incident notification with Block Kit."""
    blocks = build_notification_blocks(incident)

    app.client.chat_postMessage(
        channel=channel,
        text=f"Incident: {incident['title']}",  # Fallback for notifications
        blocks=blocks
    )

# Handle button actions
@app.action("acknowledge_incident")
def handle_acknowledge(ack, body, client):
    """Handle incident acknowledgment."""
    ack()

    incident_id = body["actions"][0]["value"]
    user = body["user"]["id"]

    # Update your system
    acknowledge_incident(incident_id, user)

    # Update message to show acknowledgment
    original_blocks = body["message"]["blocks"]

    # Add acknowledgment to context
    original_blocks[-1]["elements"].append({
        "type": "mrkdwn",
        "text": f":white_check_mark: Acknowledged by <@{user}>"
    })

    # Remove acknowledge button (prevent double-click)
    action_block = next(b for b in original_blocks if b.get("block_id", "").startswith("incident_actions"))
    action_block["elements"] = [e for e in action_block["elements"] if e["action_id"] != "acknowledge_incident"]

    client.chat_update(
        channel=body["channel"]["id"],
        ts=body["message"]["ts"],
        blocks=original_blocks
    )

# Interactive select menus
def build_user_selector_blocks():
    """Build blocks with user selector."""
    return [
        {
            "type": "section",
            "text": {"type": "mrkdwn", "text": "Assign this task:"},
            "accessory": {
                "type": "users_select",
                "action_id": "assign_user",
                "placeholder": {"type": "plain_text", "text": "Select assignee"}
            }
        }
    ]

# Overflow menu for more options
def build_task_blocks(task: dict):
    """Build task blocks with overflow menu."""
    return [
        {
            "type": "section",
            "text": {"type": "mrkdwn", "text": f"*{task['title']}*"},
            "accessory": {
                "type": "overflow",
                "action_id": "task_overflow",
                "options": [
                    {
                        "text": {"type": "plain_text", "text": "Edit"},
                        "value": f"edit_{task['id']}"
                    },
                    {
                        "text": {"type": "plain_text", "text": "Delete"},
                        "value": f"delete_{task['id']}"
                    },
                    {
                        "text": {"type": "plain_text", "text": "Share"},
                        "value": f"share_{task['id']}"
                    }
                ]
            }
        }
    ]

### Anti_patterns

- Exceeding 50 blocks per message
- Not providing fallback text for accessibility
- Hardcoding action_ids (use dynamic IDs when needed)
- Not handling button clicks idempotently

### OAuth Installation Pattern

Enable users to install your app in their workspaces via OAuth 2.0.
Bolt handles most of the OAuth flow, but you need to configure it
and store tokens securely.

Key OAuth concepts:
- Scopes define permissions (request minimum needed)
- Tokens are workspace-specific
- Installation data must be stored persistently
- Users can add scopes later (additive)

70% of users abandon installation when confronted with excessive
permission requests - request only what you need!

**When to use**: Distributing app to multiple workspaces,Building public Slack apps,Enterprise-grade integrations

from slack_bolt import App
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_sdk.oauth.installation_store import FileInstallationStore
from slack_sdk.oauth.state_store import FileOAuthStateStore
import os

# For production, use database-backed stores
# For example: PostgreSQL, MongoDB, Redis

class DatabaseInstallationStore:
    """Store installation data in your database."""

    async def save(self, installation):
        """Save installation when user completes OAuth."""
        await db.installations.upsert({
            "team_id": installation.team_id,
            "enterprise_id": installation.enterprise_id,
            "bot_token": encrypt(installation.bot_token),
            "bot_user_id": installation.bot_user_id,
            "bot_scopes": installation.bot_scopes,
            "user_id": installation.user_id,
            "installed_at": installation.installed_at
        })

    async def find_installation(self, *, enterprise_id, team_id, user_id=None, is_enterprise_install=False):
        """Find installation for a workspace."""
        record = await db.installations.find_one({
            "team_id": team_id,
            "enterprise_id": enterprise_id
        })

        if record:
            return Installation(
                bot_token=decrypt(record["bot_token"]),
                # ... other fields
            )
        return None

# Initialize OAuth-enabled app
app = App(
    signing_secret=os.environ["SLACK_SIGNING_SECRET"],
    oauth_settings=OAuthSettings(
        client_id=os.environ["SLACK_CLIENT_ID"],
        client_secret=os.environ["SLACK_CLIENT_SECRET"],
        scopes=[
            "channels:history",
            "channels:read",
            "chat:write",
            "commands",
            "users:read"
        ],
        user_scopes=[],  # User token scopes if needed
        installation_store=DatabaseInstallationStore(),
        state_store=FileOAuthStateStore(expiration_seconds=600)
    )
)

# OAuth routes are handled automatically by Bolt
# /slack/install - Initiates OAuth flow
# /slack/oauth_redirect - Handles callback

# Flask integration
from flask import Flask, request
from slack_bolt.adapter.flask import SlackRequestHandler

flask_app = Flask(__name__)
handler = SlackRequestHandler(app)

@flask_app.route("/slack/install", methods=["GET"])
def install():
    return handler.handle(request)

@flask_app.route("/slack/oauth_redirect", methods=["GET"])
def oauth_redirect():
    return handler.handle(request)

@flask_app.route("/slack/events", methods=["POST"])
def slack_events():
    return handler.handle(request)

# Handle installation success/failure
@app.oauth_success
def handle_oauth_success(args):
    """Called when OAuth completes successfully."""
    installation = args["installation"]

    # Send welcome message
    app.client.chat_postMessage(
        token=installation.bot_token,
        channel=installation.user_id,
        text="Thanks for installing! Type /help to get started."
    )

    return "Installation successful! You can close this window."

@app.oauth_failure
def handle_oauth_failure(args):
    """Called when OAuth fails."""
    error = args.get("error", "Unknown error")
    return f"Installation failed: {error}"

# Scope management - request additional scopes when needed
def request_additional_scopes(team_id: str, new_scopes: list):
    """
    Generate URL for user to add scopes.
    Note: Existing tokens retain old scopes.
    User must re-authorize for new scopes.
    """
    base_url = "https://slack.com/oauth/v2/authorize"
    params = {
        "client_id": os.environ["SLACK_CLIENT_ID"],
        "scope": ",".join(new_scopes),
        "team": team_id
    }
    return f"{base_url}?{urlencode(params)}"

### Anti_patterns

- Requesting unnecessary scopes upfront
- Storing tokens in plain text
- Not validating OAuth state parameter (CSRF risk)
- Assuming tokens have new scopes after config change

### Socket Mode Pattern

Socket Mode allows your app to receive events via WebSocket instead
of public HTTP endpoints. Perfect for development and apps behind
firewalls.

Benefits:
- No public URL needed
- Works behind corporate firewalls
- Simpler local development
- Real-time bidirectional communication

Limitation: Not recommended for high-volume production apps.

**When to use**: Local development,Apps behind corporate firewalls,Internal tools with security constraints,Prototyping and testing

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import os

# Socket Mode requires an app-level token (xapp-...)
# Create in App Settings > Basic Information > App-Level Tokens
# Needs 'connections:write' scope

app = App(token=os.environ["SLACK_BOT_TOKEN"])

@app.message("hello")
def handle_hello(message, say):
    say(f"Hey <@{message['user']}>!")

@app.command("/status")
def handle_status(ack, say):
    ack()
    say("All systems operational!")

@app.event("app_mention")
def handle_mention(event, say):
    say(f"You mentioned me, <@{event['user']}>!")

if __name__ == "__main__":
    # SocketModeHandler manages the WebSocket connection
    handler = SocketModeHandler(
        app,
        os.environ["SLACK_APP_TOKEN"]  # xapp-... token
    )

    print("Starting Socket Mode...")
    handler.start()

# For async apps
from slack_bolt.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
import asyncio

async_app = AsyncApp(token=os.environ["SLACK_BOT_TOKEN"])

@async_app.message("hello")
async def handle_hello_async(message, say):
    await say(f"Hey <@{message['user']}>!")

async def main():
    handler = AsyncSocketModeHandler(async_app, os.environ["SLACK_APP_TOKEN"])
    await handler.start_async()

if __name__ == "__main__":
    asyncio.run(main())

### Anti_patterns

- Using Socket Mode for high-volume production apps
- Not handling WebSocket disconnections
- Forgetting to create app-level token
- Using bot token instead of app token

### Workflow Builder Step Pattern

Extend Slack's Workflow Builder with custom steps powered by your app.
Users can include your custom steps in their no-code workflows.

Workflow steps can:
- Collect input from users
- Execute custom logic
- Output data for subsequent steps

**When to use**: Integrating with Workflow Builder,Enabling non-technical users to use your features,Building reusable automation components

from slack_bolt import App
from slack_bolt.workflows.step import WorkflowStep
import os

app = App(
    token=os.environ["SLACK_BOT_TOKEN"],
    signing_secret=os.environ["SLACK_SIGNING_SECRET"]
)

# Define the workflow step
def edit(ack, step, configure):
    """Called when user adds/edits the step in Workflow Builder."""
    ack()

    # Show configuration modal
    blocks = [
        {
            "type": "input",
            "block_id": "ticket_type",
            "element": {
                "type": "static_select",
                "action_id": "type_select",
                "options": [
                    {"text": {"type": "plain_text", "text": "Bug"}, "value": "bug"},
                    {"text": {"type": "plain_text", "text": "Feature"}, "value": "feature"},
                    {"text": {"type": "plain_text", "text": "Task"}, "value": "task"}
                ]
            },
            "label": {"type": "plain_text", "text": "Ticket Type"}
        },
        {
            "type": "input",
            "block_id": "title_input",
            "element": {
                "type": "plain_text_input",
                "action_id": "title"
            },
            "label": {"type": "plain_text", "text": "Title"}
        },
        {
            "type": "input",
            "block_id": "assignee_input",
            "element": {
                "type": "users_select",
                "action_id": "assignee"
            },
            "label": {"type": "plain_text", "text": "Assignee"}
        }
    ]

    configure(blocks=blocks)

def save(ack, view, update):
    """Called when user saves step configuration."""
    ack()

    values = view["state"]["values"]

    # Define inputs (from user's configuration)
    inputs = {
        "ticket_type": {
            "value": values["ticket_type"]["type_select"]["selected_option"]["value"]
        },
        "title": {
            "value": values["title_input"]["title"]["value"]
        },
        "assignee": {
            "value": values["assignee_input"]["assignee"]["selected_user"]
        }
    }

    # Define outputs (available to subsequent steps)
    outputs = [
        {
            "name": "ticket_id",
            "type": "text",
            "label": "Created Ticket ID"
        },
        {
            "name": "ticket_url",
            "type": "text",
            "label": "Ticket URL"
        }
    ]

    update(inputs=inputs, outputs=outputs)

def execute(step, complete, fail):
    """Called when the step runs in a workflow."""
    inputs = step["inputs"]

    try:
        # Get input values
        ticket_type = inputs["ticket_type"]["value"]
        title = inputs["title"]["value"]
        assignee = inputs["assignee"]["value"]

        # Create ticket in your system
        ticket = create_ticket(
            type=ticket_type,
            title=title,
            assignee=assignee
        )

        # Complete with outputs
        complete(outputs={
            "ticket_id": ticket["id"],
            "ticket_url": ticket["url"]
        })

    except Exception as e:
        fail(error={"message": str(e)})

# Register the workflow step
create_ticket_step = WorkflowStep(
    callback_id="create_ticket_step",
    edit=edit,
    save=save,
    execute=execute
)

app.step(create_ticket_step)

### Anti_patterns

- Not calling complete() or fail() in execute
- Long-running operations without progress updates
- Not validating inputs in execute
- Exposing sensitive data in outputs

## Sharp Edges

### Missing 3-Second Acknowledgment (Timeout)

Severity: CRITICAL

Situation: Handling slash commands, shortcuts, or interactive components

Symptoms:
User sees "This command timed out" or "Something went wrong."
The action never completes even though your code runs.
Works in development but fails in production.

Why this breaks:
Slack requires acknowledgment within 3 seconds for ALL interactive requests:
- Slash commands
- Button/select menu clicks
- Modal submissions
- Shortcuts

If you do ANY slow operation (database, API call, LLM) before responding,
you'll miss the window. Slack shows an error even if your bot eventually
processes the request correctly.

Recommended fix:

## Acknowledge immediately, process later

```python
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import threading

app = App(token=os.environ["SLACK_BOT_TOKEN"])

@app.command("/slow-task")
def handle_slow_task(ack, command, client, respond):
    # ACK IMMEDIATELY - before any processing
    ack("Processing your request...")

    # Do slow work in background
    def do_work():
        result = call_slow_api(command["text"])  # Takes 10 seconds
        respond(f"Done! Result: {result}")

    threading.Thread(target=do_work).start()

@app.view("modal_submission")
def handle_modal(ack, body, client, view):
    # ACK with response_action for modals
    ack(response_action="clear")  # Or "update" with new view

    # Process in background
    user_id = body["user"]["id"]
    values = view["state"]["values"]
    # ... slow processing
```

## For Bolt framework - use lazy listeners

```python
# Bolt handles ack() automatically with lazy listeners
@app.command("/slow-task")
def handle_slow_task(ack, command, respond):
    ack()  # Still call ack() first!

@handle_slow_task.lazy
def process_slow_task(command, respond):
    # This runs after ack, can take as long as needed
    result = slow_operation(command["text"])
    respond(result)
```

### Not Validating OAuth State Parameter (CSRF)

Severity: CRITICAL

Situation: Implementing OAuth installation flow

Symptoms:
Bot appears to work, but you're vulnerable to CSRF attacks.
Attackers could trick users into installing malicious configurations.

Why this breaks:
The OAuth state parameter prevents CSRF attacks. Flow:
1. You generate random state, store it, send to Slack
2. User authorizes in Slack
3. Slack redirects back with code + state
4. You MUST verify state matches what you stored

Without this, an attacker can craft a malicious OAuth URL and trick
admins into completing the flow with attacker's authorization code.

Recommended fix:

## Proper state validation

```python
import secrets
from flask import Flask, request, session, redirect
from slack_sdk.oauth import AuthorizeUrlGenerator
from slack_sdk.oauth.state_store import FileOAuthStateStore

app = Flask(__name__)
app.secret_key = os.environ["SESSION_SECRET"]

# Use Slack SDK's state store (Redis recommended for production)
state_store = FileOAuthStateStore(
    expiration_seconds=300,  # 5 minutes
    base_dir="./oauth_states"
)

@app.route("/slack/install")
def install():
    # Generate cryptographically secure state
    state = state_store.issue()

    # Store in session for verification
    session["oauth_state"] = state

    authorize_url = AuthorizeUrlGenerator(
        client_id=os.environ["SLACK_CLIENT_ID"],
        scopes=["channels:history", "chat:write"],
        user_scopes=[]
    ).generate(state)

    return redirect(authorize_url)

@app.route("/slack/oauth/callback")
def oauth_callback():
    # CRITICAL: Verify state
    received_state = request.args.get("state")
    stored_state = session.get("oauth_state")

    if not received_state or received_state != stored_state:
        return "Invalid state parameter - possible CSRF attack", 403

    # Also use state_store.consume() for one-time use
    if not state_store.consume(received_state):
        return "State already used or expired", 403

    # Now safe to exchange code for token
    code = request.args.get("code")
    # ... complete OAuth flow
```

### Exposing Bot/User Tokens

Severity: CRITICAL

Situation: Storing or logging Slack tokens

Symptoms:
Unauthorized messages sent from your bot. Attackers reading private
channels. Token found in logs, git history, or client-side code.

Why this breaks:
Slack tokens provide FULL access to whatever scopes they have:
- Bot tokens (xoxb-*): Access workspaces where installed
- User tokens (xoxp-*): Access as that specific user
- App-level tokens (xapp-*): Socket Mode connections

Common exposure points:
- Hardcoded in source code
- Logged in error messages
- Sent to frontend/client
- Stored in database without encryption

Recommended fix:

## Never hardcode or log tokens

```python
# BAD - never do this
client = WebClient(token="xoxb-12345-...")

# GOOD - environment variables
client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])

# BAD - logging tokens
logger.error(f"API call failed with token {token}")

# GOOD - never log tokens
logger.error(f"API call failed for team {team_id}")

# BAD - sending token to frontend
return {"token": bot_token}

# GOOD - only send what frontend needs
return {"channels": channel_list}
```

## Encrypt tokens in database

```python
from cryptography.fernet import Fernet

class TokenStore:
    def __init__(self, encryption_key: str):
        self.cipher = Fernet(encryption_key)

    def save_token(self, team_id: str, token: str):
        encrypted = self.cipher.encrypt(token.encode())
        db.execute(
            "INSERT INTO installations (team_id, encrypted_token) VALUES (?, ?)",
            (team_id, encrypted)
        )

    def get_token(self, team_id: str) -> str:
        row = db.execute(
            "SELECT encrypted_token FROM installations WHERE team_id = ?",
            (team_id,)
        ).fetchone()
        return self.cipher.decrypt(row[0]).decode()
```

## Rotate tokens if exposed

```
1. Slack API > Your App > OAuth & Permissions
2. Click "Rotate" for the exposed token
3. Update all deployments immediately
4. Review Slack audit logs for unauthorized access
```

### Requesting Unnecessary OAuth Scopes

Severity: HIGH

Situation: Configuring OAuth scopes for your app

Symptoms:
Users hesitate to install due to scary permission warnings.
Lower install rates. Security team blocks deployment.
App rejected from Slack App Directory.

Why this breaks:
Each OAuth scope grants specific permissions. Requesting more than
you need:
- Makes install consent screen scary
- Increases attack surface if token leaked
- May violate enterprise security policies
- Can get your app rejected from App Directory

Common over-requests:
- `admin` when you just need `chat:write`
- `channels:read` when you only message one channel
- `users:read.email` when you don't need emails

Recommended fix:

## Request minimum required scopes

```python
# For a simple notification bot
MINIMAL_SCOPES = [
    "chat:write",        # Post messages
    "channels:join",     # Join public channels (if needed)
]

# NOT NEEDED for basic notification:
# - channels:read (unless you list channels)
# - users:read (unless you look up users)
# - channels:history (unless you read messages)

# For a slash command bot
SLASH_COMMAND_SCOPES = [
    "commands",          # Register slash commands
    "chat:write",        # Respond to commands
]

# For a bot that responds to mentions
MENTION_BOT_SCOPES = [
    "app_mentions:read", # Receive @mentions
    "chat:write",        # Reply to mentions
]
```

## Scope reference by use case

| Use Case | Required Scopes |
|----------|-----------------|
| Post messages | `chat:write` |
| Slash commands | `commands` |
| Respond to @mentions | `app_mentions:read`, `chat:write` |
| Read channel messages | `channels:history` (public), `groups:history` (private) |
| Read user info | `users:read` |
| Open modals | `commands` or trigger from event |
| Add reactions | `reactions:write` |
| Upload files | `files:write` |

## Progressive scope requests

```python
# Start with minimal scopes
INITIAL_SCOPES = ["chat:write", "commands"]

# Request additional scopes only when needed
@app.command("/enable-reactions")
def enable_reactions(ack, client, command):
    ack()

    # Check if we have the scope
    auth_result = client.auth_test()
    # If missing reactions:write, prompt re-auth
    if needs_additional_scope:
        # Send user to re-auth with additional scope
        pass
```

### Exceeding Block Kit Limits

Severity: MEDIUM

Situation: Building complex message UIs with Block Kit

Symptoms:
Message fails to send with "invalid_blocks" error.
Modal won't open. Message truncated unexpectedly.

Why this breaks:
Block Kit has strict limits that aren't always obvious:
- 50 blocks per message/modal
- 3000 characters per text block
- 10 elements per actions block
- 100 options per select menu
- Modal: 50 blocks, 24KB total
- Home tab: 100 blocks

Exceeding these causes silent failures or cryptic errors.

Recommended fix:

## Know and respect the limits

```python
# Constants for Block Kit limits
BLOCK_KIT_LIMITS = {
    "blocks_per_message": 50,
    "blocks_per_modal": 50,
    "blocks_per_home": 100,
    "text_block_chars": 3000,
    "elements_per_actions": 10,
    "options_per_select": 100,
    "modal_total_bytes": 24 * 1024,  # 24KB
}

def validate_blocks(blocks: list) -> tuple[bool, str]:
    """Validate blocks before sending."""
    if len(blocks) > BLOCK_KIT_LIMITS["blocks_per_message"]:
        return False, f"Too many blocks: {len(blocks)} > 50"

    for block in blocks:
        if block.get("type") == "section":
            text = block.get("text", {}).get("text", "")
            if len(text) > BLOCK_KIT_LIMITS["text_block_chars"]:
                return False, f"Text too long: {len(text)} > 3000"

        if block.get("type") == "actions":
            elements = block.get("elements", [])
            if len(elements) > BLOCK_KIT_LIMITS["elements_per_actions"]:
                return False, f"Too many actions: {len(elements)} > 10"

    return True, "OK"

# Paginate long content
def paginate_blocks(blocks: list, page: int = 0, per_page: int = 45):
    """Paginate blocks with navigation."""
    start = page * per_page
    end = start + per_page
    page_blocks = blocks[start:end]

    # Add pagination controls
    if len(blocks) > per_page:
        page_blocks.append({
            "type": "actions",
            "elements": [
                {"type": "button", "text": {"type": "plain_text", "text": "Previous"},
                 "action_id": f"page_{page-1}", "disabled": page == 0},
                {"type": "button", "text": {"type": "plain_text", "text": "Next"},
                 "action_id": f"page_{page+1}",
                 "disabled": end >= len(blocks)}
            ]
        })

    return page_blocks
```

### Using Socket Mode in Production

Severity: HIGH

Situation: Deploying Slack bot to production

Symptoms:
Bot works in development but is unreliable in production.
Missed events. Connection drops. Can't scale horizontally.

Why this breaks:
Socket Mode is designed for development:
- Single WebSocket connection per app
- Can't scale to multiple instances
- Connection can drop (needs reconnect logic)
- No built-in load balancing

For production with multiple instances or high traffic,
HTTP webhooks are more reliable.

Recommended fix:

## Socket Mode: Only for development

```python
# Development with Socket Mode
if os.environ.get("ENVIRONMENT") == "development":
    from slack_bolt.adapter.socket_mode import SocketModeHandler
    handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
    handler.start()
```

## Production: Use HTTP endpoints

```python
# Production with HTTP (Flask example)
from slack_bolt.adapter.flask import SlackRequestHandler
from flask import Flask, request

flask_app = Flask(__name__)
handler = SlackRequestHandler(app)

@flask_app.route("/slack/events", methods=["POST"])
def slack_events():
    return handler.handle(request)

@flask_app.route("/slack/commands", methods=["POST"])
def slack_commands():
    return handler.handle(request)

@flask_app.route("/slack/interactions", methods=["POST"])
def slack_interactions():
    return handler.handle(request)
```

## If you must use Socket Mode in production

```python
from slack_bolt.adapter.socket_mode import SocketModeHandler
import time

class RobustSocketHandler:
    def __init__(self, app, app_token):
        self.app = app
        self.app_token = app_token
        self.handler = None

    def start(self):
        while True:
            try:
                self.handler = SocketModeHandler(self.app, self.app_token)
                self.handler.start()
            except Exception as e:
                logger.error(f"Socket Mode disconnected: {e}")
                time.sleep(5)  # Backoff before reconnect
```

### Not Verifying Request Signatures

Severity: CRITICAL

Situation: Receiving webhooks from Slack

Symptoms:
Attackers can send fake requests to your webhook endpoints.
Spoofed slash commands. Fake event notifications processed.

Why this breaks:
Slack signs all requests with X-Slack-Signature header using your
signing secret. Without verification, anyone who knows your webhook
URL can send fake requests.

This is different from OAuth tokens - signing verifies the REQUEST
came from Slack, not that you have permission to call Slack.

Recommended fix:

## Bolt handles this automatically

```python
from slack_bolt import App

# Bolt verifies signatures automatically when you provide signing_secret
app = App(
    token=os.environ["SLACK_BOT_TOKEN"],
    signing_secret=os.environ["SLACK_SIGNING_SECRET"]
)
# All requests to your handlers are verified
```

## Manual verification (if not using Bolt)

```python
import hmac
import hashlib
import time
from flask import Flask, request, abort

SIGNING_SECRET = os.environ["SLACK_SIGNING_SECRET"]

def verify_slack_signature(request):
    timestamp = request.headers.get("X-Slack-Request-Timestamp", "")
    signature = request.headers.get("X-Slack-Signature", "")

    # Reject old timestamps (replay attack prevention)
    if abs(time.time() - int(timestamp)) > 60 * 5:
        return False

    # Compute expected signature
    sig_basestring = f"v0:{timestamp}:{request.get_data(as_text=True)}"
    expected_sig = "v0=" + hmac.new(
        SIGNING_SECRET.encode(),
        sig_basestring.encode(),
        hashlib.sha256
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(expected_sig, signature)

@app.route("/slack/events", methods=["POST"])
def slack_events():
    if not verify_slack_signature(request):
        abort(403)
    # Safe to process
```

## Validation Checks

### Hardcoded Slack Token

Severity: ERROR

Slack tokens must never be hardcoded

Message: Hardcoded Slack token detected. Use environment variables.

### Signing Secret in Source Code

Severity: ERROR

Signing secrets should be in environment variables

Message: Hardcoded signing secret. Use os.environ['SLACK_SIGNING_SECRET'].

### Webhook Without Signature Verification

Severity: ERROR

Slack webhooks must verify X-Slack-Signature

Message: Webhook without signature verification. Use Bolt or verify manually.

### Slack Token in Client-Side Code

Severity: ERROR

Never expose Slack tokens to browsers

Message: Slack credentials exposed client-side. Only use server-side.

### Slow Operation Before Acknowledgment

Severity: WARNING

ack() must be called before slow operations

Message: Slow operation before ack(). Call ack() first, then process.

### Missing Acknowledgment Call

Severity: WARNING

Interactive handlers must call ack()

Message: Handler missing ack() call. Must acknowledge within 3 seconds.

### OAuth Without State Validation

Severity: ERROR

OAuth callback must validate state parameter

Message: OAuth without state validation. Vulnerable to CSRF attacks.

### Token Storage Without Encryption

Severity: WARNING

Tokens should be encrypted at rest

Message: Token stored without encryption. Encrypt tokens at rest.

### Requesting Admin Scopes

Severity: WARNING

Avoid admin scopes unless absolutely necessary

Message: Requesting admin scope. Use minimal required scopes.

### Potentially Unused Scope

Severity: INFO

Check if all requested scopes are used

Message: Requesting users:read.email but may not use email. Verify necessity.

## Collaboration

### Delegation Triggers

- user needs AI-powered Slack bot -> llm-architect (Integrate LLM for conversational Slack bot)
- user needs voice notifications -> twilio-communications (Escalate Slack alerts to SMS or voice calls)
- user needs workflow automation -> workflow-automation (Slack as trigger/action in n8n/Temporal workflows)
- user needs bot for Discord too -> discord-bot-architect (Cross-platform bot architecture)
- user needs full auth system -> auth-specialist (OAuth, workspace management, enterprise SSO)
- user needs database for bot data -> postgres-wizard (Store installations, user preferences, message history)
- user needs high availability -> devops (Scale webhooks, monitoring, alerting)

## When to Use
- User mentions or implies: slack bot
- User mentions or implies: slack app
- User mentions or implies: bolt framework
- User mentions or implies: block kit
- User mentions or implies: slash command
- User mentions or implies: slack webhook
- User mentions or implies: slack workflow
- User mentions or implies: slack interactive
- User mentions or implies: slack oauth

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: slack-gif-creator
description: "A toolkit providing utilities and knowledge for creating animated GIFs optimized for Slack."
risk: critical
source: community
date_added: "2026-02-27"
---

# Slack GIF Creator

A toolkit providing utilities and knowledge for creating animated GIFs optimized for Slack.

## Slack Requirements

**Dimensions:**
- Emoji GIFs: 128x128 (recommended)
- Message GIFs: 480x480

**Parameters:**
- FPS: 10-30 (lower is smaller file size)
- Colors: 48-128 (fewer = smaller file size)
- Duration: Keep under 3 seconds for emoji GIFs

## Core Workflow

```python
from core.gif_builder import GIFBuilder
from PIL import Image, ImageDraw

# 1. Create builder
builder = GIFBuilder(width=128, height=128, fps=10)

# 2. Generate frames
for i in range(12):
    frame = Image.new('RGB', (128, 128), (240, 248, 255))
    draw = ImageDraw.Draw(frame)

    # Draw your animation using PIL primitives
    # (circles, polygons, lines, etc.)

    builder.add_frame(frame)

# 3. Save with optimization
builder.save('output.gif', num_colors=48, optimize_for_emoji=True)
```

## Drawing Graphics

### Working with User-Uploaded Images
If a user uploads an image, consider whether they want to:
- **Use it directly** (e.g., "animate this", "split this into frames")
- **Use it as inspiration** (e.g., "make something like this")

Load and work with images using PIL:
```python
from PIL import Image

uploaded = Image.open('file.png')
# Use directly, or just as reference for colors/style
```

### Drawing from Scratch
When drawing graphics from scratch, use PIL ImageDraw primitives:

```python
from PIL import ImageDraw

draw = ImageDraw.Draw(frame)

# Circles/ovals
draw.ellipse([x1, y1, x2, y2], fill=(r, g, b), outline=(r, g, b), width=3)

# Stars, triangles, any polygon
points = [(x1, y1), (x2, y2), (x3, y3), ...]
draw.polygon(points, fill=(r, g, b), outline=(r, g, b), width=3)

# Lines
draw.line([(x1, y1), (x2, y2)], fill=(r, g, b), width=5)

# Rectangles
draw.rectangle([x1, y1, x2, y2], fill=(r, g, b), outline=(r, g, b), width=3)
```

**Don't use:** Emoji fonts (unreliable across platforms) or assume pre-packaged graphics exist in this skill.

### Making Graphics Look Good

Graphics should look polished and creative, not basic. Here's how:

**Use thicker lines** - Always set `width=2` or higher for outlines and lines. Thin lines (width=1) look choppy and amateurish.

**Add visual depth**:
- Use gradients for backgrounds (`create_gradient_background`)
- Layer multiple shapes for complexity (e.g., a star with a smaller star inside)

**Make shapes more interesting**:
- Don't just draw a plain circle - add highlights, rings, or patterns
- Stars can have glows (draw larger, semi-transparent versions behind)
- Combine multiple shapes (stars + sparkles, circles + rings)

**Pay attention to colors**:
- Use vibrant, complementary colors
- Add contrast (dark outlines on light shapes, light outlines on dark shapes)
- Consider the overall composition

**For complex shapes** (hearts, snowflakes, etc.):
- Use combinations of polygons and ellipses
- Calculate points carefully for symmetry
- Add details (a heart can have a highlight curve, snowflakes have intricate branches)

Be creative and detailed! A good Slack GIF should look polished, not like placeholder graphics.

## Available Utilities

### GIFBuilder (`core.gif_builder`)
Assembles frames and optimizes for Slack:
```python
builder = GIFBuilder(width=128, height=128, fps=10)
builder.add_frame(frame)  # Add PIL Image
builder.add_frames(frames)  # Add list of frames
builder.save('out.gif', num_colors=48, optimize_for_emoji=True, remove_duplicates=True)
```

### Validators (`core.validators`)
Check if GIF meets Slack requirements:
```python
from core.validators import validate_gif, is_slack_ready

# Detailed validation
passes, info = validate_gif('my.gif', is_emoji=True, verbose=True)

# Quick check
if is_slack_ready('my.gif'):
    print("Ready!")
```

### Easing Functions (`core.easing`)
Smooth motion instead of linear:
```python
from core.easing import interpolate

# Progress from 0.0 to 1.0
t = i / (num_frames - 1)

# Apply easing
y = interpolate(start=0, end=400, t=t, easing='ease_out')

# Available: linear, ease_in, ease_out, ease_in_out,
#           bounce_out, elastic_out, back_out
```

### Frame Helpers (`core.frame_composer`)
Convenience functions for common needs:
```python
from core.frame_composer import (
    create_blank_frame,         # Solid color background
    create_gradient_background,  # Vertical gradient
    draw_circle,                # Helper for circles
    draw_text,                  # Simple text rendering
    draw_star                   # 5-pointed star
)
```

## Animation Concepts

### Shake/Vibrate
Offset object position with oscillation:
- Use `math.sin()` or `math.cos()` with frame index
- Add small random variations for natural feel
- Apply to x and/or y position

### Pulse/Heartbeat
Scale object size rhythmically:
- Use `math.sin(t * frequency * 2 * math.pi)` for smooth pulse
- For heartbeat: two quick pulses then pause (adjust sine wave)
- Scale between 0.8 and 1.2 of base size

### Bounce
Object falls and bounces:
- Use `interpolate()` with `easing='bounce_out'` for landing
- Use `easing='ease_in'` for falling (accelerating)
- Apply gravity by increasing y velocity each frame

### Spin/Rotate
Rotate object around center:
- PIL: `image.rotate(angle, resample=Image.BICUBIC)`
- For wobble: use sine wave for angle instead of linear

### Fade In/Out
Gradually appear or disappear:
- Create RGBA image, adjust alpha channel
- Or use `Image.blend(image1, image2, alpha)`
- Fade in: alpha from 0 to 1
- Fade out: alpha from 1 to 0

### Slide
Move object from off-screen to position:
- Start position: outside frame bounds
- End position: target location
- Use `interpolate()` with `easing='ease_out'` for smooth stop
- For overshoot: use `easing='back_out'`

### Zoom
Scale and position for zoom effect:
- Zoom in: scale from 0.1 to 2.0, crop center
- Zoom out: scale from 2.0 to 1.0
- Can add motion blur for drama (PIL filter)

### Explode/Particle Burst
Create particles radiating outward:
- Generate particles with random angles and velocities
- Update each particle: `x += vx`, `y += vy`
- Add gravity: `vy += gravity_constant`
- Fade out particles over time (reduce alpha)

## Optimization Strategies

Only when asked to make the file size smaller, implement a few of the following methods:

1. **Fewer frames** - Lower FPS (10 instead of 20) or shorter duration
2. **Fewer colors** - `num_colors=48` instead of 128
3. **Smaller dimensions** - 128x128 instead of 480x480
4. **Remove duplicates** - `remove_duplicates=True` in save()
5. **Emoji mode** - `optimize_for_emoji=True` auto-optimizes

```python
# Maximum optimization for emoji
builder.save(
    'emoji.gif',
    num_colors=48,
    optimize_for_emoji=True,
    remove_duplicates=True
)
```

## Philosophy

This skill provides:
- **Knowledge**: Slack's requirements and animation concepts
- **Utilities**: GIFBuilder, validators, easing functions
- **Flexibility**: Create the animation logic using PIL primitives

It does NOT provide:
- Rigid animation templates or pre-made functions
- Emoji font rendering (unreliable across platforms)
- A library of pre-packaged graphics built into the skill

**Note on user uploads**: This skill doesn't include pre-built graphics, but if a user uploads an image, use PIL to load and work with it - interpret based on their request whether they want it used directly or just as inspiration.

Be creative! Combine concepts (bouncing + rotating, pulsing + sliding, etc.) and use PIL's full capabilities.

## Dependencies

```bash
pip install pillow imageio numpy
```

## When to Use
This skill is applicable to execute the workflow or actions described in the overview.

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: sleep-analyzer
description: 分析睡眠数据、识别睡眠模式、评估睡眠质量，并提供个性化睡眠改善建议。支持与其他健康数据的关联分析。
allowed-tools: Read, Grep, Glob, Write
risk: critical
source: community
---

# 睡眠分析器技能

分析睡眠数据，识别睡眠模式，评估睡眠质量，并提供个性化睡眠改善建议。

## When to Use
- 需要分析睡眠时长、效率、作息规律或睡眠质量时使用。
- 任务涉及失眠模式、夜间觉醒、PSQI 评分或睡眠问题识别。
- 需要把睡眠数据与情绪、运动或其他健康因素做关联分析时使用。

## 功能

### 1. 睡眠趋势分析

分析睡眠时长、质量、效率的变化趋势，识别改善或需要关注的方面。

**分析维度**：
- 睡眠时长趋势（平均睡眠时长变化）
- 睡眠效率趋势（睡眠效率百分比变化）
- 入睡时间模式（上床时间、入睡时间、起床时间）
- 作息规律性评分（sleep consistency score）
- 周末vs工作日对比（social jetlag）

**输出**：
- 趋势方向（改善/稳定/下降）
- 变化幅度和百分比
- 趋势显著性评估
- 最佳睡眠时间窗口识别
- 改进建议

### 2. 睡眠质量评估

综合评估睡眠质量，识别影响睡眠质量的关键因素。

**评估内容**：
- PSQI分数追踪和趋势
- 主观睡眠质量分布（好/中/差）
- 夜间觉醒分析（次数、时长、原因）
- 睡眠阶段分析（深睡、浅睡、REM比例）
- 睡后恢复感评估

**输出**：
- 睡眠质量等级（优秀/良好/一般/较差）
- 质量变化趋势
- 主要影响因素识别
- 质量改善优先级建议

### 3. 睡眠问题识别

识别常见的睡眠问题和风险因素。

**识别内容**：
- **失眠模式**：
  - 入睡困难（sleep latency >30分钟）
  - 睡眠维持困难（夜间觉醒>2次或总觉醒时间>30分钟）
  - 早醒（比预期提前醒来>30分钟）
  - 混合型失眠

- **呼吸暂停风险**：
  - STOP-BANG问卷评分
  - 症状分析（打鼾、憋醒、白天嗜睡）
  - 风险等级（低/中/高）

- **其他问题**：
  - 作息不规律检测
  - 睡眠债计算（理想时长vs实际时长）
  - 社交时差评估

**输出**：
- 问题存在与否
- 问题类型和严重程度
- 风险因素列表
- 是否需要就医建议

### 4. 相关性分析

分析睡眠与其他健康指标的相关性。

**支持的相关性分析**：
- **睡眠 ↔ 运动**：
  - 运动日vs休息日的睡眠差异
  - 运动时间对睡眠的影响（早晨/下午/晚间运动）
  - 运动强度与睡眠质量的相关性

- **睡眠 ↔ 饮食**：
  - 咖啡因摄入与睡眠时长、入睡时间的关系
  - 酒精摄入对睡眠结构的影响
  - 晚餐时间与睡眠质量的关系

- **睡眠 ↔ 情绪**：
  - 睡眠与情绪的双向关系分析
  - 压力水平对睡眠质量的影响
  - 睡眠剥夺对日间情绪的影响

- **睡眠 ↔ 慢性病**：
  - 睡眠与高血压的关系
  - 睡眠与血糖控制的关联
  - 睡眠与体重变化的关系

**输出**：
- 相关系数（-1到1）
- 相关性强度（弱/中/强）
- 统计显著性
- 因果关系推断
- 实践建议

### 5. 个性化建议生成

基于用户数据生成个性化睡眠改善建议。

**建议类型**：
- **作息调整建议**：
  - 最佳上床/起床时间
  - 作息一致性改善方案
  - 午睡管理建议

- **睡前准备建议**：
  - 睡前例行程序设计
  - 放松技巧推荐
  - 屏幕时间管理

- **睡眠环境优化**：
  - 温度、湿度、光线、噪音优化
  - 床品舒适度建议

- **生活方式调整**：
  - 运动、饮食、咖啡因、酒精管理
  - 压力管理建议

- **CBT-I元素**：
  - 刺激控制建议
  - 睡眠限制建议
  - 认知重构建议

**输出**：
- 优先级排序的建议列表
- 具体实施步骤
- 预期效果说明
- 实施时间线

---

## 使用说明

### 触发条件

当用户请求以下内容时触发本技能：
- 睡眠趋势分析
- 睡眠质量评估
- 睡眠问题识别
- 睡眠改善建议
- 睡眠与其他健康指标的关联分析

### 执行步骤

#### 步骤 1: 确定分析范围

明确用户请求的分析类型和时间范围：
- 分析类型：趋势/质量/问题/相关性/建议
- 时间范围：周/月/季度/自定义

#### 步骤 2: 读取数据

**主要数据源**：
1. `data-example/sleep-tracker.json` - 睡眠追踪主数据
2. `data-example/sleep-logs/YYYY-MM/YYYY-MM-DD.json` - 每日睡眠记录

**关联数据源**：
1. `data-example/fitness-tracker.json` - 运动数据
2. `data-example/hypertension-tracker.json` - 血压数据
3. `data-example/diabetes-tracker.json` - 血糖数据
4. `data-example/diet-records/` - 饮食记录
5. `data-example/mood-tracker.json` - 情绪数据

#### 步骤 3: 数据分析

根据分析类型执行相应的分析算法：

**趋势分析算法**：
- 线性回归计算趋势斜率
- 移动平均平滑波动
- 统计显著性检验

**相关性分析算法**：
- Pearson相关系数计算
- 滞后相关性分析（考虑时间延迟效应）
- 多变量回归分析

**模式识别算法**：
- 时间序列模式识别
- 异常值检测
- 周期性分析

#### 步骤 4: 生成报告

按照标准格式输出分析报告（见"输出格式"部分）

---

## 输出格式

### 睡眠质量分析报告

```markdown
# 睡眠质量分析报告

## 分析周期
2025-03-20 至 2025-06-20（3个月）

---

## 睡眠时长趋势

- **趋势**：⬆ 改善
- **开始**：平均6.2小时/晚
- **当前**：平均7.1小时/晚
- **变化**：+0.9小时 (+14.5%)
- **解读**：睡眠时长显著增加，接近理想目标（7.5小时）

**趋势线**：
```
6.5h ┤     ╭╮
6.0h ┤   ╭─╯╰╮
5.5h ┤ ╭─╯   ╰─╮
5.0h ┼─┘       ╰─
     └───────────
     3月  4月  5月  6月
```

---

## 睡眠效率

- **平均睡眠效率**：85.3%
- **效率范围**：78%-92%
- **达标率**：63%（>85%为达标）
- **解读**：睡眠效率正常，仍有提升空间

**效率分布**：
- 优秀（>90%）：15晚
- 良好（85-90%）：28晚
- 需改善（<85%）：47晚

---

## 作息规律性

- **平均上床时间**：23:15（范围：22:30-01:00）
- **平均起床时间**：07:05（范围：06:30-08:30）
- **作息一致性评分**：72/100
- **社交时差**：45分钟（周末比工作日晚睡晚起）
- **解读**：作息基本规律，但周末波动较大

**建议**：
- 🎯 保持一致的起床时间，包括周末
- 🎯 逐步调整上床时间，避免周末过度延迟

---

## 睡眠质量分布

| 质量等级 | 天数 | 占比 | 趋势 |
|---------|------|------|------|
| 优秀 | 8 | 9% | ⬆ |
| 很好 | 12 | 13% | ➡ |
| 好 | 15 | 17% | ⬆ |
| 一般 | 42 | 47% | ⬇ |
| 差 | 10 | 11% | ⬇ |
| 很差 | 3 | 3% | ➡ |

**解读**：睡眠质量以"一般"为主，但"好"及以上质量的天数在增加

---

## 夜间觉醒分析

- **平均觉醒次数**：1.8次/晚
- **平均觉醒时长**：18分钟
- **主要原因**：
  1. 尿意（45%）
  2. 噪音（25%）
  3. 温度过热（15%）
  4. 其他（15%）

**建议**：
- 🎯 睡前2小时限制液体摄入
- 🎯 优化卧室温度（18-22℃）
- 🎯 使用白噪音机器遮蔽背景噪音

---

## PSQI 评估趋势

- **最新分数**：8分（睡眠质量一般）
- **上次分数**：10分（2025-03-20）
- **变化**：-2分（改善）
- **趋势**：⬆ 持续改善

**历史趋势**：
```
12 ┤ ●
10 ┤  ●
 8 ┤    ●
 6 ┤
   └──────
   12月 3月 6月
```

**各成分变化**：
- 主观睡眠质量：2→2（稳定）
- 入睡时间：2→2（稳定）
- 睡眠时长：2→1（改善）
- 睡眠效率：2→1（改善）
- 睡眠障碍：2→1（改善）

---

## 睡眠问题识别

### 失眠评估

- **类型**：混合型失眠
- **频率**：4-5晚/周
- **持续时间**：18个月
- **主要症状**：
  - ✗ 入睡困难（潜伏期>30分钟）
  - ✗ 睡眠维持困难（夜间觉醒>2次）
  - ✓ 无早醒问题

- **影响**：
  - 白天疲劳：中度
  - 情绪烦躁：是
  - 注意力困难：是
  - 工作表现：轻度影响

- **建议**：🏥 持续>3个月，建议就医咨询睡眠专科

### 呼吸暂停筛查（STOP-BANG）

- **评分**：3/8
- **风险等级**：中等风险
- **阳性项目**：
  - ✗ Snoring（打鼾）
  - ✗ Tired（白天疲劳）
  - ✓ Observed apnea（未观察到呼吸暂停）
  - ✗ Pressure（高血压）
  - ✓ BMI > 28
  - ✓ Age > 50
  - ✗ Neck size > 40cm
  - ✓ Gender = male

- **建议**：⚠ 建议进行睡眠检查（PSG）

---

## 相关性分析

### 睡眠 ↔ 运动

**运动日 vs 休息日**：
- 运动日平均睡眠：7.3小时
- 休息日平均睡眠：6.8小时
- 差异：+0.5小时（+7.4%）

**运动时间对睡眠的影响**：
- 早晨运动：睡眠时长7.5小时，质量评分7.8/10
- 下午运动：睡眠时长7.2小时，质量评分7.5/10
- 晚间运动：睡眠时长6.8小时，质量评分6.8/10

**相关性**：中等正相关（r = 0.42）
**结论**：规律运动有助于改善睡眠，但应避免睡前2-3小时剧烈运动

**建议**：
- 🎯 保持规律运动习惯
- 🎯 将运动时间移至早晨或下午
- 🎯 睡前2-3小时避免剧烈运动

---

### 睡眠 ↔ 咖啡因

**咖啡因摄入时间分析**：
- 下午2点前摄入：平均睡眠7.2小时，入睡潜伏期25分钟
- 下午2点后摄入：平均睡眠6.7小时，入睡潜伏期40分钟
- 差异：-0.5小时时长，+15分钟潜伏期

**相关性**：中等负相关（r = -0.38）
**结论**：下午2点后摄入咖啡因显著影响睡眠

**建议**：
- 🎯 避免下午2点后摄入咖啡因
- 🎯 睡前6小时完全避免咖啡因

---

### 睡眠 ↔ 情绪

**睡眠质量对次日情绪的影响**：
- 睡眠好：次日情绪积极概率82%
- 睡眠一般：次日情绪积极概率45%
- 睡眠差：次日情绪积极概率18%

**睡前情绪对入睡的影响**：
- 睡前压力高：入睡潜伏期45分钟
- 睡前压力低：入睡潜伏期20分钟
- 差异：+25分钟

**相关性**：强双向相关（r = 0.65）
**结论**：睡眠与情绪存在显著的相互影响

**建议**：
- 🎯 睡前进行压力管理（冥想、深呼吸）
- 🎯 建立放松的睡前例行程序
- 🎯 记录情绪日记，识别压力模式

---

## 洞察与建议

### 关键洞察

1. **作息不一致是主要问题**
   - 社交时差45分钟
   - 周末作息显著偏离工作日
   - 影响：生物钟紊乱，周一"时差反应"

2. **晚间运动影响入睡**
   - 晚间运动日入睡潜伏期延长15分钟
   - 建议：调整运动时间

3. **睡眠环境可优化**
   - 噪音觉醒占25%
   - 温度过热占15%
   - 建议针对性改善

---

### 优先级行动计划

#### Priority 1：建立一致作息（2周）

**目标**：提高作息一致性评分至85分

**具体行动**：
1. 固定起床时间07:00（包括周末）
2. 固定上床时间23:00
3. 限制午睡<30分钟，且下午3点前
4. 逐步调整周末作息（每次提前15分钟）

**预期效果**：
- 作息一致性评分：72 → 85
- 睡眠效率提升：+3-5%
- 周一疲劳感减轻

---

#### Priority 2：创建睡前例行程序（3周）

**目标**：建立稳定的睡前例行程序

**具体行动**：
1. 提前1小时开始例行程序（22:00）
2. 关闭电子设备（22:30）
3. 调暗卧室灯光
4. 进行放松活动（阅读、冥想、温水澡）
5. 保持卧室安静、黑暗、凉爽（18-22℃）

**预期效果**：
- 入睡潜伏期缩短：30 → 20分钟
- 睡眠质量提升：一般 → 好
- 睡前压力降低

---

#### Priority 3：优化睡眠环境（1周）

**目标**：消除环境对睡眠的干扰

**具体行动**：
1. 安装遮光窗帘
2. 使用白噪音机器遮蔽背景噪音
3. 优化温度至18-22℃
4. 移除卧室时钟
5. 更换舒适的枕头和床垫

**预期效果**：
- 夜间觉醒减少：1.8 → 1.2次/晚
- 睡眠连续性提升
- 晨起状态改善

---

#### Priority 4：生活方式调整（4周）

**目标**：消除影响睡眠的生活习惯

**具体行动**：
1. 将运动移至早晨或下午
2. 下午2点后停止咖啡因摄入
3. 睡前3小时避免酒精
4. 睡前2小时避免大餐
5. 睡前1小时避免工作相关讨论

**预期效果**：
- 睡眠时长增加：+0.3小时
- 睡眠质量评分提升：+1分
- PSQI分数改善：8 → 6

---

## 长期目标

- **睡眠时长**：达到7.5小时/晚（当前7.1小时）
- **睡眠效率**：提升至>90%（当前85%）
- **PSQI分数**：降至≤5分（当前8分）
- **作息一致性**：提升至≥85分（当前72分）
- **入睡潜伏期**：缩短至<20分钟（当前28分钟）

---

## 医学安全提醒

⚠ **就医建议**：
- 🏥 失眠持续>3个月，建议咨询睡眠专科
- 🏥 STOP-BANG≥3分，建议进行睡眠检查（PSG）
- 🏥 严重嗜睡影响驾驶安全，需立即就医

---

**报告生成时间**：2025-06-20
**分析周期**：2025-03-20 至 2025-06-20（90天）
**数据记录数**：90晚
**睡眠分析器版本**：v1.0
```

---

## 数据结构

### 睡眠记录数据

```json
{
  "sleep_records": [
    {
      "id": "sleep_20250620001",
      "date": "2025-06-20",
      "sleep_times": {
        "bedtime": "23:00",
        "sleep_onset_time": "23:30",
        "wake_time": "07:00",
        "out_of_bed_time": "07:15"
      },
      "sleep_metrics": {
        "sleep_duration_hours": 7.0,
        "time_in_bed_hours": 8.25,
        "sleep_latency_minutes": 30,
        "sleep_efficiency": 84.8
      },
      "sleep_quality": {
        "subjective_quality": "fair",
        "quality_score": 5,
        "rested_feeling": "somewhat"
      },
      "factors": {
        "exercise": true,
        "exercise_time": "evening",
        "caffeine_after_2pm": false,
        "screen_time_before_bed_minutes": 60
      }
    }
  ]
}
```

---

## 算法说明

### 睡眠质量评分算法

```python
def calculate_sleep_quality_score(record):
    """
    计算睡眠质量评分（0-10分）

    因素权重：
    - 睡眠时长：30%
    - 睡眠效率：25%
    - 入睡潜伏期：20%
    - 夜间觉醒：15%
    - 主观质量：10%
    """
    score = 0

    # 睡眠时长评分（理想7-9小时）
    duration = record['sleep_duration_hours']
    if 7 <= duration <= 9:
        duration_score = 10
    elif 6 <= duration < 7 or 9 < duration <= 10:
        duration_score = 7
    else:
        duration_score = 4
    score += duration_score * 0.30

    # 睡眠效率评分（>90%优秀）
    efficiency = record['sleep_efficiency']
    efficiency_score = min(efficiency / 90 * 10, 10)
    score += efficiency_score * 0.25

    # 入睡潜伏期评分（<15分钟优秀）
    latency = record['sleep_latency_minutes']
    if latency <= 15:
        latency_score = 10
    elif latency <= 30:
        latency_score = 7
    elif latency <= 45:
        latency_score = 4
    else:
        latency_score = 1
    score += latency_score * 0.20

    # 夜间觉醒评分（0次优秀）
    awakenings = record['awakenings']['count']
    awakening_score = max(10 - awakenings * 2, 0)
    score += awakening_score * 0.15

    # 主观质量评分
    quality_map = {
        'excellent': 10,
        'very_good': 8,
        'good': 7,
        'fair': 5,
        'poor': 3,
        'very_poor': 1
    }
    subjective_score = quality_map.get(
        record['sleep_quality']['subjective_quality'],
        5
    )
    score += subjective_score * 0.10

    return round(score, 1)
```

### 作息规律性评分算法

```python
def calculate_sleep_consistency_score(records):
    """
    计算作息规律性评分（0-100分）

    因素：
    - 上床时间标准差
    - 起床时间标准差
    - 睡眠时长标准差
    - 工作日vs周末差异
    """
    # 提取时间数据
    bedtimes = [r['bedtime'] for r in records]
    wake_times = [r['wake_time'] for r in records]
    durations = [r['sleep_duration_hours'] for r in records]

    # 计算标准差（分钟）
    bedtime_std = time_to_minutes_std(bedtimes)
    wake_std = time_to_minutes_std(wake_times)
    duration_std = statistics.stdev(durations)

    # 计算工作日vs周末差异
    weekday_avg = avg([r['sleep_duration_hours']
                       for r in records if is_weekday(r)])
    weekend_avg = avg([r['sleep_duration_hours']
                       for r in records if is_weekend(r)])
    diff = abs(weekday_avg - weekend_avg)

    # 综合评分
    score = 100
    score -= bedtime_std * 0.5  # 上床时间标准差影响
    score -= wake_std * 0.5     # 起床时间标准差影响
    score -= duration_std * 2   # 睡眠时长标准差影响
    score -= diff * 10          # 工作日周末差异影响

    return max(0, min(100, round(score)))
```

### 相关性分析算法

```python
def calculate_correlation(sleep_data, other_data, lag_days=0):
    """
    计算睡眠与其他指标的相关性

    参数：
    - sleep_data: 睡眠数据列表
    - other_data: 其他指标数据列表
    - lag_days: 滞后天数（考虑延迟效应）

    返回：
    - correlation_coefficient: 相关系数
    - p_value: 统计显著性
    - interpretation: 相关性解释
    """
    # 对齐数据（考虑滞后）
    aligned = align_data_with_lag(sleep_data, other_data, lag_days)

    # 计算Pearson相关系数
    from scipy import stats
    corr, p_value = stats.pearsonr(
        aligned['sleep_values'],
        aligned['other_values']
    )

    # 解释相关性
    if abs(corr) < 0.3:
        strength = "弱"
    elif abs(corr) < 0.7:
        strength = "中等"
    else:
        strength = "强"

    direction = "正相关" if corr > 0 else "负相关"
    significant = p_value < 0.05

    interpretation = f"{strength}{direction}"
    if significant:
        interpretation += "（统计学显著）"

    return {
        'correlation_coefficient': round(corr, 3),
        'p_value': round(p_value, 4),
        'interpretation': interpretation,
        'significant': significant
    }
```

---

## 医学安全声明

本技能提供的分析和建议仅供参考，不构成医疗诊断或治疗方案。

**本技能能够做到的**：
- ✅ 分析睡眠数据和模式
- ✅ 识别睡眠问题风险
- ✅ 提供睡眠卫生建议
- ✅ 评估与其他健康指标的相关性

**本技能不能做的**：
- ❌ 诊断失眠、睡眠呼吸暂停等疾病
- ❌ 开具助眠药物或治疗
- ❌ 替代专业睡眠医学治疗
- ❌ 处理严重睡眠障碍

**何时需要就医**：
- 🏥 失眠持续>3个月
- 🏥 疑似睡眠呼吸暂停（STOP-BANG≥3）
- 🏥 严重嗜睡影响安全
- 🏥 突发严重睡眠问题

---

## 参考资源

- AASM 睡眠评分标准：https://aasm.org/
- PSQI 量表：https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3455216/
- STOP-BANG 问卷：https://www.stopbang.ca/
- CBT-I 治疗：https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3455216/

---

**技能版本**: v1.0
**创建日期**: 2026-01-02
**维护者**: WellAlly Tech

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: slo-implementation
description: "Framework for defining and implementing Service Level Indicators (SLIs), Service Level Objectives (SLOs), and error budgets."
risk: critical
source: community
date_added: "2026-02-27"
---

# SLO Implementation

Framework for defining and implementing Service Level Indicators (SLIs), Service Level Objectives (SLOs), and error budgets.

## Do not use this skill when

- The task is unrelated to slo implementation
- You need a different domain or tool outside this scope

## Instructions

- Clarify goals, constraints, and required inputs.
- Apply relevant best practices and validate outcomes.
- Provide actionable steps and verification.
- If detailed examples are required, open `resources/implementation-playbook.md`.

## Purpose

Implement measurable reliability targets using SLIs, SLOs, and error budgets to balance reliability with innovation velocity.

## Use this skill when

- Define service reliability targets
- Measure user-perceived reliability
- Implement error budgets
- Create SLO-based alerts
- Track reliability goals

## SLI/SLO/SLA Hierarchy

```
SLA (Service Level Agreement)
  ↓ Contract with customers
SLO (Service Level Objective)
  ↓ Internal reliability target
SLI (Service Level Indicator)
  ↓ Actual measurement
```

## Defining SLIs

### Common SLI Types

#### 1. Availability SLI
```promql
# Successful requests / Total requests
sum(rate(http_requests_total{status!~"5.."}[28d]))
/
sum(rate(http_requests_total[28d]))
```

#### 2. Latency SLI
```promql
# Requests below latency threshold / Total requests
sum(rate(http_request_duration_seconds_bucket{le="0.5"}[28d]))
/
sum(rate(http_request_duration_seconds_count[28d]))
```

#### 3. Durability SLI
```
# Successful writes / Total writes
sum(storage_writes_successful_total)
/
sum(storage_writes_total)
```

**Reference:** See `references/slo-definitions.md`

## Setting SLO Targets

### Availability SLO Examples

| SLO % | Downtime/Month | Downtime/Year |
|-------|----------------|---------------|
| 99%   | 7.2 hours      | 3.65 days     |
| 99.9% | 43.2 minutes   | 8.76 hours    |
| 99.95%| 21.6 minutes   | 4.38 hours    |
| 99.99%| 4.32 minutes   | 52.56 minutes |

### Choose Appropriate SLOs

**Consider:**
- User expectations
- Business requirements
- Current performance
- Cost of reliability
- Competitor benchmarks

**Example SLOs:**
```yaml
slos:
  - name: api_availability
    target: 99.9
    window: 28d
    sli: |
      sum(rate(http_requests_total{status!~"5.."}[28d]))
      /
      sum(rate(http_requests_total[28d]))

  - name: api_latency_p95
    target: 99
    window: 28d
    sli: |
      sum(rate(http_request_duration_seconds_bucket{le="0.5"}[28d]))
      /
      sum(rate(http_request_duration_seconds_count[28d]))
```

## Error Budget Calculation

### Error Budget Formula

```
Error Budget = 1 - SLO Target
```

**Example:**
- SLO: 99.9% availability
- Error Budget: 0.1% = 43.2 minutes/month
- Current Error: 0.05% = 21.6 minutes/month
- Remaining Budget: 50%

### Error Budget Policy

```yaml
error_budget_policy:
  - remaining_budget: 100%
    action: Normal development velocity
  - remaining_budget: 50%
    action: Consider postponing risky changes
  - remaining_budget: 10%
    action: Freeze non-critical changes
  - remaining_budget: 0%
    action: Feature freeze, focus on reliability
```

**Reference:** See `references/error-budget.md`

## SLO Implementation

### Prometheus Recording Rules

```yaml
# SLI Recording Rules
groups:
  - name: sli_rules
    interval: 30s
    rules:
      # Availability SLI
      - record: sli:http_availability:ratio
        expr: |
          sum(rate(http_requests_total{status!~"5.."}[28d]))
          /
          sum(rate(http_requests_total[28d]))

      # Latency SLI (requests < 500ms)
      - record: sli:http_latency:ratio
        expr: |
          sum(rate(http_request_duration_seconds_bucket{le="0.5"}[28d]))
          /
          sum(rate(http_request_duration_seconds_count[28d]))

  - name: slo_rules
    interval: 5m
    rules:
      # SLO compliance (1 = meeting SLO, 0 = violating)
      - record: slo:http_availability:compliance
        expr: sli:http_availability:ratio >= bool 0.999

      - record: slo:http_latency:compliance
        expr: sli:http_latency:ratio >= bool 0.99

      # Error budget remaining (percentage)
      - record: slo:http_availability:error_budget_remaining
        expr: |
          (sli:http_availability:ratio - 0.999) / (1 - 0.999) * 100

      # Error budget burn rate
      - record: slo:http_availability:burn_rate_5m
        expr: |
          (1 - (
            sum(rate(http_requests_total{status!~"5.."}[5m]))
            /
            sum(rate(http_requests_total[5m]))
          )) / (1 - 0.999)
```

### SLO Alerting Rules

```yaml
groups:
  - name: slo_alerts
    interval: 1m
    rules:
      # Fast burn: 14.4x rate, 1 hour window
      # Consumes 2% error budget in 1 hour
      - alert: SLOErrorBudgetBurnFast
        expr: |
          slo:http_availability:burn_rate_1h > 14.4
          and
          slo:http_availability:burn_rate_5m > 14.4
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Fast error budget burn detected"
          description: "Error budget burning at {{ $value }}x rate"

      # Slow burn: 6x rate, 6 hour window
      # Consumes 5% error budget in 6 hours
      - alert: SLOErrorBudgetBurnSlow
        expr: |
          slo:http_availability:burn_rate_6h > 6
          and
          slo:http_availability:burn_rate_30m > 6
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Slow error budget burn detected"
          description: "Error budget burning at {{ $value }}x rate"

      # Error budget exhausted
      - alert: SLOErrorBudgetExhausted
        expr: slo:http_availability:error_budget_remaining < 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "SLO error budget exhausted"
          description: "Error budget remaining: {{ $value }}%"
```

## SLO Dashboard

**Grafana Dashboard Structure:**

```
┌────────────────────────────────────┐
│ SLO Compliance (Current)           │
│ ✓ 99.95% (Target: 99.9%)          │
├────────────────────────────────────┤
│ Error Budget Remaining: 65%        │
│ ████████░░ 65%                     │
├────────────────────────────────────┤
│ SLI Trend (28 days)                │
│ [Time series graph]                │
├────────────────────────────────────┤
│ Burn Rate Analysis                 │
│ [Burn rate by time window]         │
└────────────────────────────────────┘
```

**Example Queries:**

```promql
# Current SLO compliance
sli:http_availability:ratio * 100

# Error budget remaining
slo:http_availability:error_budget_remaining

# Days until error budget exhausted (at current burn rate)
(slo:http_availability:error_budget_remaining / 100)
*
28
/
(1 - sli:http_availability:ratio) * (1 - 0.999)
```

## Multi-Window Burn Rate Alerts

```yaml
# Combination of short and long windows reduces false positives
rules:
  - alert: SLOBurnRateHigh
    expr: |
      (
        slo:http_availability:burn_rate_1h > 14.4
        and
        slo:http_availability:burn_rate_5m > 14.4
      )
      or
      (
        slo:http_availability:burn_rate_6h > 6
        and
        slo:http_availability:burn_rate_30m > 6
      )
    labels:
      severity: critical
```

## SLO Review Process

### Weekly Review
- Current SLO compliance
- Error budget status
- Trend analysis
- Incident impact

### Monthly Review
- SLO achievement
- Error budget usage
- Incident postmortems
- SLO adjustments

### Quarterly Review
- SLO relevance
- Target adjustments
- Process improvements
- Tooling enhancements

## Best Practices

1. **Start with user-facing services**
2. **Use multiple SLIs** (availability, latency, etc.)
3. **Set achievable SLOs** (don't aim for 100%)
4. **Implement multi-window alerts** to reduce noise
5. **Track error budget** consistently
6. **Review SLOs regularly**
7. **Document SLO decisions**
8. **Align with business goals**
9. **Automate SLO reporting**
10. **Use SLOs for prioritization**

## Reference Files

- `assets/slo-template.md` - SLO definition template
- `references/slo-definitions.md` - SLO definition patterns
- `references/error-budget.md` - Error budget calculations

## Related Skills

- `prometheus-configuration` - For metric collection
- `grafana-dashboards` - For SLO visualization

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: smart-git-automation
version: 1.0.0
description: "Smart change detection, auto branch naming, and streamlined commit/PR workflow"
risk: critical
source: community
source_type: community
source_repo: mskadu/opencode-agent-skills
license: MIT
license_source: "https://github.com/mskadu/opencode-agent-skills/blob/main/LICENSE"
date_added: "2026-06-05"
---

## What I do
- Intelligently detect and group related changes
- Auto-generate descriptive branch names from changes
- Streamlined workflow: scan → branch → commit → push → PR with fewer prompts

## When to Use
Use this when you want a faster, smarter git workflow that groups changes logically and reduces manual confirmation overhead.

## Workflow Steps

### 1. Smart Detection & Grouping
Run in parallel:
- `git status` - check what's changed
- `git diff --stat` - see file modification summary
- `git diff --name-only` - list changed files only
- `git diff --staged --stat` - see what's already staged

Analyze changes to group them logically:
- Files in the same module/directory → likely related
- Files that were modified together in recent edits → likely related
- New files that complement each other → likely related

Present grouped changes in a clear format, e.g.:
```
📁 Group 1: UI Components
  - src/components/Button.tsx (modified)
  - src/components/Button.test.tsx (modified)

📁 Group 2: API Layer
  - src/api/client.ts (new)
  - src/api/types.ts (modified)
```

### 2. Auto Branch Name Generation
Generate branch name from dominant change pattern:
- Use format: `<type>/<short-description>`
- Types: `feature`, `fix`, `refactor`, `docs`, `test`, `chore`
- Derive description from most significant changed file/feature
- Convert to kebab-case, max 50 chars
- Examples:
  - `feature/add-user-auth` (from auth-related files)
  - `fix/login-validation` (from validation changes)
  - `refactor/api-cleanup` (from API refactoring)

Show the proposed branch name and ask for one-word confirmation (or type alternative).

### 3. Streamlined Branch & Commit
- If not on main/master: check if current branch matches proposed name
  - If yes: stay on it
  - If no: ask to switch or create new
- Create branch only after validating the branch name, then use `git checkout -b "$branch_name"`
- Stage explicit pathspecs only: `git add -- path/to/file ...`
  - If file paths are generated, keep them NUL-delimited (`git diff -z --name-only`) and pass them as pathspec arguments.
  - Never concatenate untrusted filenames into a shell command and never run the placeholder text literally.
- Auto-generate commit message from changes:
  - First line: `<type>: <short description>` (max 72 chars)
  - Body: grouped file changes with brief descriptions
- Commit with generated message, show preview first
- Ask for one-word confirmation to proceed

### 4. Push & Optional PR
- After commit, ask: "Push to remote? (yes/no/abort)"
- If yes: `git push -u origin <branch-name>`
- Then ask: "Create PR? (yes/no)"
- If yes:
  - Check remote: `git remote -v`
  - If fork: use fork's remote (e.g., `mskadu/repo-name`)
  - Auto-generate PR description from commit messages
  - Use `gh pr create` with:
    - Title from branch name
    - Body: summary of changes + file breakdown + follow-up notes

## Key Rules
- Group related files automatically, but allow user to adjust
- Generate branch names from actual changes, don't ask user to name them
- Reduce confirmations: ask for one-word answers or single confirmation points
- Never commit secrets, credentials, or large binaries
- Check if GitHub repo exists before PR creation
- Skip PR step if user says "no" at any point
- If branch already exists with changes, offer to amend or add new commit

## Limitations

- Do not bypass repository-specific maintainer rules, branch policies, or required review gates.
- Confirm destructive or publishing actions explicitly; this skill should streamline routine Git flow, not remove accountability.


---
name: smartui-skill
description: Generates SmartUI visual regression test configurations for screenshot comparison on TestMu AI cloud. Framework-agnostic — works with Playwright, Selenium, Cypress, Puppeteer. Use when user mentions "SmartUI", "visual regression", "screenshot comparison", "visual testing". Triggers on:...
risk: critical
source: https://github.com/LambdaTest/agent-skills/tree/main/smartui-skill
source_repo: LambdaTest/agent-skills
source_type: community
date_added: 2026-07-01
license: MIT
license_source: https://github.com/LambdaTest/agent-skills/blob/main/LICENSE
---

# SmartUI Visual Regression Skill
## When to Use

Use this skill when you need generates SmartUI visual regression test configurations for screenshot comparison on TestMu AI cloud. Framework-agnostic — works with Playwright, Selenium, Cypress, Puppeteer. Use when user mentions "SmartUI", "visual regression", "screenshot comparison", "visual testing". Triggers on:...

## Core Patterns

### Playwright + SmartUI SDK

```javascript
const { chromium } = require('playwright');
const { smartuiSnapshot } = require('@lambdatest/smartui-cli');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  await page.goto('https://example.com');
  await smartuiSnapshot(page, 'Homepage');

  await page.goto('https://example.com/login');
  await smartuiSnapshot(page, 'Login Page');

  await browser.close();
})();
```

### Selenium + SmartUI

```java
// Take SmartUI screenshot
((JavascriptExecutor) driver).executeScript(
    "smartui.takeScreenshot=Login Page"
);
```

### CLI Execution

```bash
# Install
npm install @lambdatest/smartui-cli --save-dev

# Configure
npx smartui config:create smartui.config.json

# Execute
npx smartui exec -- node test.js
# or with Playwright
npx smartui exec -- npx playwright test
```

### smartui.config.json

```json
{
  "web": {
    "browsers": ["chrome", "firefox", "safari"],
    "viewports": [[1920, 1080], [1366, 768], [375, 812]]
  },
  "waitForPageRender": 5000,
  "waitForTimeout": 1000
}
```

### SmartUI with Storybook

```bash
npx smartui storybook http://localhost:6006 --config smartui.config.json
```

### Approval Workflow

1. First run creates baseline screenshots
2. Subsequent runs compare against baseline
3. Differences highlighted in dashboard
4. Approve/reject changes in LambdaTest SmartUI dashboard
5. Approved screenshots become new baseline

### Anti-Patterns

| Bad | Good | Why |
|-----|------|-----|
| No viewport config | Multiple viewports | Responsive issues |
| No wait for render | `waitForPageRender` | Incomplete screenshots |
| Screenshot everything | Key pages/components | Noise reduction |
| No approval process | Review diffs in dashboard | Catch regressions |

### Cloud Authentication

Set environment variables:

```bash
export PROJECT_TOKEN="your-smartui-project-token"   # From SmartUI dashboard
export LT_USERNAME="your-username"                   # For Selenium/Playwright cloud
export LT_ACCESS_KEY="your-access-key"               # For Selenium/Playwright cloud
```

**CLI approach** (uses `PROJECT_TOKEN`):
```bash
npx smartui exec -- npx playwright test
```

**Selenium Cloud approach** (uses `LT_USERNAME`/`LT_ACCESS_KEY`):
```java
// Capabilities include SmartUI options
HashMap<String, Object> ltOptions = new HashMap<>();
ltOptions.put("user", System.getenv("LT_USERNAME"));
ltOptions.put("accessKey", System.getenv("LT_ACCESS_KEY"));
ltOptions.put("build", "SmartUI Build");
ltOptions.put("smartUI.project", "My SmartUI Project");
ChromeOptions options = new ChromeOptions();
options.setCapability("LT:Options", ltOptions);
WebDriver driver = new RemoteWebDriver(
    new URL("https://hub.lambdatest.com/wd/hub"), options);
```

## Quick Reference

| Task | Command |
|------|---------|
| Install | `npm install @lambdatest/smartui-cli` |
| Init config | `npx smartui config:create smartui.config.json` |
| Run | `npx smartui exec -- <test command>` |
| Storybook | `npx smartui storybook <url>` |
| Dashboard | `https://smartui.lambdatest.com` |

## Deep Patterns

For advanced patterns, debugging guides, CI/CD integration, and best practices,
see `reference/playbook.md`.

## Limitations

- Use this skill only when the task clearly matches its upstream source and local project context.
- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.
- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.