开发 Slack 应用并不是一个新话题,但很多人对“SlackCLI”这几个字是有误解的。乍一听,它像是一个用来发消息的命令行小工具;实际上,真正把 Slack 变成“可编程平台”的 CLI,和你想象中的那个 CLI,根本不是同一条技术路线。
这篇文章的目标很直接:帮你把 SlackCLI 这个关键词背后的真实技术选型讲清楚,然后带着你用官方 Slack CLI 从零构建一个能响应的自定义应用,同时也给运维和测试同学一条更轻量的路径。如果你正在犹豫“要不要引入 Slack CLI”“应该用官方 SDK 还是自己写脚本”,读完可以少走一段弯路。
1. 为什么“用命令行操作 Slack”这件事值得重视
先说一个比较普遍的场景。
很多团队用 Slack 不只是聊天,还会把构建结果、告警信息、自动化流程都接进来。以前最原始的做法是让机器人在某个固定频道里定时发消息,或者由人工去 Slack 后台点来点去配置 webhook。时间一长,问题就出来了:
- 频道和用户一变,配置就得手动改,维护成本高。
- 想做一个“主动读取频道消息、再根据内容执行动作”的能力,纯 Webhook 完全做不到。
- 团队内部要管理多个应用、多个 token,权限边界很难收敛。
- 所有配置分散在网页后台,无法像代码一样做版本管理、Code Review 和灰度发布。
SlackCLI 这类工具真正解决的,就是把 Slack 应用从一个“网页配置中心”变成一个“代码仓库”。大部分逻辑可以和普通软件项目一样提交、审查、测试、部署,而不是在后台页面里反复点鼠标。
这个判断很重要:SlackCLI 的定位不是“帮你发一条消息”,而是“让 Slack 的配置和逻辑进入工程化轨道”。如果你只是想发一条通知,那确实不需要它;如果你希望在 Slack 上构建自定义应用,那它就是最正规的起点。
2. 先分清:SlackCLI 到底指哪一个
在搜资料的时候,你会看到几个名字很像的项目,这里先做个概念区分。
| 常见叫法 | 真实指向 | 适用场景 |
|---|---|---|
| Slack CLI(官方) | Slack 官方提供的命令行开发工具,前缀命令通常是slack | 创建、调试、部署 Slack 自定义应用 |
| Slack Web API 脚本 | 基于@slack/web-api等 SDK 编写的 Node/Python/Java 脚本 | 快速发送消息、读取历史消息、管理用户 |
| Incoming Webhook | Slack 提供的一种只写接口,通过 URL 直接 POST 消息 | 最简单的一种消息推送 |
| 第三方命令行客户端 | 一些社区项目封装了聊天功能 | 早期工具,目前活跃度不稳定 |
这里最容易踩的误区是:以为搜到一个slack-cli仓库,就等于找到了官方唯一的工具。实际上,官方 CLI 和社区 CLI 的安装方式、职责边界完全不同。
官方 Slack CLI 的核心技术栈是 Deno 和 Slack SDK,它的核心抽象包括 App、Function、Workflow 和 Trigger。你可以把它理解为:Function 是能力单元,Workflow 是流程编排,Trigger 是触发入口,App 是整体打包与安装单元。
这个概念体系和“建一个应用、配权限、写代码、部署”的老流程相比,变化发生在架构层面,而不是简单多了一个命令行工具。
3. 官方 Slack CLI 的环境准备与前置条件
正式安装之前,先确认环境。
| 前置条件 | 建议 |
|---|---|
| 操作系统 | macOS 或 Linux,Windows 下建议使用 WSL |
| 运行时 | 官方 SDK 以 Deno 为主,建议先安装 Deno |
| Node.js | 如果后续用 Web API 脚本,Node.js 16 以上更稳妥 |
| Slack 账号 | 需要能创建应用的团队管理员权限 |
| 网络环境 | 安装依赖和 OAuth 回调需要能正常访问 Slack 服务 |
需要提醒一下:Slack 官方 CLI 的分发方式会随版本调整,不同年份安装命令可能不同,所以这里不推荐把某一条安装命令写死。正确做法是:打开 Slack API 官方文档,找到 CLI 或 SDK 的安装页面,按当前文档选择 Homebrew、npm 或安装脚本。
安装完成后,命令行里会出现slack命令,可以先做一次基础检查:
slack --version如果命令不存在,多半是安装目录没有加入 PATH,或者包名对不上,不要急着下一步。
接着登录你的 Slack 团队:
slack login这条命令会触发浏览器授权,把当前命令行会话和工作区关联起来。登录完成以后,后续的创建、部署命令就不需要重复输入凭证。
这一步看着简单,但实际项目里最容易出问题的是登录时遇到的回调异常。如果是公司内网有代理,授权回调可能失败,可以先在终端里观察日志,确认是否是网络策略导致。
4. 用官方 Slack CLI 创建第一个自定义应用
环境准备好之后,开始创建应用。官方 CLI 的核心命令是slack create,可以基于模板生成一个可运行的工程。
slack create my-app执行后,CLI 会列出官方模板,常见的有纯函数模板、空应用模板、示例应用模板。这里选择最基础的空白模板即可,后面逻辑我们自己写。
生成的目录结构大致是这样:
my-app/ ├── .slack/ ├── functions/ ├── workflows/ ├── triggers/ ├── manifest.ts ├── slack.json ├── deno.json └── imports_map.json各文件的作用:
manifest.ts:应用清单,声明应用名称、机器人权限、包含的函数和工作流。slack.json:CLI 项目配置,描述应用如何运行和部署。functions/:自定义函数的源码目录。workflows/:工作流编排代码。triggers/:触发器定义,决定用户在什么场景下启动工作流。
我们打开manifest.ts,把它改成下面这样:
// 文件路径:my-app/manifest.ts import { Manifest } from "deno-slack-sdk/mod.ts"; import { SummarizeWorkflow } from "./workflows/summarize_workflow.ts"; export default Manifest({ name: "summary-bot", description: "读取指定消息并生成摘要", icon: "assets/icon.png", workflows: [SummarizeWorkflow], botScopes: ["commands", "chat:write", "channels:history"], });这里有几个比较关键的点。
botScopes是权限声明,实际安装应用时会按这里申请的 scope 进行授权。权限范围越小越安全,比如我们只是读取历史和发送消息,就不要申请users:read或admin权限。workflows字段必须和你实际写的 Workflow 对应起来,如果只写了 manifest 却少了 Workflow 文件,部署时会直接报错。
5. 编写第一个自定义函数:把消息变成一段摘要
在官方 Slack CLI 的模型里,自定义能力都封装成 Function。我们可以做一个简单的“消息摘要函数”:接收一个频道 ID 和一段消息文本,返回一段加工后的摘要文本。
先创建函数文件:
// 文件路径:my-app/functions/summarize.ts import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; export const SummarizeFunction = DefineFunction({ callback_id: "summarize", title: "消息摘要", description: "把最新消息转换成一两句摘要", source_file: "functions/summarize.ts", input_parameters: { properties: { channel: { type: Schema.slack.types.channel_id, description: "目标频道", }, message: { type: Schema.types.string, description: "原始消息", }, }, required: ["channel", "message"], }, output_parameters: { properties: { summary: { type: Schema.types.string, description: "处理后的摘要", }, }, required: ["summary"], }, }); export default SlackFunction(SummarizeFunction, ({ inputs }) => { const raw = inputs.message ?? ""; const summary = raw.length > 50 ? raw.slice(0, 50) + "..." : raw; return { outputs: { summary: `频道 <#${inputs.channel}> 的消息摘要:${summary}` } }; });简单解释一下:
DefineFunction负责声明函数的输入输出参数类型,类型系统来自Schema,不是普通 JSON,而是带 Slack 平台语义的类型定义。callback_id是函数的全局唯一标识,后续工作流引用时靠它来对应。source_file指向本函数的源码路径,这个路径不能写错。SlackFunction是真正的执行函数,接收inputs,返回outputs。
这个函数目前做的事情很简单,但它演示了完整的“声明-实现-输出”结构。真实项目里,这个位置可以替换成调用内部 AI 接口、查询数据库、读取外部系统的数据,只要返回结果是结构化对象就行。
接下来创建工作流,把函数和“发送消息”这一步串起来。官方 SDK 里已经有内置的SendMessage函数,可以直接复用:
// 文件路径:my-app/workflows/summarize_workflow.ts import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; import { SummarizeFunction } from "../functions/summarize.ts"; export const SummarizeWorkflow = DefineWorkflow({ callback_id: "summarize_workflow", title: "消息摘要流程", description: "对指定消息做摘要,然后把结果发送到频道", input_parameters: { properties: { channel: { type: Schema.slack.types.channel_id, }, message: { type: Schema.types.string, }, }, required: ["channel", "message"], }, }); const step = SummarizeWorkflow.addStep(SummarizeFunction, { channel: SummarizeWorkflow.inputs.channel, message: SummarizeWorkflow.inputs.message, }); SummarizeWorkflow.addStep(Schema.slack.functions.SendMessage, { channel_id: SummarizeWorkflow.inputs.channel, message: step.outputs.summary, });Workflow 的本质是一个“步骤列表”。第一步调用我们自定义的摘要函数,拿到outputs.summary;第二步把它交给内置的发送消息函数。这里真正体现工程价值的是:每个步骤的输入输出都是显式声明的,平台的类型系统能够在部署前就发现参数不匹配的问题,而不是等到运行时才暴露。
6. 本地运行、调试与部署
应用写完以后,先不要急着部署。官方 CLI 提供本地开发模式,可以把这个应用装到一个临时开发工作区里,实时观察日志和输出。
slack run执行后,CLI 会启动本地调试进程,并创建一个对应的开发工作区。在这个模式下,你在my-app目录里改代码,保存之后会看到日志刷新,调试体验比“改完网页配置再刷新页面”好很多。
调试的时候,建议把终端日志级别调高:
slack run --debug--debug会输出应用配置、入参、出参等详细日志,排查“为什么函数没拿到我预期的字段”这类问题时非常有用。
确认本地运行正常之后,再执行部署:
slack deploy这个命令会把应用打包,并安装到当前登录的 Slack 团队中。部署完成后,应用的名字会出现在团队的应用列表里,机器人账号也会被创建。
如果应用是给内部项目用的,部署到当前团队就够了。如果是准备上架到 Slack App Directory 或供其他团队安装,还需要走更完整的发布流程。这一步先不展开,团队内部项目重点是把部署链路跑通。
部署后,应用还需要一个触发入口。一个常见入口是斜杠命令,比如/summary 一段消息。创建触发器的命令大致如下:
slack trigger create --trigger-def triggers/summary_trigger.ts触发器文件负责声明“用户在什么场景下可以启动工作流”。比如:
// 文件路径:my-app/triggers/summary_trigger.ts import { Trigger } from "deno-slack-sdk/mod.ts"; const trigger: Trigger = { type: "slash_command", name: "summary", description: "对当前频道的最新消息做摘要", workflow: "#/workflows/summarize_workflow", inputs: { channel: "{{data.channel_id}}", message: "{{data.text}}", }, }; export default trigger;注意这里用了模板字符串来引用斜杠命令运行时提供的上下文数据,这种方式比写死频道 ID 更通用,也更适合复用。
整体流程可以总结成:slack create创建项目,slack run本地调试,slack deploy部署安装,slack trigger create把应用暴露给用户。正常情况下,一条龙走完,团队的人就可以在频道里输入/summary测试了。
7. 轻量替代方案:用脚本直接发消息
如果你是运维或者后端同学,老板的需求其实就是“往 #dev-alerts 频道发一条部署结果”,那你其实不需要引入官方 Slack CLI 那套工程体系。用 Slack Web API 加一个几行的脚本就够了。
一个经典做法是使用 Node.js 的官方 SDK:
// 文件路径:send.js const { WebClient } = require("@slack/web-api"); const token = process.env.SLACK_BOT_TOKEN; const channel = process.env.SLACK_CHANNEL || "#dev-alerts"; const client = new WebClient(token); (async () => { try { const result = await client.chat.postMessage({ channel, text: "构建完成,新版本已发布。", }); console.log("发送成功,时间戳:", result.ts); } catch (error) { console.error("发送失败:", error.message); process.exit(1); } })();使用方式:
export SLACK_BOT_TOKEN="xoxb-你的机器人token" node send.js这个方案的优势是轻、直白、没有学习成本。但它也有明显的边界:
- 只能做“主动触发”的事情,无法接收事件,也无法响应命令。
- 所有逻辑都写死在脚本里,配置和 token 容易散落。
- 没有权限范围校验,一旦脚本被滥用,风险会扩散。
所以,我的建议很明确:运维告警、部署通知、定时上报这类“单向推送”场景,用脚本就够了;要交互、要权限管理、要流程编排,再用官方 Slack CLI。
8. 常见问题与排查思路
实际操作中,最容易出问题的不是写代码,而是环境、权限和触发链路。下面按经验列一份排查清单:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
slack命令找不到 | 安装路径未加入 PATH | 执行which slack和echo $PATH | 把安装目录加入 PATH,或重装 CLI |
slack login无法完成授权 | 网络代理导致回调失败 | 查看终端授权日志 | 调整代理策略后重新登录 |
slack create创建失败 | 团队策略禁止创建应用 | 查看工作区管理员配置 | 联系管理员或换用测试工作区 |
| 部署成功但斜杠命令不出现 | 触发器未创建或权限不足 | 执行slack trigger list查看 | 重新创建 trigger,确认已安装应用 |
| 函数输入参数为空 | 触发器模板参数写错 | 用slack run --debug查看入参 | 对照{{data.xxx}}字段名修正 |
| 机器人能说话但读不到消息 | 缺少channels:history权限 | 查看 manifest 的 botScopes | 补充权限后重新部署安装 |
看到slack run本地正常但部署后不生效时,优先怀疑“安装版本”没有更新,而不是代码逻辑。CLI 部署是重新打包安装,如果团队应用上有旧版本占用,需要确认部署过程确实覆盖到了目标工作区。
9. 最佳实践与工程建议
走到这里,功能已经跑通,接下来是让它经得住生产环境考验。
9.1 权限遵循最小化原则
这是最重要的一条。申请 scope 时只选择应用真实需要的能力,能申请chat:write就不要顺手加users:read。权限一旦发出去,相当于给了应用一把钥匙,宁可少申请,也不要为了“以后可能用得上”提前放开。
9.2 Token 和密钥不落盘、不提交
官方 CLI 和 Web API 脚本都会涉及 token。Token 应该通过环境变量或密钥管理服务注入,而不是写在代码仓库里。建议在项目根目录加一份.gitignore,把.env、token 文件全部排除。
9.3 工作流和函数保持单一职责
一个 Workflow 只做编排,一个 Function 只做一件事。比如上面的摘要例子,如果要扩展成“先汇总、再翻译、再发送”,正确做法是增加一个翻译函数,而不是把翻译逻辑塞进摘要函数里。这样每个步骤都能独立调试和测试。
9.4 触发器参数命名保持可读
Trigger 的输入参数会直接决定用户体验。建议使用和业务一致的命名,比如channel、text、ticket_id,不要使用a、b这类无意义缩写。参数一旦上线,后续修改会影响调用方,命名要谨慎。
9.5 先本地调试,再部署到生产工作区
slack run创建的本地开发工作区非常适合做预发布验证。团队内部可以约定:所有逻辑变更先通过slack run联调,确认没有报错再用slack deploy上生产。
9.6 对敏感信息做脱敏
如果应用会读取聊天内容,要在设计阶段就考虑数据隐私。日志里不要打印完整消息,函数输出也尽量避免泄露客户编号、密码类信息。如果消息摘要会保存到数据库,需要明确数据保留周期。
10. 总结与后续学习方向
SlackCLI 这个话题真正含金量不在于某个命令,而在于选择:官方 CLI 适合构建需要交互、权限和工作流编排的正式应用;Web API 脚本适合轻量级、单向的消息推送。把这两条路线分清,你的技术方案会干净很多。
如果你决定继续深入,下一步可以重点看三个方向:
- 官方 SDK 里的
Schema.slack.types有哪些内置类型,这决定了你的函数能做多复杂的事情。 - 事件订阅和触发器的高级写法,比如
shortcut、event触发模式。 - 将 Slack 应用与内部系统打通,比如从消息中读取工单号、查询数据库、调用内部 API,这才是自动化价值最大的地方。
实际操作时,建议找一个小而真实的需求开始,比如“把每日发布记录汇总到运维频道”。把它完整跑通一遍,比一次设计十个功能更有意义,也更容易建立团队对 Slack CLI 的信心。