{
  "schema": "snappy-card/1",
  "skill": "snappy-slack",
  "tier": "public",
  "origin_tier": "base",
  "purpose": "You handle Snappy's Slack channel: posting to channels, DMs, thread replies, urgent Robert notifications, and morning triage. Direct Slack Web API via `api.ts` -- no Xano middleware.",
  "verbs": [
    {
      "name": "channels",
      "args": [],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts channels"
    },
    {
      "name": "messages",
      "args": [
        "channel",
        "limit?"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts messages <channel>"
    },
    {
      "name": "search",
      "args": [
        "query",
        "limit?"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts search \"<query>\""
    },
    {
      "name": "thread",
      "args": [
        "channel",
        "ts"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts thread <channel> <ts>"
    },
    {
      "name": "send",
      "args": [
        "channel",
        "text"
      ],
      "effect": "send",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts send <channel> \"<text>\""
    },
    {
      "name": "reply",
      "args": [
        "channel",
        "ts",
        "text"
      ],
      "effect": "send",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts reply <channel> <ts> \"<text>\""
    },
    {
      "name": "react",
      "args": [
        "channel",
        "ts",
        "emoji"
      ],
      "effect": "write-reversible",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts react <channel> <ts> <emoji>"
    },
    {
      "name": "edit",
      "args": [
        "channel",
        "ts",
        "text"
      ],
      "effect": "write-reversible",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts edit <channel> <ts> \"<text>\""
    },
    {
      "name": "delete",
      "args": [
        "channel",
        "ts"
      ],
      "effect": "delete",
      "firstCall": "npx tsx ~/.claude/skills/snappy-slack/api.ts delete <channel> <ts>"
    }
  ],
  "faces": [],
  "agents": [],
  "uses": [
    "snappy-ops",
    "snappy-update",
    "snappy-clients",
    "snappy-freshbooks",
    "snappy-whatsapp",
    "snappy-telegram",
    "snappy-email",
    "snappy-imessage"
  ],
  "used_by": [
    "snappy-calendar",
    "snappy-course",
    "snappy-inbox-sweep",
    "snappy-telegram",
    "snappy-whatsapp"
  ],
  "checks": {
    "pass": 45,
    "fail": 8,
    "defer": 8,
    "line": "45 of 53 checks pass",
    "failing": [
      {
        "id": 4,
        "rule": "Near-neighbour descriptions cross-reference each other",
        "says": "near neighbours not named: snappy-desktop, snappy-docs, snappy-xano-mcp"
      },
      {
        "id": 8,
        "rule": "Skill body and reference graph stay within disclosure budgets",
        "says": "267 lines; ~3246 tokens; deep reference ../snappy-infra/auth-reference.md; deep reference ../snappy-infra/messaging-and-comms.md"
      },
      {
        "id": 10,
        "rule": "One term names each concept",
        "says": "mixed synonym sets: endpoint/url/route/path"
      },
      {
        "id": 12,
        "rule": "Description contains no unauthorised imperative",
        "says": "unauthorised description imperatives: read"
      },
      {
        "id": 40,
        "rule": "Metadata, skill body, and resources follow three-level budgets",
        "says": "metadata ~220 tokens; body ~3246 tokens"
      },
      {
        "id": 54,
        "rule": "At least three eval scenarios precede ready status",
        "says": "0 eval scenario(s); ready=true"
      },
      {
        "id": 55,
        "rule": "Eval scenarios are multi-call with ground truth",
        "says": "eval manifest missing"
      },
      {
        "id": 57,
        "rule": "Eval manifest contains a disjoint holdout",
        "says": "0 disjoint holdout scenario(s)"
      }
    ]
  },
  "coverage": "covers 9 of 174 operations of Slack, checked 2026-09-09",
  "spec": {
    "kind": "openapi",
    "vendor": "Slack",
    "version": "1.7.0",
    "url": "https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/slack_web_openapi_v2.json",
    "checked": "2026-09-09",
    "covers": 9,
    "of": 174
  },
  "evals": null,
  "files": [
    "AGENTS.md",
    "SKILL.md",
    "adapter.ts",
    "api.ts",
    "entities.json",
    "face.test.ts",
    "faces/components/slack-channel-list.css",
    "faces/components/slack-channel-list.tsx",
    "faces/components/slack-thread.css",
    "faces/components/slack-thread.tsx",
    "faces/family.tsx",
    "faces/fixtures/slack-channels.json",
    "faces/fixtures/slack-decision.json",
    "faces/fixtures/slack-draft.json",
    "faces/fixtures/slack-list.json",
    "faces/fixtures/slack-message.json",
    "faces/fixtures/slack-thread.json",
    "orbiter-status.ts",
    "refusals.test.ts",
    "workflows.md"
  ],
  "total_size": 150985,
  "checksum": "sha256:6ee3fbde2746644d9a9d5551c326a7f76cecd5cec00566e84c692e5ea23d66ca",
  "built_at": "2026-09-09T22:11:01.725Z",
  "openapi": {
    "openapi": "3.1.0",
    "info": {
      "title": "snappy-slack",
      "version": "0.0.0",
      "description": "Slack operations channel for Snappy over the Slack Web API directly (no Xano — banned 2026-08-30). Send channel messages, DMs, thread replies, urgent Robert-only notifications via `slack-notify-robert`. Read channel history, manage `#client-{name}` channels, triage unread, post morning briefings, route notifications from producer skills (snappy-update, snappy-content, snappy-blog, snappy-publish, snappy-freshbooks, snappy-pipeline, snappy-youtube, snappy-clients, snappy-deploy). Triggers on: slack, send slack, slack message, slack channel, slack DM, slack thread, slack notification, post in slack, notify team, notify robert, slack-notify-robert, slack briefing, morning slack, triage slack, client channel, #all-snappy, #social, #bugs-and-issues, #tech, #proj-total-crm, slack reply, slack history, slack digest, slack update, create slack channel, archive slack channel."
    },
    "x-snappy-transport": "cli",
    "servers": [
      {
        "url": "npx tsx ~/.claude/skills/snappy-slack/api.ts",
        "description": "The hand itself. Every path is a verb word, every query or body key an argument."
      }
    ],
    "paths": {
      "/channels": {
        "get": {
          "operationId": "snappy_slack_channels",
          "summary": "snappy-slack channels",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "parameters": [
            {
              "name": "json",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--json"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/messages": {
        "get": {
          "operationId": "snappy_slack_messages",
          "summary": "snappy-slack messages",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "parameters": [
            {
              "name": "channel",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "The Slack channel id (C…), or a channel name this hand resolves to one",
                "x-snappy-position": 0
              }
            },
            {
              "name": "limit",
              "in": "query",
              "required": false,
              "schema": {
                "type": "integer",
                "description": "How many messages to return, newest first",
                "default": 20,
                "maximum": 200,
                "x-snappy-position": 1
              }
            },
            {
              "name": "json",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--json"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/search": {
        "get": {
          "operationId": "snappy_slack_search",
          "summary": "snappy-slack search",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "parameters": [
            {
              "name": "query",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "A Slack search expression, exactly as the Slack search box takes it — `in:#tech from:@sam`",
                "x-snappy-position": 0
              }
            },
            {
              "name": "limit",
              "in": "query",
              "required": false,
              "schema": {
                "type": "integer",
                "description": "How many matches to return",
                "default": 20,
                "maximum": 100,
                "x-snappy-position": 1
              }
            },
            {
              "name": "json",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--json"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/thread": {
        "get": {
          "operationId": "snappy_slack_thread",
          "summary": "snappy-slack thread",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "parameters": [
            {
              "name": "channel",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "The Slack channel id (C…), or a channel name this hand resolves to one",
                "x-snappy-position": 0
              }
            },
            {
              "name": "ts",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "The message timestamp Slack uses as its id — `1788532320.001`, from a `messages` row's `ts`",
                "x-snappy-position": 1
              }
            },
            {
              "name": "limit",
              "in": "query",
              "required": false,
              "schema": {
                "type": "integer",
                "description": "How many replies of that conversation to return. The count is the FLAG --limit, never a positional word; default 20, ceiling 200",
                "default": 20,
                "minimum": 1,
                "maximum": 200,
                "x-snappy-flag": "--limit"
              }
            },
            {
              "name": "json",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--json"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/send": {
        "post": {
          "operationId": "snappy_slack_send",
          "summary": "snappy-slack send",
          "x-snappy-effect": "send",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": true,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channel": {
                      "type": "string",
                      "description": "The Slack channel id (C…), or a channel name this hand resolves to one",
                      "x-snappy-position": 0
                    },
                    "text": {
                      "type": "string",
                      "description": "The message's words, verbatim",
                      "x-snappy-position": 1
                    },
                    "json": {
                      "x-snappy-flag": "--json"
                    }
                  },
                  "required": [
                    "channel",
                    "text"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/reply": {
        "post": {
          "operationId": "snappy_slack_reply",
          "summary": "snappy-slack reply",
          "x-snappy-effect": "send",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": true,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channel": {
                      "type": "string",
                      "description": "The Slack channel id (C…), or a channel name this hand resolves to one",
                      "x-snappy-position": 0
                    },
                    "ts": {
                      "type": "string",
                      "description": "The parent message's timestamp — `1788532320.001`, from a `messages` row's `ts`",
                      "x-snappy-position": 1
                    },
                    "text": {
                      "type": "string",
                      "description": "The reply's words, verbatim",
                      "x-snappy-position": 2
                    },
                    "json": {
                      "x-snappy-flag": "--json"
                    }
                  },
                  "required": [
                    "channel",
                    "ts",
                    "text"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/react": {
        "post": {
          "operationId": "snappy_slack_react",
          "summary": "snappy-slack react",
          "x-snappy-effect": "write-reversible",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channel": {
                      "type": "string",
                      "description": "The Slack channel id (C…), or a channel name this hand resolves to one",
                      "x-snappy-position": 0
                    },
                    "ts": {
                      "type": "string",
                      "description": "The message timestamp Slack uses as its id — `1788532320.001`, from a `messages` row's `ts`",
                      "x-snappy-position": 1
                    },
                    "emoji": {
                      "type": "string",
                      "description": "The reaction's emoji name without colons — `thumbsup`",
                      "x-snappy-position": 2
                    }
                  },
                  "required": [
                    "channel",
                    "ts",
                    "emoji"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/edit": {
        "post": {
          "operationId": "snappy_slack_edit",
          "summary": "snappy-slack edit",
          "x-snappy-effect": "write-reversible",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channel": {
                      "type": "string",
                      "description": "The Slack channel id (C…), or a channel name this hand resolves to one",
                      "x-snappy-position": 0
                    },
                    "ts": {
                      "type": "string",
                      "description": "The message timestamp Slack uses as its id — `1788532320.001`, from a `messages` row's `ts`",
                      "x-snappy-position": 1
                    },
                    "text": {
                      "type": "string",
                      "description": "The message's words, verbatim",
                      "x-snappy-position": 2
                    }
                  },
                  "required": [
                    "channel",
                    "ts",
                    "text"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/delete": {
        "post": {
          "operationId": "snappy_slack_delete",
          "summary": "snappy-slack delete",
          "x-snappy-effect": "delete",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": true,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channel": {
                      "type": "string",
                      "description": "The Slack channel id (C…), or a channel name this hand resolves to one",
                      "x-snappy-position": 0
                    },
                    "ts": {
                      "type": "string",
                      "description": "The message timestamp Slack uses as its id — `1788532320.001`, from a `messages` row's `ts`",
                      "x-snappy-position": 1
                    }
                  },
                  "required": [
                    "channel",
                    "ts"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      }
    },
    "components": {
      "schemas": {
        "SnappyRefusal": {
          "type": "object",
          "required": [
            "outcome",
            "code",
            "message"
          ],
          "properties": {
            "outcome": {
              "type": "string",
              "const": "refused"
            },
            "code": {
              "type": "string",
              "description": "A row of the closed refusal table."
            },
            "message": {
              "type": "string"
            },
            "fix": {
              "type": "string"
            }
          }
        }
      }
    },
    "x-snappy-requires": [
      "SLACK_BOT_TOKEN"
    ],
    "x-snappy-vendor-spec": {
      "kind": "openapi",
      "url": "https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/slack_web_openapi_v2.json",
      "operations": {
        "channels": "conversations_list",
        "messages": "conversations_history",
        "search": "search_messages",
        "thread": "conversations_replies",
        "send": "chat_postMessage",
        "reply": "chat_postMessage",
        "react": "reactions_add",
        "edit": "chat_update",
        "delete": "chat_delete"
      },
      "pinned": {
        "sha256": "742a5c977180a829df8767cf57bc417d99b3713583aee83741efb9c08ca731e7",
        "checked_at": "2026-09-09T20:44:11Z",
        "version": "1.7.0"
      }
    }
  }
}