{"id":"51c5f199-45fe-48fb-915c-420fcfdb282e","shortId":"HLVNRQ","kind":"skill","title":"slack-bot-builder","tagline":"Build Slack apps using the Bolt framework across Python,","description":"# Slack Bot Builder\n\nBuild Slack apps using the Bolt framework across Python, JavaScript, and Java.\nCovers Block Kit for rich UIs, interactive components, slash commands,\nevent handling, OAuth installation flows, and Workflow Builder integration.\nFocus on best practices for production-ready Slack apps.\n\n## Patterns\n\n### Bolt App Foundation Pattern\n\nThe Bolt framework is Slack's recommended approach for building apps.\nIt handles authentication, event routing, request verification, and\nHTTP request processing so you can focus on app logic.\n\nKey benefits:\n- Event handling in a few lines of code\n- Security checks and payload validation built-in\n- Organized, consistent patterns\n- Works for experiments and production\n\nAvailable in: Python, JavaScript (Node.js), Java\n\n**When to use**: Starting any new Slack app,Migrating from legacy Slack APIs,Building production Slack integrations\n\n# Python Bolt App\nfrom slack_bolt import App\nfrom slack_bolt.adapter.socket_mode import SocketModeHandler\nimport os\n\n# Initialize with tokens from environment\napp = App(\n    token=os.environ[\"SLACK_BOT_TOKEN\"],\n    signing_secret=os.environ[\"SLACK_SIGNING_SECRET\"]\n)\n\n# Handle messages containing \"hello\"\n@app.message(\"hello\")\ndef handle_hello(message, say):\n    \"\"\"Respond to messages containing 'hello'.\"\"\"\n    user = message[\"user\"]\n    say(f\"Hey there <@{user}>!\")\n\n# Handle slash command\n@app.command(\"/ticket\")\ndef handle_ticket_command(ack, body, client):\n    \"\"\"Handle /ticket slash command.\"\"\"\n    # Acknowledge immediately (within 3 seconds)\n    ack()\n\n    # Open a modal for ticket creation\n    client.views_open(\n        trigger_id=body[\"trigger_id\"],\n        view={\n            \"type\": \"modal\",\n            \"callback_id\": \"ticket_modal\",\n            \"title\": {\"type\": \"plain_text\", \"text\": \"Create Ticket\"},\n            \"submit\": {\"type\": \"plain_text\", \"text\": \"Submit\"},\n            \"blocks\": [\n                {\n                    \"type\": \"input\",\n                    \"block_id\": \"title_block\",\n                    \"element\": {\n                        \"type\": \"plain_text_input\",\n                        \"action_id\": \"title_input\"\n                    },\n                    \"label\": {\"type\": \"plain_text\", \"text\": \"Title\"}\n                },\n                {\n                    \"type\": \"input\",\n                    \"block_id\": \"desc_block\",\n                    \"element\": {\n                        \"type\": \"plain_text_input\",\n                        \"multiline\": True,\n                        \"action_id\": \"desc_input\"\n                    },\n                    \"label\": {\"type\": \"plain_text\", \"text\": \"Description\"}\n                },\n                {\n                    \"type\": \"input\",\n                    \"block_id\": \"priority_block\",\n                    \"element\": {\n                        \"type\": \"static_select\",\n                        \"action_id\": \"priority_select\",\n                        \"options\": [\n                            {\"text\": {\"type\": \"plain_text\", \"text\": \"Low\"}, \"value\": \"low\"},\n                            {\"text\": {\"type\": \"plain_text\", \"text\": \"Medium\"}, \"value\": \"medium\"},\n                            {\"text\": {\"type\": \"plain_text\", \"text\": \"High\"}, \"value\": \"high\"}\n                        ]\n                    },\n                    \"label\": {\"type\": \"plain_text\", \"text\": \"Priority\"}\n                }\n            ]\n        }\n    )\n\n# Handle modal submission\n@app.view(\"ticket_modal\")\ndef handle_ticket_submission(ack, body, client, view):\n    \"\"\"Handle ticket modal submission.\"\"\"\n    ack()\n\n    # Extract values from the view\n    values = view[\"state\"][\"values\"]\n    title = values[\"title_block\"][\"title_input\"][\"value\"]\n    desc = values[\"desc_block\"][\"desc_input\"][\"value\"]\n    priority = values[\"priority_block\"][\"priority_select\"][\"selected_option\"][\"value\"]\n    user_id = body[\"user\"][\"id\"]\n\n    # Create ticket in your system\n    ticket_id = create_ticket(title, desc, priority, user_id)\n\n    # Notify user\n    client.chat_postMessage(\n        channel=user_id,\n        text=f\"Ticket #{ticket_id} created: {title}\"\n    )\n\n# Handle button clicks\n@app.action(\"approve_button\")\ndef handle_approval(ack, body, client):\n    \"\"\"Handle approval button click.\"\"\"\n    ack()\n\n    # Get context from the action\n    user = body[\"user\"][\"id\"]\n    action_value = body[\"actions\"][0][\"value\"]\n\n    # Update the message to remove interactive elements\n    # (Best practice: prevent double-clicks)\n    client.chat_update(\n        channel=body[\"channel\"][\"id\"],\n        ts=body[\"message\"][\"ts\"],\n        text=f\"Approved by <@{user}>\",\n        blocks=[]  # Remove interactive blocks\n    )\n\n# Listen for app_home_opened events\n@app.event(\"app_home_opened\")\ndef update_home_tab(client, event):\n    \"\"\"Update the Home tab when user opens it.\"\"\"\n    client.views_publish(\n        user_id=event[\"user\"],\n        view={\n            \"type\": \"home\",\n            \"blocks\": [\n                {\n                    \"type\": \"section\",\n                    \"text\": {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Welcome to the Ticket Bot!*\"\n                    }\n                },\n                {\n                    \"type\": \"actions\",\n                    \"elements\": [\n                        {\n                            \"type\": \"button\",\n                            \"text\": {\"type\": \"plain_text\", \"text\": \"Create Ticket\"},\n                            \"action_id\": \"create_ticket_button\"\n                        }\n                    ]\n                }\n            ]\n        }\n    )\n\n# Socket Mode for development (no public URL needed)\nif __name__ == \"__main__\":\n    handler = SocketModeHandler(app, os.environ[\"SLACK_APP_TOKEN\"])\n    handler.start()\n\n# For production, use HTTP mode with a web server\n# from flask import Flask, request\n# from slack_bolt.adapter.flask import SlackRequestHandler\n#\n# flask_app = Flask(__name__)\n# handler = SlackRequestHandler(app)\n#\n# @flask_app.route(\"/slack/events\", methods=[\"POST\"])\n# def slack_events():\n#     return handler.handle(request)\n\n### Anti_patterns\n\n- Not acknowledging requests within 3 seconds\n- Blocking operations in the ack handler\n- Hardcoding tokens in source code\n- Not using Socket Mode for development\n\n### Block Kit UI Pattern\n\nBlock Kit is Slack's UI framework for building rich, interactive messages.\nCompose messages using blocks (sections, actions, inputs) and elements\n(buttons, menus, text inputs).\n\nLimits:\n- Up to 50 blocks per message\n- Up to 100 blocks in modals/Home tabs\n- Block text limited to 3000 characters\n\nUse Block Kit Builder to prototype: https://app.slack.com/block-kit-builder\n\n**When to use**: Building rich message layouts,Adding interactive components to messages,Creating forms in modals,Building Home tab experiences\n\nfrom slack_bolt import App\nimport os\n\napp = App(token=os.environ[\"SLACK_BOT_TOKEN\"])\n\ndef build_notification_blocks(incident: dict) -> list:\n    \"\"\"Build Block Kit blocks for incident notification.\"\"\"\n    severity_emoji = {\n        \"critical\": \":red_circle:\",\n        \"high\": \":large_orange_circle:\",\n        \"medium\": \":large_yellow_circle:\",\n        \"low\": \":white_circle:\"\n    }\n\n    return [\n        # Header\n        {\n            \"type\": \"header\",\n            \"text\": {\n                \"type\": \"plain_text\",\n                \"text\": f\"{severity_emoji.get(incident['severity'], '')} Incident Alert\"\n            }\n        },\n        # Details section\n        {\n            \"type\": \"section\",\n            \"fields\": [\n                {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"*Incident:*\\n{incident['title']}\"\n                },\n                {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"*Severity:*\\n{incident['severity'].upper()}\"\n                },\n                {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"*Service:*\\n{incident['service']}\"\n                },\n                {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"*Reported:*\\n<!date^{incident['timestamp']}^{date_short} {time}|{incident['timestamp']}>\"\n                }\n            ]\n        },\n        # Description\n        {\n            \"type\": \"section\",\n            \"text\": {\n                \"type\": \"mrkdwn\",\n                \"text\": f\"*Description:*\\n{incident['description'][:2000]}\"\n            }\n        },\n        # Divider\n        {\"type\": \"divider\"},\n        # Action buttons\n        {\n            \"type\": \"actions\",\n            \"block_id\": f\"incident_actions_{incident['id']}\",\n            \"elements\": [\n                {\n                    \"type\": \"button\",\n                    \"text\": {\"type\": \"plain_text\", \"text\": \"Acknowledge\"},\n                    \"style\": \"primary\",\n                    \"action_id\": \"acknowledge_incident\",\n                    \"value\": incident['id']\n                },\n                {\n                    \"type\": \"button\",\n                    \"text\": {\"type\": \"plain_text\", \"text\": \"Resolve\"},\n                    \"style\": \"danger\",\n                    \"action_id\": \"resolve_incident\",\n                    \"value\": incident['id'],\n                    \"confirm\": {\n                        \"title\": {\"type\": \"plain_text\", \"text\": \"Resolve Incident?\"},\n                        \"text\": {\"type\": \"mrkdwn\", \"text\": \"Are you sure this incident is resolved?\"},\n                        \"confirm\": {\"type\": \"plain_text\", \"text\": \"Yes, Resolve\"},\n                        \"deny\": {\"type\": \"plain_text\", \"text\": \"Cancel\"}\n                    }\n                },\n                {\n                    \"type\": \"button\",\n                    \"text\": {\"type\": \"plain_text\", \"text\": \"View Details\"},\n                    \"action_id\": \"view_incident\",\n                    \"value\": incident['id'],\n                    \"url\": f\"https://incidents.example.com/{incident['id']}\"\n                }\n            ]\n        },\n        # Context footer\n        {\n            \"type\": \"context\",\n            \"elements\": [\n                {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"Incident ID: {incident['id']} | <https://runbook.example.com/{incident['service']}|View Runbook>\"\n                }\n            ]\n        }\n    ]\n\ndef send_incident_notification(channel: str, incident: dict):\n    \"\"\"Send incident notification with Block Kit.\"\"\"\n    blocks = build_notification_blocks(incident)\n\n    app.client.chat_postMessage(\n        channel=channel,\n        text=f\"Incident: {incident['title']}\",  # Fallback for notifications\n        blocks=blocks\n    )\n\n# Handle button actions\n@app.action(\"acknowledge_incident\")\ndef handle_acknowledge(ack, body, client):\n    \"\"\"Handle incident acknowledgment.\"\"\"\n    ack()\n\n    incident_id = body[\"actions\"][0][\"value\"]\n    user = body[\"user\"][\"id\"]\n\n    # Update your system\n    acknowledge_incident(incident_id, user)\n\n    # Update message to show acknowledgment\n    original_blocks = body[\"message\"][\"blocks\"]\n\n    # Add acknowledgment to context\n    original_blocks[-1][\"elements\"].append({\n        \"type\": \"mrkdwn\",\n        \"text\": f\":white_check_mark: Acknowledged by <@{user}>\"\n    })\n\n    # Remove acknowledge button (prevent double-click)\n    action_block = next(b for b in original_blocks if b.get(\"block_id\", \"\").startswith(\"incident_actions\"))\n    action_block[\"elements\"] = [e for e in action_block[\"elements\"] if e[\"action_id\"] != \"acknowledge_incident\"]\n\n    client.chat_update(\n        channel=body[\"channel\"][\"id\"],\n        ts=body[\"message\"][\"ts\"],\n        blocks=original_blocks\n    )\n\n# Interactive select menus\ndef build_user_selector_blocks():\n    \"\"\"Build blocks with user selector.\"\"\"\n    return [\n        {\n            \"type\": \"section\",\n            \"text\": {\"type\": \"mrkdwn\", \"text\": \"Assign this task:\"},\n            \"accessory\": {\n                \"type\": \"users_select\",\n                \"action_id\": \"assign_user\",\n                \"placeholder\": {\"type\": \"plain_text\", \"text\": \"Select assignee\"}\n            }\n        }\n    ]\n\n# Overflow menu for more options\ndef build_task_blocks(task: dict):\n    \"\"\"Build task blocks with overflow menu.\"\"\"\n    return [\n        {\n            \"type\": \"section\",\n            \"text\": {\"type\": \"mrkdwn\", \"text\": f\"*{task['title']}*\"},\n            \"accessory\": {\n                \"type\": \"overflow\",\n                \"action_id\": \"task_overflow\",\n                \"options\": [\n                    {\n                        \"text\": {\"type\": \"plain_text\", \"text\": \"Edit\"},\n                        \"value\": f\"edit_{task['id']}\"\n                    },\n                    {\n                        \"text\": {\"type\": \"plain_text\", \"text\": \"Delete\"},\n                        \"value\": f\"delete_{task['id']}\"\n                    },\n                    {\n                        \"text\": {\"type\": \"plain_text\", \"text\": \"Share\"},\n                        \"value\": f\"share_{task['id']}\"\n                    }\n                ]\n            }\n        }\n    ]\n\n### Anti_patterns\n\n- Exceeding 50 blocks per message\n- Not providing fallback text for accessibility\n- Hardcoding action_ids (use dynamic IDs when needed)\n- Not handling button clicks idempotently\n\n### OAuth Installation Pattern\n\nEnable users to install your app in their workspaces via OAuth 2.0.\nBolt handles most of the OAuth flow, but you need to configure it\nand store tokens securely.\n\nKey OAuth concepts:\n- Scopes define permissions (request minimum needed)\n- Tokens are workspace-specific\n- Installation data must be stored persistently\n- Users can add scopes later (additive)\n\n70% of users abandon installation when confronted with excessive\npermission requests - request only what you need!\n\n**When to use**: Distributing app to multiple workspaces,Building public Slack apps,Enterprise-grade integrations\n\nfrom slack_bolt import App\nfrom slack_bolt.oauth.oauth_settings import OAuthSettings\nfrom slack_sdk.oauth.installation_store import FileInstallationStore\nfrom slack_sdk.oauth.state_store import FileOAuthStateStore\nimport os\n\n# For production, use database-backed stores\n# For example: PostgreSQL, MongoDB, Redis\n\nclass DatabaseInstallationStore:\n    \"\"\"Store installation data in your database.\"\"\"\n\n    async def save(self, installation):\n        \"\"\"Save installation when user completes OAuth.\"\"\"\n        await db.installations.upsert({\n            \"team_id\": installation.team_id,\n            \"enterprise_id\": installation.enterprise_id,\n            \"bot_token\": encrypt(installation.bot_token),\n            \"bot_user_id\": installation.bot_user_id,\n            \"bot_scopes\": installation.bot_scopes,\n            \"user_id\": installation.user_id,\n            \"installed_at\": installation.installed_at\n        })\n\n    async def find_installation(self, *, enterprise_id, team_id, user_id=None, is_enterprise_install=False):\n        \"\"\"Find installation for a workspace.\"\"\"\n        record = await db.installations.find_one({\n            \"team_id\": team_id,\n            \"enterprise_id\": enterprise_id\n        })\n\n        if record:\n            return Installation(\n                bot_token=decrypt(record[\"bot_token\"]),\n                # ... other fields\n            )\n        return None\n\n# Initialize OAuth-enabled app\napp = App(\n    signing_secret=os.environ[\"SLACK_SIGNING_SECRET\"],\n    oauth_settings=OAuthSettings(\n        client_id=os.environ[\"SLACK_CLIENT_ID\"],\n        client_secret=os.environ[\"SLACK_CLIENT_SECRET\"],\n        scopes=[\n            \"channels:history\",\n            \"channels:read\",\n            \"chat:write\",\n            \"commands\",\n            \"users:read\"\n        ],\n        user_scopes=[],  # User token scopes if needed\n        installation_store=DatabaseInstallationStore(),\n        state_store=FileOAuthStateStore(expiration_seconds=600)\n    )\n)\n\n# OAuth routes are handled automatically by Bolt\n# /slack/install - Initiates OAuth flow\n# /slack/oauth_redirect - Handles callback\n\n# Flask integration\nfrom flask import Flask, request\nfrom slack_bolt.adapter.flask import SlackRequestHandler\n\nflask_app = Flask(__name__)\nhandler = SlackRequestHandler(app)\n\n@flask_app.route(\"/slack/install\", methods=[\"GET\"])\ndef install():\n    return handler.handle(request)\n\n@flask_app.route(\"/slack/oauth_redirect\", methods=[\"GET\"])\ndef oauth_redirect():\n    return handler.handle(request)\n\n@flask_app.route(\"/slack/events\", methods=[\"POST\"])\ndef slack_events():\n    return handler.handle(request)\n\n# Handle installation success/failure\n@app.oauth_success\ndef handle_oauth_success(args):\n    \"\"\"Called when OAuth completes successfully.\"\"\"\n    installation = args[\"installation\"]\n\n    # Send welcome message\n    app.client.chat_postMessage(\n        token=installation.bot_token,\n        channel=installation.user_id,\n        text=\"Thanks for installing! Type /help to get started.\"\n    )\n\n    return \"Installation successful! You can close this window.\"\n\n@app.oauth_failure\ndef handle_oauth_failure(args):\n    \"\"\"Called when OAuth fails.\"\"\"\n    error = args.get(\"error\", \"Unknown error\")\n    return f\"Installation failed: {error}\"\n\n# Scope management - request additional scopes when needed\ndef request_additional_scopes(team_id: str, new_scopes: list):\n    \"\"\"\n    Generate URL for user to add scopes.\n    Note: Existing tokens retain old scopes.\n    User must re-authorize for new scopes.\n    \"\"\"\n    base_url = \"https://slack.com/oauth/v2/authorize\"\n    params = {\n        \"client_id\": os.environ[\"SLACK_CLIENT_ID\"],\n        \"scope\": \",\".join(new_scopes),\n        \"team\": team_id\n    }\n    return f\"{base_url}?{urlencode(params)}\"\n\n### Anti_patterns\n\n- Requesting unnecessary scopes upfront\n- Storing tokens in plain text\n- Not validating OAuth state parameter (CSRF risk)\n- Assuming tokens have new scopes after config change\n\n### Socket Mode Pattern\n\nSocket Mode allows your app to receive events via WebSocket instead\nof public HTTP endpoints. Perfect for development and apps behind\nfirewalls.\n\nBenefits:\n- No public URL needed\n- Works behind corporate firewalls\n- Simpler local development\n- Real-time bidirectional communication\n\nLimitation: Not recommended for high-volume production apps.\n\n**When to use**: Local development,Apps behind corporate firewalls,Internal tools with security constraints,Prototyping and testing\n\nfrom slack_bolt import App\nfrom slack_bolt.adapter.socket_mode import SocketModeHandler\nimport os\n\n# Socket Mode requires an app-level token (xapp-...)\n# Create in App Settings > Basic Information > App-Level Tokens\n# Needs 'connections:write' scope\n\napp = App(token=os.environ[\"SLACK_BOT_TOKEN\"])\n\n@app.message(\"hello\")\ndef handle_hello(message, say):\n    say(f\"Hey <@{message['user']}>!\")\n\n@app.command(\"/status\")\ndef handle_status(ack, say):\n    ack()\n    say(\"All systems operational!\")\n\n@app.event(\"app_mention\")\ndef handle_mention(event, say):\n    say(f\"You mentioned me, <@{event['user']}>!\")\n\nif __name__ == \"__main__\":\n    # SocketModeHandler manages the WebSocket connection\n    handler = SocketModeHandler(\n        app,\n        os.environ[\"SLACK_APP_TOKEN\"]  # xapp-... token\n    )\n\n    print(\"Starting Socket Mode...\")\n    handler.start()\n\n# For async apps\nfrom slack_bolt.async_app import AsyncApp\nfrom slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler\nimport asyncio\n\nasync_app = AsyncApp(token=os.environ[\"SLACK_BOT_TOKEN\"])\n\n@async_app.message(\"hello\")\nasync def handle_hello_async(message, say):\n    await say(f\"Hey <@{message['user']}>!\")\n\nasync def main():\n    handler = AsyncSocketModeHandler(async_app, os.environ[\"SLACK_APP_TOKEN\"])\n    await handler.start_async()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n\n### Anti_patterns\n\n- Using Socket Mode for high-volume production apps\n- Not handling WebSocket disconnections\n- Forgetting to create app-level token\n- Using bot token instead of app token\n\n### Workflow Builder Step Pattern\n\nExtend Slack's Workflow Builder with custom steps powered by your app.\nUsers can include your custom steps in their no-code workflows.\n\nWorkflow steps can:\n- Collect input from users\n- Execute custom logic\n- Output data for subsequent steps\n\n**When to use**: Integrating with Workflow Builder,Enabling non-technical users to use your features,Building reusable automation components\n\nfrom slack_bolt import App\nfrom slack_bolt.workflows.step import WorkflowStep\nimport os\n\napp = App(\n    token=os.environ[\"SLACK_BOT_TOKEN\"],\n    signing_secret=os.environ[\"SLACK_SIGNING_SECRET\"]\n)\n\n# Define the workflow step\ndef edit(ack, step, configure):\n    \"\"\"Called when user adds/edits the step in Workflow Builder.\"\"\"\n    ack()\n\n    # Show configuration modal\n    blocks = [\n        {\n            \"type\": \"input\",\n            \"block_id\": \"ticket_type\",\n            \"element\": {\n                \"type\": \"static_select\",\n                \"action_id\": \"type_select\",\n                \"options\": [\n                    {\"text\": {\"type\": \"plain_text\", \"text\": \"Bug\"}, \"value\": \"bug\"},\n                    {\"text\": {\"type\": \"plain_text\", \"text\": \"Feature\"}, \"value\": \"feature\"},\n                    {\"text\": {\"type\": \"plain_text\", \"text\": \"Task\"}, \"value\": \"task\"}\n                ]\n            },\n            \"label\": {\"type\": \"plain_text\", \"text\": \"Ticket Type\"}\n        },\n        {\n            \"type\": \"input\",\n            \"block_id\": \"title_input\",\n            \"element\": {\n                \"type\": \"plain_text_input\",\n                \"action_id\": \"title\"\n            },\n            \"label\": {\"type\": \"plain_text\", \"text\": \"Title\"}\n        },\n        {\n            \"type\": \"input\",\n            \"block_id\": \"assignee_input\",\n            \"element\": {\n                \"type\": \"users_select\",\n                \"action_id\": \"assignee\"\n            },\n            \"label\": {\"type\": \"plain_text\", \"text\": \"Assignee\"}\n        }\n    ]\n\n    configure(blocks=blocks)\n\ndef save(ack, view, update):\n    \"\"\"Called when user saves step configuration.\"\"\"\n    ack()\n\n    values = view[\"state\"][\"values\"]\n\n    # Define inputs (from user's configuration)\n    inputs = {\n        \"ticket_type\": {\n            \"value\": values[\"ticket_type\"][\"type_select\"][\"selected_option\"][\"value\"]\n        },\n        \"title\": {\n            \"value\": values[\"title_input\"][\"title\"][\"value\"]\n        },\n        \"assignee\": {\n            \"value\": values[\"assignee_input\"][\"assignee\"][\"selected_user\"]\n        }\n    }\n\n    # Define outputs (available to subsequent steps)\n    outputs = [\n        {\n            \"name\": \"ticket_id\",\n            \"type\": \"text\",\n            \"label\": \"Created Ticket ID\"\n        },\n        {\n            \"name\": \"ticket_url\",\n            \"type\": \"text\",\n            \"label\": \"Ticket URL\"\n        }\n    ]\n\n    update(inputs=inputs, outputs=outputs)\n\ndef execute(step, complete, fail):\n    \"\"\"Called when the step runs in a workflow.\"\"\"\n    inputs = step[\"inputs\"]\n\n    try:\n        # Get input values\n        ticket_type = inputs[\"ticket_type\"][\"value\"]\n        title = inputs[\"title\"][\"value\"]\n        assignee = inputs[\"assignee\"][\"value\"]\n\n        # Create ticket in your system\n        ticket = create_ticket(\n            type=ticket_type,\n            title=title,\n            assignee=assignee\n        )\n\n        # Complete with outputs\n        complete(outputs={\n            \"ticket_id\": ticket[\"id\"],\n            \"ticket_url\": ticket[\"url\"]\n        })\n\n    except Exception as e:\n        fail(error={\"message\": str(e)})\n\n# Register the workflow step\ncreate_ticket_step = WorkflowStep(\n    callback_id=\"create_ticket_step\",\n    edit=edit,\n    save=save,\n    execute=execute\n)\n\napp.step(create_ticket_step)\n\n### Anti_patterns\n\n- Not calling complete() or fail() in execute\n- Long-running operations without progress updates\n- Not validating inputs in execute\n- Exposing sensitive data in outputs\n\n## Sharp Edges\n\n### Missing 3-Second Acknowledgment (Timeout)\n\nSeverity: CRITICAL\n\nSituation: Handling slash commands, shortcuts, or interactive components\n\nSymptoms:\nUser sees \"This command timed out\" or \"Something went wrong.\"\nThe action never completes even though your code runs.\nWorks in development but fails in production.\n\nWhy this breaks:\nSlack requires acknowledgment within 3 seconds for ALL interactive requests:\n- Slash commands\n- Button/select menu clicks\n- Modal submissions\n- Shortcuts\n\nIf you do ANY slow operation (database, API call, LLM) before responding,\nyou'll miss the window. Slack shows an error even if your bot eventually\nprocesses the request correctly.\n\nRecommended fix:\n\n## Acknowledge immediately, process later\n\n```python\nfrom slack_bolt import App\nfrom slack_bolt.adapter.socket_mode import SocketModeHandler\nimport threading\n\napp = App(token=os.environ[\"SLACK_BOT_TOKEN\"])\n\n@app.command(\"/slow-task\")\ndef handle_slow_task(ack, command, client, respond):\n    # ACK IMMEDIATELY - before any processing\n    ack(\"Processing your request...\")\n\n    # Do slow work in background\n    def do_work():\n        result = call_slow_api(command[\"text\"])  # Takes 10 seconds\n        respond(f\"Done! Result: {result}\")\n\n    threading.Thread(target=do_work).start()\n\n@app.view(\"modal_submission\")\ndef handle_modal(ack, body, client, view):\n    # ACK with response_action for modals\n    ack(response_action=\"clear\")  # Or \"update\" with new view\n\n    # Process in background\n    user_id = body[\"user\"][\"id\"]\n    values = view[\"state\"][\"values\"]\n    # ... slow processing\n```\n\n## For Bolt framework - use lazy listeners\n\n```python\n# Bolt handles ack() automatically with lazy listeners\n@app.command(\"/slow-task\")\ndef handle_slow_task(ack, command, respond):\n    ack()  # Still call ack() first!\n\n@handle_slow_task.lazy\ndef process_slow_task(command, respond):\n    # This runs after ack, can take as long as needed\n    result = slow_operation(command[\"text\"])\n    respond(result)\n```\n\n### Not Validating OAuth State Parameter (CSRF)\n\nSeverity: CRITICAL\n\nSituation: Implementing OAuth installation flow\n\nSymptoms:\nBot appears to work, but you're vulnerable to CSRF attacks.\nAttackers could trick users into installing malicious configurations.\n\nWhy this breaks:\nThe OAuth state parameter prevents CSRF attacks. Flow:\n1. You generate random state, store it, send to Slack\n2. User authorizes in Slack\n3. Slack redirects back with code + state\n4. You MUST verify state matches what you stored\n\nWithout this, an attacker can craft a malicious OAuth URL and trick\nadmins into completing the flow with attacker's authorization code.\n\nRecommended fix:\n\n## Proper state validation\n\n```python\nimport secrets\nfrom flask import Flask, request, session, redirect\nfrom slack_sdk.oauth import AuthorizeUrlGenerator\nfrom slack_sdk.oauth.state_store import FileOAuthStateStore\n\napp = Flask(__name__)\napp.secret_key = os.environ[\"SESSION_SECRET\"]\n\n# Use Slack SDK's state store (Redis recommended for production)\nstate_store = FileOAuthStateStore(\n    expiration_seconds=300,  # 5 minutes\n    base_dir=\"./oauth_states\"\n)\n\n@app.route(\"/slack/install\")\ndef install():\n    # Generate cryptographically secure state\n    state = state_store.issue()\n\n    # Store in session for verification\n    session[\"oauth_state\"] = state\n\n    authorize_url = AuthorizeUrlGenerator(\n        client_id=os.environ[\"SLACK_CLIENT_ID\"],\n        scopes=[\"channels:history\", \"chat:write\"],\n        user_scopes=[]\n    ).generate(state)\n\n    return redirect(authorize_url)\n\n@app.route(\"/slack/oauth/callback\")\ndef oauth_callback():\n    # CRITICAL: Verify state\n    received_state = request.args.get(\"state\")\n    stored_state = session.get(\"oauth_state\")\n\n    if not received_state or received_state != stored_state:\n        return \"Invalid state parameter - possible CSRF attack\", 403\n\n    # Also use state_store.consume() for one-time use\n    if not state_store.consume(received_state):\n        return \"State already used or expired\", 403\n\n    # Now safe to exchange code for token\n    code = request.args.get(\"code\")\n    # ... complete OAuth flow\n```\n\n### Exposing Bot/User Tokens\n\nSeverity: CRITICAL\n\nSituation: Storing or logging Slack tokens\n\nSymptoms:\nUnauthorized messages sent from your bot. Attackers reading private\nchannels. Token found in logs, git history, or client-side code.\n\nWhy this breaks:\nSlack tokens provide FULL access to whatever scopes they have:\n- Bot tokens (xoxb-*): Access workspaces where installed\n- User tokens (xoxp-*): Access as that specific user\n- App-level tokens (xapp-*): Socket Mode connections\n\nCommon exposure points:\n- Hardcoded in source code\n- Logged in error messages\n- Sent to frontend/client\n- Stored in database without encryption\n\nRecommended fix:\n\n## Never hardcode or log tokens\n\n```python\n# BAD - never do this\nclient = WebClient(token=\"xoxb-12345-...\")\n\n# GOOD - environment variables\nclient = WebClient(token=os.environ[\"SLACK_BOT_TOKEN\"])\n\n# BAD - logging tokens\nlogger.error(f\"API call failed with token {token}\")\n\n# GOOD - never log tokens\nlogger.error(f\"API call failed for team {team_id}\")\n\n# BAD - sending token to frontend\nreturn {\"token\": bot_token}\n\n# GOOD - only send what frontend needs\nreturn {\"channels\": channel_list}\n```\n\n## Encrypt tokens in database\n\n```python\nfrom cryptography.fernet import Fernet\n\nclass TokenStore:\n    def __init__(self, encryption_key: str):\n        self.cipher = Fernet(encryption_key)\n\n    def save_token(self, team_id: str, token: str):\n        encrypted = self.cipher.encrypt(token.encode())\n        db.execute(\n            \"INSERT INTO installations (team_id, encrypted_token) VALUES (?, ?)\",\n            (team_id, encrypted)\n        )\n\n    def get_token(self, team_id: str) -> str:\n        row = db.execute(\n            \"SELECT encrypted_token FROM installations WHERE team_id = ?\",\n            (team_id,)\n        ).fetchone()\n        return self.cipher.decrypt(row[0]).decode()\n```\n\n## Rotate tokens if exposed\n\n```\n1. Slack API > Your App > OAuth & Permissions\n2. Click \"Rotate\" for the exposed token\n3. Update all deployments immediately\n4. Review Slack audit logs for unauthorized access\n```\n\n### Requesting Unnecessary OAuth Scopes\n\nSeverity: HIGH\n\nSituation: Configuring OAuth scopes for your app\n\nSymptoms:\nUsers hesitate to install due to scary permission warnings.\nLower install rates. Security team blocks deployment.\nApp rejected from Slack App Directory.\n\nWhy this breaks:\nEach OAuth scope grants specific permissions. Requesting more than\nyou need:\n- Makes install consent screen scary\n- Increases attack surface if token leaked\n- May violate enterprise security policies\n- Can get your app rejected from App Directory\n\nCommon over-requests:\n- `admin` when you just need `chat:write`\n- `channels:read` when you only message one channel\n- `users:read.email` when you don't need emails\n\nRecommended fix:\n\n## Request minimum required scopes\n\n```python\n# For a simple notification bot\nMINIMAL_SCOPES = [\n    \"chat:write\",        # Post messages\n    \"channels:join\",     # Join public channels (if needed)\n]\n\n# NOT NEEDED for basic notification:\n# - channels:read (unless you list channels)\n# - users:read (unless you look up users)\n# - channels:history (unless you read messages)\n\n# For a slash command bot\nSLASH_COMMAND_SCOPES = [\n    \"commands\",          # Register slash commands\n    \"chat:write\",        # Respond to commands\n]\n\n# For a bot that responds to mentions\nMENTION_BOT_SCOPES = [\n    \"app_mentions:read\", # Receive @mentions\n    \"chat:write\",        # Reply to mentions\n]\n```\n\n## Scope reference by use case\n\n| Use Case | Required Scopes |\n|----------|-----------------|\n| Post messages | `chat:write` |\n| Slash commands | `commands` |\n| Respond to @mentions | `app_mentions:read`, `chat:write` |\n| Read channel messages | `channels:history` (public), `groups:history` (private) |\n| Read user info | `users:read` |\n| Open modals | `commands` or trigger from event |\n| Add reactions | `reactions:write` |\n| Upload files | `files:write` |\n\n## Progressive scope requests\n\n```python\n# Start with minimal scopes\nINITIAL_SCOPES = [\"chat:write\", \"commands\"]\n\n# Request additional scopes only when needed\n@app.command(\"/enable-reactions\")\ndef enable_reactions(ack, client, command):\n    ack()\n\n    # Check if we have the scope\n    auth_result = client.auth_test()\n    # If missing reactions:write, prompt re-auth\n    if needs_additional_scope:\n        # Send user to re-auth with additional scope\n        pass\n```\n\n### Exceeding Block Kit Limits\n\nSeverity: MEDIUM\n\nSituation: Building complex message UIs with Block Kit\n\nSymptoms:\nMessage fails to send with \"invalid_blocks\" error.\nModal won't open. Message truncated unexpectedly.\n\nWhy this breaks:\nBlock Kit has strict limits that aren't always obvious:\n- 50 blocks per message/modal\n- 3000 characters per text block\n- 10 elements per actions block\n- 100 options per select menu\n- Modal: 50 blocks, 24KB total\n- Home tab: 100 blocks\n\nExceeding these causes silent failures or cryptic errors.\n\nRecommended fix:\n\n## Know and respect the limits\n\n```python\n# Constants for Block Kit limits\nBLOCK_KIT_LIMITS = {\n    \"blocks_per_message\": 50,\n    \"blocks_per_modal\": 50,\n    \"blocks_per_home\": 100,\n    \"text_block_chars\": 3000,\n    \"elements_per_actions\": 10,\n    \"options_per_select\": 100,\n    \"modal_total_bytes\": 24 * 1024,  # 24KB\n}\n\ndef validate_blocks(blocks: list) -> tuple[bool, str]:\n    \"\"\"Validate blocks before sending.\"\"\"\n    if len(blocks) > BLOCK_KIT_LIMITS[\"blocks_per_message\"]:\n        return False, f\"Too many blocks: {len(blocks)} > 50\"\n\n    for block in blocks:\n        if block.get(\"type\") == \"section\":\n            text = block.get(\"text\", {}).get(\"text\", \"\")\n            if len(text) > BLOCK_KIT_LIMITS[\"text_block_chars\"]:\n                return False, f\"Text too long: {len(text)} > 3000\"\n\n        if block.get(\"type\") == \"actions\":\n            elements = block.get(\"elements\", [])\n            if len(elements) > BLOCK_KIT_LIMITS[\"elements_per_actions\"]:\n                return False, f\"Too many actions: {len(elements)} > 10\"\n\n    return True, \"OK\"\n\n# Paginate long content\ndef paginate_blocks(blocks: list, page: int = 0, per_page: int = 45):\n    \"\"\"Paginate blocks with navigation.\"\"\"\n    start = page * per_page\n    end = start + per_page\n    page_blocks = blocks[start:end]\n\n    # Add pagination controls\n    if len(blocks) > per_page:\n        page_blocks.append({\n            \"type\": \"actions\",\n            \"elements\": [\n                {\"type\": \"button\", \"text\": {\"type\": \"plain_text\", \"text\": \"Previous\"},\n                 \"action_id\": f\"page_{page-1}\", \"disabled\": page == 0},\n                {\"type\": \"button\", \"text\": {\"type\": \"plain_text\", \"text\": \"Next\"},\n                 \"action_id\": f\"page_{page+1}\",\n                 \"disabled\": end >= len(blocks)}\n            ]\n        })\n\n    return page_blocks\n```\n\n### Using Socket Mode in Production\n\nSeverity: HIGH\n\nSituation: Deploying Slack bot to production\n\nSymptoms:\nBot works in development but is unreliable in production.\nMissed events. Connection drops. Can't scale horizontally.\n\nWhy this breaks:\nSocket Mode is designed for development:\n- Single WebSocket connection per app\n- Can't scale to multiple instances\n- Connection can drop (needs reconnect logic)\n- No built-in load balancing\n\nFor production with multiple instances or high traffic,\nHTTP webhooks are more reliable.\n\nRecommended fix:\n\n## Socket Mode: Only for development\n\n```python\n# Development with Socket Mode\nif os.environ.get(\"ENVIRONMENT\") == \"development\":\n    from slack_bolt.adapter.socket_mode import SocketModeHandler\n    handler = SocketModeHandler(app, os.environ[\"SLACK_APP_TOKEN\"])\n    handler.start()\n```\n\n## Production: Use HTTP endpoints\n\n```python\n# Production with HTTP (Flask example)\nfrom slack_bolt.adapter.flask import SlackRequestHandler\nfrom flask import Flask, request\n\nflask_app = Flask(__name__)\nhandler = SlackRequestHandler(app)\n\n@flask_app.route(\"/slack/events\", methods=[\"POST\"])\ndef slack_events():\n    return handler.handle(request)\n\n@flask_app.route(\"/slack/commands\", methods=[\"POST\"])\ndef slack_commands():\n    return handler.handle(request)\n\n@flask_app.route(\"/slack/interactions\", methods=[\"POST\"])\ndef slack_interactions():\n    return handler.handle(request)\n```\n\n## If you must use Socket Mode in production\n\n```python\nfrom slack_bolt.adapter.socket_mode import SocketModeHandler\nimport time\n\nclass RobustSocketHandler:\n    def __init__(self, app, app_token):\n        self.app = app\n        self.app_token = app_token\n        self.handler = None\n\n    def start(self):\n        while True:\n            try:\n                self.handler = SocketModeHandler(self.app, self.app_token)\n                self.handler.start()\n            except Exception as e:\n                logger.error(f\"Socket Mode disconnected: {e}\")\n                time.sleep(5)  # Backoff before reconnect\n```\n\n### Not Verifying Request Signatures\n\nSeverity: CRITICAL\n\nSituation: Receiving webhooks from Slack\n\nSymptoms:\nAttackers can send fake requests to your webhook endpoints.\nSpoofed slash commands. Fake event notifications processed.\n\nWhy this breaks:\nSlack signs all requests with X-Slack-Signature header using your\nsigning secret. Without verification, anyone who knows your webhook\nURL can send fake requests.\n\nThis is different from OAuth tokens - signing verifies the REQUEST\ncame from Slack, not that you have permission to call Slack.\n\nRecommended fix:\n\n## Bolt handles this automatically\n\n```python\nfrom slack_bolt import App\n\n# Bolt verifies signatures automatically when you provide signing_secret\napp = App(\n    token=os.environ[\"SLACK_BOT_TOKEN\"],\n    signing_secret=os.environ[\"SLACK_SIGNING_SECRET\"]\n)\n# All requests to your handlers are verified\n```\n\n## Manual verification (if not using Bolt)\n\n```python\nimport hmac\nimport hashlib\nimport time\nfrom flask import Flask, request, abort\n\nSIGNING_SECRET = os.environ[\"SLACK_SIGNING_SECRET\"]\n\ndef verify_slack_signature(request):\n    timestamp = request.headers.get(\"X-Slack-Request-Timestamp\", \"\")\n    signature = request.headers.get(\"X-Slack-Signature\", \"\")\n\n    # Reject old timestamps (replay attack prevention)\n    if abs(time.time() - int(timestamp)) > 60 * 5:\n        return False\n\n    # Compute expected signature\n    sig_basestring = f\"v0:{timestamp}:{request.get_data(as_text=True)}\"\n    expected_sig = \"v0=\" + hmac.new(\n        SIGNING_SECRET.encode(),\n        sig_basestring.encode(),\n        hashlib.sha256\n    ).hexdigest()\n\n    # Constant-time comparison\n    return hmac.compare_digest(expected_sig, signature)\n\n@app.route(\"/slack/events\", methods=[\"POST\"])\ndef slack_events():\n    if not verify_slack_signature(request):\n        abort(403)\n    # Safe to process\n```\n\n## Validation Checks\n\n### Hardcoded Slack Token\n\nSeverity: ERROR\n\nSlack tokens must never be hardcoded\n\nMessage: Hardcoded Slack token detected. Use environment variables.\n\n### Signing Secret in Source Code\n\nSeverity: ERROR\n\nSigning secrets should be in environment variables\n\nMessage: Hardcoded signing secret. Use os.environ['SLACK_SIGNING_SECRET'].\n\n### Webhook Without Signature Verification\n\nSeverity: ERROR\n\nSlack webhooks must verify X-Slack-Signature\n\nMessage: Webhook without signature verification. Use Bolt or verify manually.\n\n### Slack Token in Client-Side Code\n\nSeverity: ERROR\n\nNever expose Slack tokens to browsers\n\nMessage: Slack credentials exposed client-side. Only use server-side.\n\n### Slow Operation Before Acknowledgment\n\nSeverity: WARNING\n\nack() must be called before slow operations\n\nMessage: Slow operation before ack(). Call ack() first, then process.\n\n### Missing Acknowledgment Call\n\nSeverity: WARNING\n\nInteractive handlers must call ack()\n\nMessage: Handler missing ack() call. Must acknowledge within 3 seconds.\n\n### OAuth Without State Validation\n\nSeverity: ERROR\n\nOAuth callback must validate state parameter\n\nMessage: OAuth without state validation. Vulnerable to CSRF attacks.\n\n### Token Storage Without Encryption\n\nSeverity: WARNING\n\nTokens should be encrypted at rest\n\nMessage: Token stored without encryption. Encrypt tokens at rest.\n\n### Requesting Admin Scopes\n\nSeverity: WARNING\n\nAvoid admin scopes unless absolutely necessary\n\nMessage: Requesting admin scope. Use minimal required scopes.\n\n### Potentially Unused Scope\n\nSeverity: INFO\n\nCheck if all requested scopes are used\n\nMessage: Requesting users:read.email but may not use email. Verify necessity.\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs AI-powered Slack bot -> llm-architect (Integrate LLM for conversational Slack bot)\n- user needs voice notifications -> twilio-communications (Escalate Slack alerts to SMS or voice calls)\n- user needs workflow automation -> workflow-automation (Slack as trigger/action in n8n/Temporal workflows)\n- user needs bot for Discord too -> discord-bot-architect (Cross-platform bot architecture)\n- user needs full auth system -> auth-specialist (OAuth, workspace management, enterprise SSO)\n- user needs database for bot data -> postgres-wizard (Store installations, user preferences, message history)\n- user needs high availability -> devops (Scale webhooks, monitoring, alerting)\n\n## When to Use\n- User mentions or implies: slack bot\n- User mentions or implies: slack app\n- User mentions or implies: bolt framework\n- User mentions or implies: block kit\n- User mentions or implies: slash command\n- User mentions or implies: slack webhook\n- User mentions or implies: slack workflow\n- User mentions or implies: slack interactive\n- User mentions or implies: slack oauth\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","tags":["slack","bot","builder","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding"],"capabilities":["skill","source-sickn33","skill-slack-bot-builder","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/slack-bot-builder","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34515 github stars · SKILL.md body (41,504 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T12:51:47.832Z","embedding":null,"createdAt":"2026-04-18T21:45:07.436Z","updatedAt":"2026-04-22T12:51:47.832Z","lastSeenAt":"2026-04-22T12:51:47.832Z","tsv":"'+1':3737 '-1':1029,3720 '-12345':2993 '/block-kit-builder':689 '/enable-reactions':3409 '/help':1598 '/oauth/v2/authorize':1673 '/oauth_states':2780 '/slack/commands':3887 '/slack/events':598,1555,3877,4174 '/slack/install':1510,1536,2782 '/slack/interactions':3897 '/slack/oauth/callback':2823 '/slack/oauth_redirect':1514,1545 '/slow-task':2495,2594 '/status':1843 '/ticket':202,211 '0':457,999,3116,3673,3723 '1':2675,3122 '10':2528,3501,3563,3659 '100':670,3506,3518,3555,3567 '1024':3572 '2':2685,3129 '2.0':1240 '2000':825 '24':3571 '24kb':3514,3573 '3':217,613,2376,2424,2690,3136,4327 '300':2775 '3000':679,3496,3559,3634 '4':2697,3141 '403':2855,2875,4187 '45':3677 '5':2776,3961,4139 '50':664,1203,3492,3512,3547,3551,3603 '60':4138 '600':1502 '70':1284 'ab':4134 'abandon':1287 'abort':4102,4186 'absolut':4380 'access':1212,2929,2938,2945,3148 'accessori':1117,1159 'ack':207,219,353,361,436,443,619,988,994,1847,1849,2070,2082,2177,2186,2500,2504,2509,2546,2550,2556,2588,2599,2602,2605,2617,3413,3416,4292,4303,4305,4318,4322 'acknowledg':214,610,848,853,983,987,993,1008,1017,1024,1039,1043,1079,2378,2422,2470,4289,4310,4325 'across':12,24 'action':265,288,308,448,453,456,537,548,653,829,832,837,851,868,916,981,998,1049,1064,1065,1072,1077,1121,1162,1214,2097,2144,2163,2402,2553,2558,3504,3562,3638,3650,3656,3705,3715,3732 'ad':697 'add':1023,1280,1653,3381,3695 'addit':1283,1634,1640,3403,3437,3446 'adds/edits':2076 'admin':2718,3227,4372,4377,4384 'ai':4419 'ai-pow':4418 'alert':768,4441,4511 'allow':1725 'alreadi':2871 'also':2856 'alway':3490 'anti':607,1200,1694,1948,2347 'anyon':4012 'api':136,2445,2524,3009,3021,3124 'app':7,19,57,60,73,90,131,143,148,161,162,493,498,566,569,591,596,714,717,718,1234,1304,1311,1320,1453,1454,1455,1529,1534,1727,1742,1770,1776,1792,1805,1811,1816,1823,1824,1855,1879,1882,1893,1896,1907,1935,1938,1958,1967,1975,1992,2044,2051,2052,2479,2487,2488,2752,2951,3126,3161,3179,3183,3218,3221,3326,3355,3789,3844,3847,3870,3875,3927,3928,3931,3934,4054,4064,4065,4526 'app-level':1804,1815,1966,2950 'app.action':430,982 'app.client.chat':965,1585 'app.command':201,1842,2494,2593,3408 'app.event':497,1854 'app.message':178,1830 'app.oauth':1567,1610 'app.route':2781,2822,4173 'app.secret':2755 'app.slack.com':688 'app.slack.com/block-kit-builder':687 'app.step':2343 'app.view':346,2540 'appear':2646 'append':1031 'approach':70 'approv':431,435,440,484 'architect':4425,4469 'architectur':4474 'aren':3488 'arg':1573,1580,1616 'args.get':1622 'ask':4602 'assign':1114,1123 'assigne':1131,2157,2165,2171,2216,2219,2221,2283,2285,2300,2301 'assum':1712 'async':1358,1402,1892,1906,1916,1920,1929,1934,1942 'async_app.message':1914 'asyncapp':1898,1908 'asyncio':1905 'asyncio.run':1946 'asyncsocketmodehandl':1903,1933 'attack':2655,2656,2673,2709,2724,2854,2907,3205,3977,4131,4349 'audit':3144 'auth':3423,3434,3444,4478,4481 'auth-specialist':4480 'authent':76 'author':1665,2687,2726,2800,2820 'authorizeurlgener':2746,2802 'autom':2038,4450,4453 'automat':1507,2589,4048,4058 'avail':118,2226,4506 'avoid':4376 'await':1369,1424,1923,1940 'b':1052,1054 'b.get':1059 'back':1343,2693 'background':2517,2567 'backoff':3962 'bad':2985,3004,3028 'balanc':3807 'base':1669,1690,2778 'basestr':4146 'basic':1813,3278 'behind':1743,1751,1777 'benefit':93,1745 'best':50,466 'bidirect':1760 'block':30,253,256,259,277,280,300,303,374,381,388,487,490,524,615,632,636,651,665,671,675,682,727,732,734,833,958,960,963,977,978,1019,1022,1028,1050,1057,1060,1066,1073,1091,1093,1101,1103,1140,1145,1204,2086,2089,2135,2155,2173,2174,3177,3450,3461,3470,3482,3493,3500,3505,3513,3519,3538,3541,3544,3548,3552,3557,3576,3577,3583,3588,3589,3592,3600,3602,3605,3607,3620,3624,3645,3668,3669,3679,3691,3692,3700,3741,3744,4537 'block.get':3609,3613,3636,3640 'bodi':208,230,354,396,437,450,455,475,479,989,997,1002,1020,1084,1088,2547,2570 'bolt':10,22,59,64,142,146,712,1241,1318,1509,1790,2042,2477,2580,2586,4045,4052,4055,4089,4255,4531 'bool':3580 'bot':3,15,166,535,722,1379,1384,1390,1439,1443,1828,1912,1971,2056,2462,2492,2645,2906,2935,3002,3035,3261,3303,3318,3324,3755,3759,4069,4422,4431,4462,4468,4473,4492,4520 'bot/user':2890 'boundari':4610 'break':2419,2666,2924,3187,3481,3778,3995 'browser':4273 'bug':2107,2109 'build':5,17,72,137,644,693,706,725,731,961,1098,1102,1138,1143,1308,2036,3456 'builder':4,16,46,684,1978,1985,2026,2081 'built':108,3804 'built-in':107,3803 'button':428,432,441,540,552,657,830,842,859,908,980,1044,1223,3708,3725 'button/select':2432 'byte':3570 'call':1574,1617,2073,2180,2258,2350,2446,2522,2604,3010,3022,4041,4295,4304,4311,4317,4323,4446 'callback':236,1516,2332,2826,4336 'came':4032 'cancel':906 'case':3340,3342 'caus':3522 'chang':1719 'channel':417,474,476,950,967,968,1083,1085,1478,1480,1590,2810,2910,3044,3045,3234,3241,3268,3272,3280,3285,3293,3361,3363 'char':3558,3625 'charact':680,3497 'chat':1482,2812,3232,3264,3311,3331,3347,3358,3399 'check':103,1037,3417,4192,4395 'circl':742,746,750,753 'clarif':4604 'class':1350,3056,3922 'clear':2559,4577 'click':429,442,471,1048,1224,2434,3130 'client':209,355,438,505,990,1465,1469,1471,1475,1675,1679,2502,2548,2803,2807,2919,2989,2997,3414,4263,4279 'client-sid':2918,4262,4278 'client.auth':3425 'client.chat':415,472,1081 'client.views':226,515 'close':1607 'code':101,625,2003,2408,2695,2727,2880,2883,2885,2921,2964,4216,4265 'collabor':4413 'collect':2008 'command':38,200,206,213,1484,2385,2394,2431,2501,2525,2600,2612,2627,3302,3305,3307,3310,3315,3350,3351,3376,3401,3415,3892,3988,4544 'common':2958,3223 'communic':1761,4438 'comparison':4166 'complet':1367,1577,2256,2302,2305,2351,2404,2720,2886 'complex':3457 'compon':36,699,2039,2389 'compos':648 'comput':4142 'concept':1260 'config':1718 'configur':1252,2072,2084,2172,2185,2196,2663,3156 'confirm':875,894 'confront':1290 'connect':1820,1876,2957,3770,3787,3796 'consent':3201 'consist':111 'constant':3536,4164 'constant-tim':4163 'constraint':1784 'contain':176,188 'content':3665 'context':445,928,931,1026 'control':3697 'convers':4429 'corpor':1752,1778 'correct':2467 'could':2657 'cover':29 'craft':2711 'creat':245,399,406,425,546,550,702,1809,1965,2237,2287,2293,2328,2334,2344 'creation':225 'credenti':4276 'criteria':4613 'critic':740,2381,2638,2827,2893,3970 'cross':4471 'cross-platform':4470 'cryptic':3526 'cryptograph':2786 'cryptography.fernet':3053 'csrf':1710,2636,2654,2672,2853,4348 'custom':1987,1997,2013 'danger':867 'data':1273,1354,2016,2370,4151,4493 'databas':1342,1357,2444,2974,3050,4490 'database-back':1341 'databaseinstallationstor':1351,1496 'date':805,808 'db.execute':3080,3101 'db.installations.find':1425 'db.installations.upsert':1370 'decod':3117 'decrypt':1441 'def':180,203,349,433,501,601,724,946,985,1097,1137,1359,1403,1539,1548,1558,1569,1612,1638,1832,1844,1857,1917,1930,2068,2175,2253,2496,2518,2543,2595,2608,2783,2824,3058,3068,3092,3410,3574,3666,3880,3890,3900,3924,3938,4109,4177 'defin':1262,2064,2191,2224 'deleg':4414 'delet':1183,1186 'deni':901 'deploy':3139,3178,3753 'desc':279,290,378,380,382,409 'describ':4581 'descript':297,813,821,824 'design':3782 'detail':769,915 'detect':4208 'develop':556,631,1740,1756,1775,2412,3762,3784,3827,3829,3836 'devop':4507 'dict':729,953,1142 'differ':4024 'digest':4169 'dir':2779 'directori':3184,3222 'disabl':3721,3738 'disconnect':1962,3958 'discord':4464,4467 'discord-bot-architect':4466 'distribut':1303 'divid':826,828 'done':2532 'doubl':470,1047 'double-click':469,1046 'drop':3771,3798 'due':3167 'dynam':1217 'e':1068,1070,1076,2318,2323,3953,3959 'edg':2374 'edit':1172,1175,2069,2337,2338 'element':260,281,304,465,538,656,840,932,1030,1067,1074,2093,2139,2159,3502,3560,3639,3641,3644,3648,3658,3706 'email':3249,4410 'emoji':739 'enabl':1229,1452,2027,3411 'encrypt':1381,2976,3047,3061,3066,3077,3086,3091,3103,4353,4359,4366,4367 'end':3686,3694,3739 'endpoint':1737,3853,3985 'enterpris':1313,1375,1407,1415,1431,1433,3212,4486 'enterprise-grad':1312 'environ':160,2995,3835,4210,4224,4593 'environment-specif':4592 'error':1621,1623,1625,1630,2320,2458,2967,3471,3527,4197,4218,4240,4267,4334 'escal':4439 'even':2405,2459 'event':39,77,94,496,506,519,603,1560,1730,1860,1867,3380,3769,3882,3990,4179 'eventu':2463 'exampl':1346,3859 'exceed':1202,3449,3520 'except':2315,2316,3950,3951 'excess':1292 'exchang':2879 'execut':2012,2254,2341,2342,2355,2367 'exist':1656 'expect':4143,4155,4170 'experi':115,709 'expert':4598 'expir':1500,2773,2874 'expos':2368,2889,3121,3134,4269,4277 'exposur':2959 'extend':1981 'extract':362 'f':194,421,483,763,777,785,794,802,820,835,924,936,970,1035,1156,1174,1185,1196,1627,1689,1838,1863,1925,2531,3008,3020,3597,3628,3653,3717,3734,3955,4147 'fail':1620,1629,2257,2319,2353,2414,3011,3023,3465 'failur':1611,1615,3524 'fake':3980,3989,4020 'fallback':974,1209 'fals':1417,3596,3627,3652,4141 'featur':2035,2115,2117 'fernet':3055,3065 'fetchon':3112 'field':773,1446 'file':3386,3387 'fileinstallationstor':1330 'fileoauthstatestor':1335,1499,2751,2772 'find':1404,1418 'firewal':1744,1753,1779 'first':2606,4306 'fix':2469,2729,2978,3251,3529,3822,4044 'flask':582,584,590,592,1517,1520,1522,1528,1530,2737,2739,2753,3858,3865,3867,3869,3871,4098,4100 'flask_app.route':597,1535,1544,1554,3876,3886,3896 'flow':43,1247,1513,2643,2674,2722,2888 'focus':48,88 'footer':929 'forget':1963 'form':703 'found':2912 'foundat':61 'framework':11,23,65,642,2581,4532 'frontend':3032,3041 'frontend/client':2971 'full':2928,4477 'generat':1648,2677,2785,2816 'get':444,1538,1547,1600,2270,3093,3216,3615 'git':2915 'good':2994,3015,3037 'grade':1314 'grant':3191 'group':3366 'handl':40,75,95,174,181,198,204,210,343,350,357,427,434,439,979,986,991,1222,1242,1506,1515,1564,1570,1613,1833,1845,1858,1918,1960,2383,2497,2544,2587,2596,4046 'handle_slow_task.lazy':2607 'handler':564,594,620,1532,1877,1901,1932,3842,3873,4081,4315,4320 'handler.handle':605,1542,1552,1562,3884,3894,3904 'handler.start':571,1890,1941,3849 'hardcod':621,1213,2961,2980,4193,4203,4205,4227 'hashlib':4094 'hashlib.sha256':4161 'header':755,757,4005 'hello':177,179,182,189,1831,1834,1915,1919 'hesit':3164 'hexdigest':4162 'hey':195,1839,1926 'high':334,336,743,1767,1955,3154,3751,3814,4505 'high-volum':1766,1954 'histori':1479,2811,2916,3294,3364,3367,4502 'hmac':4092 'hmac.compare':4168 'hmac.new':4158 'home':494,499,503,509,523,707,3516,3554 'horizont':3775 'http':82,575,1736,3816,3852,3857 'id':229,232,237,257,266,278,289,301,309,395,398,405,412,419,424,452,477,518,549,834,839,852,857,869,874,917,922,927,938,940,996,1004,1011,1061,1078,1086,1122,1163,1177,1188,1199,1215,1218,1372,1374,1376,1378,1386,1389,1395,1397,1408,1410,1412,1428,1430,1432,1434,1466,1470,1592,1643,1676,1680,1687,2090,2098,2136,2145,2156,2164,2233,2239,2308,2310,2333,2569,2572,2804,2808,3027,3073,3085,3090,3097,3109,3111,3716,3733 'idempot':1225 'immedi':215,2471,2505,3140 'implement':2640 'impli':4518,4524,4530,4536,4542,4548,4554,4560,4566 'import':147,152,154,583,588,713,715,1319,1324,1329,1334,1336,1521,1526,1791,1796,1798,1897,1902,1904,2043,2047,2049,2478,2483,2485,2734,2738,2745,2750,3054,3840,3862,3866,3918,3920,4053,4091,4093,4095,4099 'incid':728,736,765,767,778,780,788,797,806,811,823,836,838,854,856,871,873,882,891,919,921,926,937,939,942,948,952,955,964,971,972,984,992,995,1009,1010,1063,1080 'incidents.example.com':925 'includ':1995 'increas':3204 'info':3371,4394 'inform':1814 'init':3059,3925 'initi':156,1449,1511,3397 'input':255,264,268,276,285,291,299,376,383,654,660,2009,2088,2134,2138,2143,2154,2158,2192,2197,2213,2220,2249,2250,2266,2268,2271,2275,2280,2284,2365,4607 'insert':3081 'instal':42,1227,1232,1272,1288,1353,1362,1364,1398,1405,1416,1419,1438,1494,1540,1565,1579,1581,1596,1603,1628,2642,2661,2784,2941,3083,3106,3166,3173,3200,4498 'installation.bot':1382,1387,1392,1588 'installation.enterprise':1377 'installation.installed':1400 'installation.team':1373 'installation.user':1396,1591 'instanc':3795,3812 'instead':1733,1973 'int':3672,3676,4136 'integr':47,140,1315,1518,2023,4426 'interact':35,464,489,646,698,1094,2388,2428,3902,4314,4562 'intern':1780 'invalid':2849,3469 'java':28,123 'javascript':26,121 'join':1682,3269,3270 'key':92,1258,2756,3062,3067 'kit':31,633,637,683,733,959,3451,3462,3483,3539,3542,3590,3621,3646,4538 'know':3530,4014 'label':269,292,337,2126,2147,2166,2236,2245 'larg':744,748 'later':1282,2473 'layout':696 'lazi':2583,2591 'leak':3209 'legaci':134 'len':3587,3601,3618,3632,3643,3657,3699,3740 'level':1806,1817,1968,2952 'limit':661,677,1762,3452,3486,3534,3540,3543,3591,3622,3647,4569 'line':99 'list':730,1647,3046,3284,3578,3670 'listen':491,2584,2592 'll':2451 'llm':2447,4424,4427 'llm-architect':4423 'load':3806 'local':1755,1774 'log':2897,2914,2965,2982,3005,3017,3145 'logger.error':3007,3019,3954 'logic':91,2014,3801 'long':2357,2621,3631,3664 'long-run':2356 'look':3290 'low':318,320,751 'lower':3172 'main':563,1871,1931,1945,1947 'make':3199 'malici':2662,2713 'manag':1632,1873,4485 'mani':3599,3655 'manual':4084,4258 'mark':1038 'match':2702,4578 'may':3210,4407 'medium':326,328,747,3454 'mention':1856,1859,1865,3322,3323,3327,3330,3335,3354,3356,4516,4522,4528,4534,4540,4546,4552,4558,4564 'menu':1133,1148,2433,3510 'menus':658,1096 'messag':175,183,187,191,461,480,647,649,667,695,701,1014,1021,1089,1206,1584,1835,1840,1921,1927,2321,2902,2968,3239,3267,3298,3346,3362,3458,3464,3476,3546,3594,4204,4226,4249,4274,4299,4319,4341,4362,4382,4402,4501 'message/modal':3495 'method':599,1537,1546,1556,3878,3888,3898,4175 'migrat':132 'minim':3262,3395,4387 'minimum':1265,3253 'minut':2777 'miss':2375,2452,3428,3768,4309,4321,4615 'modal':222,235,239,344,348,359,705,2085,2435,2541,2545,2555,3375,3472,3511,3550,3568 'modals/home':673 'mode':151,554,576,629,1721,1724,1795,1801,1889,1952,2482,2956,3747,3780,3824,3832,3839,3911,3917,3957 'mongodb':1348 'monitor':4510 'mrkdwn':529,775,783,792,800,818,885,934,1033,1112,1154 'multilin':286 'multipl':1306,3794,3811 'must':1274,1662,2699,3908,4200,4243,4293,4316,4324,4337 'n':779,787,796,804,822 'n8n/temporal':4458 'name':562,593,1531,1870,1944,2231,2240,2754,3872 'navig':3681 'necess':4412 'necessari':4381 'need':560,1220,1250,1266,1299,1493,1637,1749,1819,2623,3042,3198,3231,3248,3274,3276,3407,3436,3799,4417,4433,4448,4461,4476,4489,4504 'never':2403,2979,2986,3016,4201,4268 'new':129,1645,1667,1683,1715,2563 'next':1051,3731 'no-cod':2001 'node.js':122 'non':2029 'non-techn':2028 'none':1413,1448,3937 'note':1655 'notif':726,737,949,956,962,976,3260,3279,3991,4435 'notifi':413 'oauth':41,1226,1239,1246,1259,1368,1451,1462,1503,1512,1549,1571,1576,1614,1619,1707,2633,2641,2668,2714,2797,2825,2837,2887,3127,3151,3157,3189,4026,4329,4335,4342,4483,4568 'oauth-en':1450 'oauthset':1325,1464 'obvious':3491 'ok':3662 'old':1659,4128 'one':1426,2861,3240 'one-tim':2860 'open':220,227,495,500,513,3374,3475 'oper':616,1853,2359,2443,2626,4287,4298,4301 'option':312,392,1136,1166,2101,2207,3507,3564 'orang':745 'organ':110 'origin':1018,1027,1056,1092 'os':155,716,1337,1799,2050 'os.environ':164,170,567,720,1458,1467,1473,1677,1826,1880,1910,1936,2054,2060,2490,2757,2805,3000,3845,4067,4073,4105,4231 'os.environ.get':3834 'output':2015,2225,2230,2251,2252,2304,2306,2372,4587 'over-request':3224 'overflow':1132,1147,1161,1165 'page':3671,3675,3683,3685,3689,3690,3702,3718,3719,3722,3735,3736,3743 'page_blocks.append':3703 'pagin':3663,3667,3678,3696 'param':1674,1693 'paramet':1709,2635,2670,2851,4340 'pass':3448 'pattern':58,62,112,608,635,1201,1228,1695,1722,1949,1980,2348 'payload':105 'per':666,1205,3494,3498,3503,3508,3545,3549,3553,3561,3565,3593,3649,3674,3684,3688,3701,3788 'perfect':1738 'permiss':1263,1293,3128,3170,3193,4039,4608 'persist':1277 'placehold':1125 'plain':242,249,262,271,283,294,315,323,331,339,543,760,845,862,878,896,903,911,1127,1169,1180,1191,1703,2104,2112,2120,2128,2141,2149,2168,3711,3728 'platform':4472 'point':2960 'polici':3214 'possibl':2852 'post':600,1557,3266,3345,3879,3889,3899,4176 'postgr':4495 'postgres-wizard':4494 'postgresql':1347 'postmessag':416,966,1586 'potenti':4390 'power':1989,4420 'practic':51,467 'prefer':4500 'prevent':468,1045,2671,4132 'previous':3714 'primari':850 'print':1886 'prioriti':302,310,342,385,387,389,410 'privat':2909,3368 'process':84,2464,2472,2508,2510,2565,2578,2609,3992,4190,4308 'product':54,117,138,573,1339,1769,1957,2416,2769,3749,3757,3767,3809,3850,3855,3913 'production-readi':53 'progress':2361,3389 'prompt':3431 'proper':2730 'prototyp':686,1785 'provid':1208,2927,4061 'public':558,1309,1735,1747,3271,3365 'publish':516 'python':13,25,120,141,2474,2585,2733,2984,3051,3256,3392,3535,3828,3854,3914,4049,4090 'random':2678 'rate':3174 're':1664,2651,3433,3443 're-auth':3432,3442 're-author':1663 'reaction':3382,3383,3412,3429 'read':1481,1486,2908,3235,3281,3287,3297,3328,3357,3360,3369,3373 'read.email':3243,4405 'readi':55 'real':1758 'real-tim':1757 'receiv':1729,2830,2841,2844,2867,3329,3972 'recommend':69,1764,2468,2728,2767,2977,3250,3528,3821,4043 'reconnect':3800,3964 'record':1423,1436,1442 'red':741 'redi':1349,2766 'redirect':1550,2692,2742,2819 'refer':3337 'regist':2324,3308 'reject':3180,3219,4127 'reliabl':3820 'remov':463,488,1042 'replay':4130 'repli':3333 'report':803 'request':79,83,585,606,611,1264,1294,1295,1523,1543,1553,1563,1633,1639,1696,2429,2466,2512,2740,3149,3194,3226,3252,3391,3402,3868,3885,3895,3905,3967,3981,3999,4021,4031,4078,4101,4113,4119,4185,4371,4383,4398,4403 'request.args.get':2832,2884 'request.get':4150 'request.headers.get':4115,4122 'requir':1802,2421,3254,3343,4388,4606 'resolv':865,870,881,893,900 'respect':3532 'respond':185,2449,2503,2530,2601,2613,2629,3313,3320,3352 'respons':2552,2557 'rest':4361,4370 'result':2521,2533,2534,2624,2630,3424 'retain':1658 'return':604,754,1107,1149,1437,1447,1541,1551,1561,1602,1626,1688,2818,2848,2869,3033,3043,3113,3595,3626,3651,3660,3742,3883,3893,3903,4140,4167 'reusabl':2037 'review':3142,4599 'rich':33,645,694 'risk':1711 'robustsockethandl':3923 'rotat':3118,3131 'rout':78,1504 'row':3100,3115 'run':2262,2358,2409,2615 'runbook':945 'runbook.example.com':941 'safe':2877,4188 'safeti':4609 'save':1360,1363,2176,2183,2339,2340,3069 'say':184,193,1836,1837,1848,1850,1861,1862,1922,1924 'scale':3774,3792,4508 'scari':3169,3203 'scope':1261,1281,1391,1393,1477,1488,1491,1631,1635,1641,1646,1654,1660,1668,1681,1684,1698,1716,1822,2809,2815,2932,3152,3158,3190,3255,3263,3306,3325,3336,3344,3390,3396,3398,3404,3422,3438,3447,4373,4378,4385,4389,4392,4399,4580 'screen':3202 'sdk':2762 'second':218,614,1501,2377,2425,2529,2774,4328 'secret':169,173,1457,1461,1472,1476,2059,2063,2735,2759,4009,4063,4072,4076,4104,4108,4213,4220,4229,4234 'section':526,652,770,772,815,1109,1151,3611 'secur':102,1257,1783,2787,3175,3213 'see':2392 'select':307,311,390,391,1095,1120,1130,2096,2100,2162,2205,2206,2222,3102,3509,3566 'selector':1100,1106 'self':1361,1406,3060,3071,3095,3926,3940 'self.app':3930,3932,3946,3947 'self.cipher':3064 'self.cipher.decrypt':3114 'self.cipher.encrypt':3078 'self.handler':3936,3944 'self.handler.start':3949 'send':947,954,1582,2682,3029,3039,3439,3467,3585,3979,4019 'sensit':2369 'sent':2903,2969 'server':580,4284 'server-sid':4283 'servic':795,798,943 'session':2741,2758,2793,2796 'session.get':2836 'set':1323,1463,1812 'sever':738,766,786,789,2380,2637,2892,3153,3453,3750,3969,4196,4217,4239,4266,4290,4312,4333,4354,4374,4393 'severity_emoji.get':764 'share':1194,1197 'sharp':2373 'short':809 'shortcut':2386,2437 'show':1016,2083,2456 'side':2920,4264,4280,4285 'sig':4145,4156,4171 'sig_basestring.encode':4160 'sign':168,172,1456,1460,2058,2062,3997,4008,4028,4062,4071,4075,4103,4107,4212,4219,4228,4233 'signatur':3968,4004,4057,4112,4121,4126,4144,4172,4184,4237,4248,4252 'signing_secret.encode':4159 'silent':3523 'simpl':3259 'simpler':1754 'singl':3785 'situat':2382,2639,2894,3155,3455,3752,3971 'skill':4572 'skill-slack-bot-builder' 'slack':2,6,14,18,56,67,130,135,139,145,165,171,568,602,639,711,721,1310,1317,1459,1468,1474,1559,1678,1789,1827,1881,1911,1937,1982,2041,2055,2061,2420,2455,2476,2491,2684,2689,2691,2761,2806,2898,2925,3001,3123,3143,3182,3754,3846,3881,3891,3901,3975,3996,4003,4034,4042,4051,4068,4074,4106,4111,4118,4125,4178,4183,4194,4198,4206,4232,4241,4247,4259,4270,4275,4421,4430,4440,4454,4519,4525,4549,4555,4561,4567 'slack-bot-build':1 'slack.com':1672 'slack.com/oauth/v2/authorize':1671 'slack_bolt.adapter.flask':587,1525,3861 'slack_bolt.adapter.socket':150,1794,2481,3838,3916 'slack_bolt.adapter.socket_mode.async':1900 'slack_bolt.async':1895 'slack_bolt.oauth.oauth':1322 'slack_bolt.workflows.step':2046 'slack_sdk.oauth':2744 'slack_sdk.oauth.installation':1327 'slack_sdk.oauth.state':1332,2748 'slackrequesthandl':589,595,1527,1533,3863,3874 'slash':37,199,212,2384,2430,3301,3304,3309,3349,3987,4543 'slow':2442,2498,2514,2523,2577,2597,2610,2625,4286,4297,4300 'sms':4443 'socket':553,628,1720,1723,1800,1888,1951,2955,3746,3779,3823,3831,3910,3956 'socketmodehandl':153,565,1797,1872,1878,2484,3841,3843,3919,3945 'someth':2398 'sourc':624,2963,4215 'source-sickn33' 'specialist':4482 'specif':1271,2948,3192,4594 'spoof':3986 'sso':4487 'start':127,1601,1887,2539,3393,3682,3687,3693,3939 'startswith':1062 'state':369,1497,1708,2189,2575,2634,2669,2679,2696,2701,2731,2764,2770,2788,2789,2798,2799,2817,2829,2831,2833,2835,2838,2842,2845,2847,2850,2868,2870,4331,4339,4344 'state_store.consume':2858,2866 'state_store.issue':2790 'static':306,2095 'status':1846 'step':1979,1988,1998,2006,2019,2067,2071,2078,2184,2229,2255,2261,2267,2327,2330,2336,2346 'still':2603 'stop':4600 'storag':4351 'store':1255,1276,1328,1333,1344,1352,1495,1498,1700,2680,2705,2749,2765,2771,2791,2834,2846,2895,2972,4364,4497 'str':951,1644,2322,3063,3074,3076,3098,3099,3581 'strict':3485 'style':849,866 'submiss':345,352,360,2436,2542 'submit':247,252 'subsequ':2018,2228 'substitut':4590 'success':1568,1572,1578,1604,4612 'success/failure':1566 'sure':889 'surfac':3206 'symptom':2390,2644,2900,3162,3463,3758,3976 'system':403,1007,1852,2291,4479 'tab':504,510,674,708,3517 'take':2527,2619 'target':2536 'task':1116,1139,1141,1144,1157,1164,1176,1187,1198,2123,2125,2499,2598,2611,4576 'team':1371,1409,1427,1429,1642,1685,1686,3025,3026,3072,3084,3089,3096,3108,3110,3176 'technic':2030 'test':1787,3426,4596 'text':243,244,250,251,263,272,273,284,295,296,313,316,317,321,324,325,329,332,333,340,341,420,482,527,530,541,544,545,659,676,758,761,762,776,784,793,801,816,819,843,846,847,860,863,864,879,880,883,886,897,898,904,905,909,912,913,935,969,1034,1110,1113,1128,1129,1152,1155,1167,1170,1171,1178,1181,1182,1189,1192,1193,1210,1593,1704,2102,2105,2106,2110,2113,2114,2118,2121,2122,2129,2130,2142,2150,2151,2169,2170,2235,2244,2526,2628,3499,3556,3612,3614,3616,3619,3623,3629,3633,3709,3712,3713,3726,3729,3730,4153 'thank':1594 'though':2406 'thread':2486 'threading.thread':2535 'ticket':205,224,238,246,347,351,358,400,404,407,422,423,534,547,551,2091,2131,2198,2202,2232,2238,2241,2246,2273,2276,2288,2292,2294,2296,2307,2309,2311,2313,2329,2335,2345 'time':810,1759,2395,2862,3921,4096,4165 'time.sleep':3960 'time.time':4135 'timeout':2379 'timestamp':807,812,4114,4120,4129,4137,4149 'titl':240,258,267,274,371,373,375,408,426,781,876,973,1158,2137,2146,2152,2209,2212,2214,2279,2281,2298,2299 'token':158,163,167,570,622,719,723,1256,1267,1380,1383,1440,1444,1490,1587,1589,1657,1701,1713,1807,1818,1825,1829,1883,1885,1909,1913,1939,1969,1972,1976,2053,2057,2489,2493,2882,2891,2899,2911,2926,2936,2943,2953,2983,2991,2999,3003,3006,3013,3014,3018,3030,3034,3036,3048,3070,3075,3087,3094,3104,3119,3135,3208,3848,3929,3933,3935,3948,4027,4066,4070,4195,4199,4207,4260,4271,4350,4356,4363,4368 'token.encode':3079 'tokenstor':3057 'tool':1781 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'total':3515,3569 'traffic':3815 'treat':4585 'tri':2269,3943 'trick':2658,2717 'trigger':228,231,3378,4415 'trigger/action':4456 'true':287,3661,3942,4154 'truncat':3477 'ts':478,481,1087,1090 'tupl':3579 'twilio':4437 'twilio-commun':4436 'type':234,241,248,254,261,270,275,282,293,298,305,314,322,330,338,522,525,528,536,539,542,756,759,771,774,782,791,799,814,817,827,831,841,844,858,861,877,884,895,902,907,910,930,933,1032,1108,1111,1118,1126,1150,1153,1160,1168,1179,1190,1597,2087,2092,2094,2099,2103,2111,2119,2127,2132,2133,2140,2148,2153,2160,2167,2199,2203,2204,2234,2243,2274,2277,2295,2297,3610,3637,3704,3707,3710,3724,3727 'ui':34,634,641,3459 'unauthor':2901,3147 'unexpect':3478 'unknown':1624 'unless':3282,3288,3295,4379 'unnecessari':1697,3150 'unreli':3765 'unus':4391 'updat':459,473,502,507,1005,1013,1082,2179,2248,2362,2561,3137 'upfront':1699 'upload':3385 'upper':790 'url':559,923,1649,1670,1691,1748,2242,2247,2312,2314,2715,2801,2821,4017 'urlencod':1692 'use':8,20,126,574,627,650,681,692,1216,1302,1340,1773,1950,1970,2022,2033,2582,2760,2857,2863,2872,3339,3341,3745,3851,3909,4006,4088,4209,4230,4254,4282,4386,4401,4409,4514,4570 'user':190,192,197,394,397,411,414,418,449,451,486,512,517,520,1001,1003,1012,1041,1099,1105,1119,1124,1230,1278,1286,1366,1385,1388,1394,1411,1485,1487,1489,1651,1661,1841,1868,1928,1993,2011,2031,2075,2161,2182,2194,2223,2391,2568,2571,2659,2686,2814,2942,2949,3163,3242,3286,3292,3370,3372,3440,4404,4416,4432,4447,4460,4475,4488,4499,4503,4515,4521,4527,4533,4539,4545,4551,4557,4563 'v0':4148,4157 'valid':106,1706,2364,2632,2732,3575,3582,4191,4332,4338,4345,4595 'valu':319,327,335,363,367,370,372,377,379,384,386,393,454,458,855,872,920,1000,1173,1184,1195,2108,2116,2124,2187,2190,2200,2201,2208,2210,2211,2215,2217,2218,2272,2278,2282,2286,2573,2576,3088 'variabl':2996,4211,4225 'verif':80,2795,4011,4085,4238,4253 'verifi':2700,2828,3966,4029,4056,4083,4110,4182,4244,4257,4411 'via':1238,1731 'view':233,356,366,368,521,914,918,944,2178,2188,2549,2564,2574 'violat':3211 'voic':4434,4445 'volum':1768,1956 'vulner':2652,4346 'warn':3171,4291,4313,4355,4375 'web':579 'webclient':2990,2998 'webhook':3817,3973,3984,4016,4235,4242,4250,4509,4550 'websocket':1732,1875,1961,3786 'welcom':531,1583 'went':2399 'whatev':2931 'white':752,1036 'window':1609,2454 'within':216,612,2423,4326 'without':2360,2706,2975,4010,4236,4251,4330,4343,4352,4365 'wizard':4496 'won':3473 'work':113,1750,2410,2515,2520,2538,2648,3760 'workflow':45,1977,1984,2004,2005,2025,2066,2080,2265,2326,4449,4452,4459,4556 'workflow-autom':4451 'workflowstep':2048,2331 'workspac':1237,1270,1307,1422,2939,4484 'workspace-specif':1269 'write':1483,1821,2813,3233,3265,3312,3332,3348,3359,3384,3388,3400,3430 'wrong':2400 'x':4002,4117,4124,4246 'x-slack-request-timestamp':4116 'x-slack-signatur':4001,4123,4245 'xapp':1808,1884,2954 'xoxb':2937,2992 'xoxp':2944 'yellow':749 'yes':899","prices":[{"id":"44d8ae3b-302c-474e-a03e-8f8827b891d0","listingId":"51c5f199-45fe-48fb-915c-420fcfdb282e","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:45:07.436Z"}],"sources":[{"listingId":"51c5f199-45fe-48fb-915c-420fcfdb282e","source":"github","sourceId":"sickn33/antigravity-awesome-skills/slack-bot-builder","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/slack-bot-builder","isPrimary":false,"firstSeenAt":"2026-04-18T21:45:07.436Z","lastSeenAt":"2026-04-22T12:51:47.832Z"}],"details":{"listingId":"51c5f199-45fe-48fb-915c-420fcfdb282e","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"slack-bot-builder","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34515,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-22T06:40:00Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"f97ecd0b7e6799738e86fd033d73c166bb83294a","skill_md_path":"skills/slack-bot-builder/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/slack-bot-builder"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"slack-bot-builder","description":"Build Slack apps using the Bolt framework across Python,"},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/slack-bot-builder"},"updatedAt":"2026-04-22T12:51:47.832Z"}}