如果让我用一个词来形容过去大半年的工作状态,那就是"入口碎片化"。白天在网页控制台点按钮,晚上在另一套系统里填工单,中间还得打开聊天工具问同事某台服务器到底归谁管,一天下来光"切换系统"就耗掉一大半精力。所以我动手搞了个叫CLI-Anything的方案——它不是某一个具体工具,而是一套把任何系统与服务收口到统一命令行入口的通用接口方案。简单说,凡是能通过界面或接口完成的操作,都能以命令的方式执行:云资源管理、工单流转、知识库查询、日志检索,甚至那种只给你留了一个老旧网页后台的系统,都可以用一行命令跑通。这篇文章我会把整套方案的架构思路、落地过程和踩过的坑都摊开讲,适合运维、开发,以及所有手里攥着一堆后台系统、每天被迫反复横跳的人。
1. 根源问题:为什么我偏偏要把一切塞进命令行
1.1 工具越多,反而成了负担
很多团队的通病是系统越上越多,操作入口也越来越分散。我之前手头的系统包括:云控制台、工单平台、监控告警面板、内部知识库、数据库查询页面,有时还需要登录跳板机去碰服务器。每个平台都有自己的登录方式、自己的术语、自己的按钮位置。
表面看这些系统都提供了足够丰富的功能,但真正做事的时候效率极低。最典型的一个场景:夜间收到告警,需要先在监控面板确认指标,再跳到云控制台查实例状态,然后去工单系统提交一条处理记录,最后还要在群里@相关同事。这串操作如果纯靠人肉点击,快则十分钟,慢则半小时,中间任何一步忘了截图、填错了字段,后面追溯全是麻烦。
我一度尝试直接对接各家 API,结果发现更痛苦:有的系统是 REST 风格,有的是 GraphQL,有的还停留在老式 XML-RPC;鉴权模型也完全不同,有的要 AK/SK,有的要 OAUTH2 刷新 token,有的干脆只支持内网 IP 白名单加密码登录。真要逐个写脚本去调,工作量比手工点按钮还大。
1.2 用户真正的诉求不是"喜欢命令行"
后来我想明白一个问题:大家需要的不是"命令行"这种形式,而是命令行天然带出来的三样东西——可重复、可审计、可编排。
- 可重复:一段命令执行一百次,结果一致;人点按钮不行,手一抖就点错。
- 可审计:命令本身和执行结果可以被记成日志,将来出了事知道谁在什么时间做了什么;网页上的点击操作基本留不下这种痕迹。
- 可编排:命令行输出可以被其他程序消费,可以被放进定时任务或 CI 流水线;人肉点按钮永远无法实现自动化。
所以 CLI-Anything 的本质不是炫技,也不是否定图形界面存在的价值,而是把所有系统的能力翻译成"可重复、可审计、可编排"的动作。图形界面保留给低频、探索性的操作,高频、确定性、需要批量执行的操作全部走命令行入口。
2. 核心架构:我把它拆成"入口层、适配层、执行层"
2.1 三层抽象,缺一不可
设计 CLI-Anything 时,我没有上来就写代码,而是先想清楚一个核心问题:不同系统的差异到底藏在哪里?答案是:藏在交互细节里,而交互的上层语义其实是很接近的。
比如"创建一台服务器""创建一个工单""新建一篇文档",本质都是"在某个系统里创建一个资源"。区别只在于:调用哪个地址、带什么参数、用什么认证方式、返回什么结构。只要把这四件事抽象出来,就能形成统一入口。
所以我最终把架构分成三层:
- 入口层:负责接收标准化的命令输入。用户输入
cli-anything <系统> <动作> --参数,入口层把它解析成一条结构化的指令。 - 适配层:最核心的一层。它定义了"某个系统有哪些动作、每个动作需要哪些参数、怎么认证、怎么把用户参数映射成目标系统真正需要的格式"。这一层是可配置的,新接入一个系统时通常只改这里。
- 执行层:真正发请求、调接口、或者操作目标系统的地方。执行完成后,把原始结果再统一成标准输出格式。
用个生活化的类比:入口层是前台接待,记录你"要什么";适配层是翻译官,把你的话翻译成目标系统听得懂的方言;执行层是跑腿的人,真的去把事办成,再把结果带回来。这三层必须解耦,否则每接一个新系统都要动全局代码,根本维护不住。
2.2 为什么用"适配器"而不是"插件"这个说法
做架构选型时,我特意强调团队里统一叫"适配器",不叫"插件"。虽然两者在实现上很像,但语义完全不同:插件是"给本系统增加能力",适配器是"把别的系统的能力翻译过来"。搞清楚这个定位,大家写代码时才不会跑偏。
一个合格适配器要回答四个问题:
- 目标系统是谁,连接地址是什么?
- 有哪些动作可以暴露给用户?
- 每个动作的入参和出参长什么样?
- 出错时应该怎么翻译错误信息?
注意,适配器不关心目标系统是 API、SDK、数据库,还是只有 GUI。只要有办法把动作执行完成,它就可以被封装成同一条命令。这也是"Anything"信心的来源——不是每个系统都有接口,但只要有可操作的方式,理论上就能接入。
2.3 配置优先:把逻辑固化成 YAML,而不是写死代码
我见过很多类似的工具,每接一个系统就写一个 Python 模块,代码越堆越多,最后没人敢改。CLI-Anything 选择了另一条路:尽量把适配器的声明部分做成 YAML 配置。
一个最小适配器的配置长这样:
name: cmdb description: 内部资源管理系统 entry: auth: token base_url: https://cmdb.internal.local/api/v1 actions: list_servers: description: 查询服务器列表 method: GET path: /servers params: - name: env type: string required: false hint: 环境:prod/staging/dev output_fields: - instance_id - hostname - ip - status这份配置做的事情很简单:声明了一个叫cmdb的系统,它支持list_servers这个动作,动作是 GET 请求/servers,支持可选的env参数,输出时只保留四个字段。
用户执行:
cli-anything cmdb list-servers --env prod入口层把list_servers转成适配器内部的动作名,适配层补全 URL、鉴权头、参数映射,执行层发请求,最后输出标准化的表格或 JSON。整个过程不需要为这个系统单独写一行业务代码。
3. 从零到第一个可用命令:我不想再点网页按钮了
3.1 技术选型:Python + typer + httpx,够了
技术栈上我没有引入重型框架,核心就三样:
- Python 3.9+:生态成熟,自己实现适配器也方便。
- typer:用来做命令行参数解析和帮助信息生成,比 argparse 写起来省一半的代码,还自动带补全能力。
- httpx:用来发 HTTP 请求,支持同步和异步,也方便做超时和重试。
安装很简单:
pip install cli-anything typer httpx pyyaml这里我多说一句选型理由:为什么不直接用requests?因为httpx对 HTTP/2、超时控制、以及未来的异步改造都更友好。而typer的引入是为了让命令的--help输出非常友好,这对团队推广很有价值——你在终端里敲cli-anything cmdb --help,它直接告诉你该填什么参数、怎么填。
3.2 最小示例:把"远程执行命令"封装成 cli
很多人刚接触这套思路时,最容易理解的例子就是"把 SSH 远程执行命令封装成命令"。我拿它当作第一个适配器来跑通全流程。
首先在配置目录下新建adapters/ssh_exec.yaml:
name: ssh_exec description: SSH 远程命令执行 entry: auth: key default_user: ops actions: exec: description: 在远程服务器执行命令 method: ssh params: - name: host type: string required: true hint: 目标主机 IP 或主机名 - name: cmd type: string required: true hint: 要执行的命令 output_fields: - host - exit_code - stdout - stderr然后执行:
cli-anything ssh_exec exec --host web-01 --cmd "uptime"输出:
{ "host": "web-01", "exit_code": 0, "stdout": " 14:23:11 up 92 days, 4:12, 1 user, load average: 0.08, 0.03, 0.05", "stderr": "" }第一次跑通这个流程后,我最大的感触是:之前每次排查问题都要先登录跳板机、记 IP、敲 ssh 命令,现在统一入口之后,我甚至可以把这条命令嵌进自己的监控工具脚本里。
3.3 输出格式化的问题,我为什么放到最后才处理
项目初期我犯过一个典型错误:一上来就折腾终端表格、彩色高亮、进度条。结果写了三天,真正能用的命令没几条。后来我把输出格式化全部推迟,并定了一个规矩:适配器只负责返回结构化对象,输出渲染统一由框架处理。
也就是说,适配器执行完动作后,返回一个普通的数据结构,框架根据用户指令选择渲染方式:
--output table:人类阅读友好的对齐表格。--output json:给脚本和其他程序吃。--output text:简洁的单行文本,适合嵌入告警消息。
这样做的收益非常大:调试时用 JSON 看完整字段,日常用表格快速扫描,写脚本时用 JSON 再处理。而且后续想新增渲染格式,只需要改框架的输出层,不用动任何适配器。
4. 实战案例:把四个孤岛系统收归到一个入口
4.1 案例背景:真的有人这么用
我有位做内部运维平台的朋友,团队里系统不少,但最常用的其实就四个:工单系统(网页表单)、云控制台(有 API 但权限模型复杂)、知识库(不支持脚本)、数据库查询平台(只读账号)。他们一直想搞自动化,但每个系统的接入方式都不一样,推进很慢。
我直接用 CLI-Anything 的适配层帮他们做了接入。做法很简单:对每个系统,先做三件事——读文档、列高频操作、写适配器。注意顺序很重要,很多团队一上来就想把系统的所有功能都封装,结果适配器写了一堆,用户根本用不上。我的原则是:只封装高频且低频变动的操作,冷门操作保留原生接口透传能力,绝不强行封装。
4.2 每个系统到底封装了什么
工单系统没有 API,只有网页表单。这听起来很绝望,但实际上表单提交就是一次 POST 请求。我们通过浏览器开发者工具抓到表单的提交结构和 cookie 策略,然后用httpx模拟提交。适配器配置如下:
name: itsm actions: create_ticket: description: 创建工单 method: POST path: /api/ticket/create params: - name: title required: true - name: priority required: false default: P3 - name: desc required: true output_fields: - ticket_id - status云控制台有 API,但权限模型很绕,直接调接口需要先换 token 再签请求体。这部分我们没有硬啃完整文档,而是只封装了三个高频操作:查实例列表、查实例详情、重启实例。重启这种操作还特意加了二次确认参数--yes,防止手滑。
知识库就更有意思了,它连登录表单都是内嵌的。我们通过维护会话 cookie,把"搜索文档""读取详情"两个动作封装出来。效果是:之前要打开网页慢慢翻的文档,现在一行cli-anything wiki search --keyword "故障预案"就能出结果,还能直接拿到文档链接。
数据库查询平台属于最标准的场景:只读账号 + 查询接口。我们封装了query动作,参数是 SQL 语句,输出统一转成 JSON。为了防止误操作,适配器对非 SELECT 开头的语句一律拦截,并且设置单次查询最大返回行数。
4.3 接入之后的变化
四个系统接入后,朋友团队里出现了一个变化:跨系统操作开始变成"一条命令的事"。比如处理一次服务器告警,他们现在的操作是:
cli-anything cloud describe-instance --id i-xxxxx --output json cli-anything db query --sql "select * from metric where host='web-01' order by ts desc limit 10" cli-anything itsm create-ticket --title "web-01 CPU 告警处理" --priority P2 --desc "已确认实例状态,正在深入排查"三条命令,不到十秒出结果,全程有日志记录。更重要的是,由于命令可编排,他们加了一条定时任务:每天晚上三点自动做一次服务器健康巡检,发现 CPU 连续五次超过阈值就自动创建 P2 工单。这个流程换人工做,基本不可能坚持执行超过两周。
5. 踩坑记录:这些坑不填,封装越多越翻车
5.1 权限与凭据管理:千万别把密码写进 YAML
最早我图省事,直接在 YAML 里写明文 token,因为内网系统觉得无所谓。结果一次仓库权限配错,差点把密钥推到公共代码库。从那以后我定了死规矩:配置文件里只允许出现凭据引用标记,真正的密钥放到环境变量或系统钥匙串中。
配置里长这样:
entry: auth: token token_env: CMDB_TOKEN框架加载适配器时,检测到token_env就去读环境变量CMDB_TOKEN,读不到就报错终止,绝不默认用空值。涉及密码的适配器,我建议直接接入操作系统的 keyring 服务,Python 里用keyrings.alt这个库就能统一封装。
另外提醒一句:日志里绝对不能打印凭据。很多框架默认会把请求头或上下文打入日志,必须显式配置脱敏规则,把 Authorization、password、token 这些字段在输出前打码。
5.2 超时与重试:GUI 系统可没有"标准超时"这一说
调用标准 API 时,接口通常会给明确的超时时间或错误码。但适配器对接的不全是 API,有的是模拟网页提交,有的是连数据库查询。这类场景最大的坑是:你根本不知道目标系统什么时候会"假死"。
我给的解决方案是在框架层面统一提供超时和重试参数,并且让每个适配器可以单独覆盖默认值:
cli-anything itsm create-ticket --title "测试" --timeout 30 --retry 2--timeout控制单次操作最长等待时间,--retry控制失败后的重试次数。重试之间加指数退避,第一次等 2 秒,第二次等 4 秒,避免目标系统刚缓过来又被刷垮。
5.3 并发冲突:两个命令同时提交,系统直接懵了
有段时间我们发现工单系统偶尔会创建出重复工单。查了一圈,不是适配器的问题,而是操作员同时跑了两个终端窗口,同一个账号在同一秒内提交了两次表单。这对人有界面时不会发生,因为页面会跳转;但脚本化之后,并发完全不受控制。
解决方式是在框架里给写操作加了一个最小锁机制:每个适配器可以声明自己的锁文件路径,命令执行前先尝试获取锁,拿不到就排队等待,默认超时 10 秒。比如创建工单前会锁住这台机器的~/.cache/cli-anything/locks/itsm.create.lock,执行完再释放。
这个设计不算高级,但在真实场景中非常有效。它解决的是"同一账号写冲突"这个最底层的问题,至于跨机器共享锁,就要接 Redis 之类的分布式锁了,属于更大场景的优化话题,这里不展开。
5.4 错误信息要"说人话",别把底层报错甩给用户
所有适配器都会出错,但直接把底层错误抛给用户是最劝退的做法。比如数据库查询超时,你让用户看到一段 Python traceback,没有任何意义。
我给框架加了一个error_translator机制:每个适配器配置一段错误翻译映射,把目标系统的常见错误码翻译成中文提示。
error_translator: - match: "timeout" hint: "目标系统响应超时,请稍后重试,或使用 --timeout 参数调大等待时间" - match: "permission denied" hint: "当前账号权限不足,请检查该账号是否有对应操作权限" - match: "duplicate" hint: "检测到重复提交,请确认工单列表中是否已存在相同内容"框架捕获异常后,先查翻译表,能匹配就输出提示,匹配不到再输出原始信息。这个细节决定了 CLI-Anything 能不能推给团队别人用——只有做好错误提示,别人才敢真正依赖命令行干活。
6. 再进一步:扩展机制与生态融合
6.1 写一个自己的适配器插件
前面讲的都是通过 YAML 配置接入简单系统,但如果目标系统的操作逻辑很复杂,比如要计算签名、要做状态机转换,这时候纯配置就搞不定了。所以我预留了 Python 适配器的扩展口子。
写一个适配器只需要继承基类,实现handle方法:
from cli_anything import BaseAdapter class CustomAdapter(BaseAdapter): name = "custom_api" def handle(self, action: str, params: dict): if action == "do_something": # 在这里调用你想要的任何 Python 逻辑 result = self._call_target_system(params) return result.to_dict() raise ValueError(f"不支持的动作: {action}")框架会自动发现这些自定义适配器。用它来处理签名计算、文件上传、或者需要组合多个请求的复杂流程都很顺手。
6.2 接入 shell 自动补全和 CI 流程
CLI 工具如果只靠记忆命令,推广起来阻力很大。typer 帮了我一个大忙:它自带 shell 补全生成功能。框架安装后,只需要执行:
eval "$(cli-anything completion install)"之后在终端里敲cli-anything cmdb再按 Tab,就会自动列出所有可用的动作和参数提示。这个体验已经非常接近网页下拉菜单了,新人基本不用看文档。
CI 流程方面,我们直接把关键命令接进了流水线。比如发布部署前的检查步骤,原来是运维手工确认,现在流水线里插入一段:
stages: - precheck precheck: script: - cli-anything db query --sql "select count(*) from pending_release" --output text - cli-anything cloud describe-instance --id $DEPLOY_TARGET --output json这样做的意义在于:命令本身成为流程的一部分,可回放、可对比。哪一次发布失败,翻日志就能精确看到当时每个系统的状态。
6.3 后续演进:从命令入口走向 Agent 入口
CLI-Anything 做到这个阶段,我已经达到了最初的目标:把高频操作收拢到命令行。但观察一段时间后发现,还可以往两个方向演进。
- 一是自然语言转发:既然命令已经标准化了,就可以在上面叠一层自然语言解析,比如输入"创建一台 2C4G 的香港测试机",自动转成对应命令。这不是异想天开,很多大模型已经能做意图识别,剩下的就是把它接到入口层。
- 二是共享适配器市场:现在每个团队都在重复封装云厂商、工单系统、数据库这些通用对象。如果适配器配置能像 npm 包一样集中发布和拉取,不同团队就不用反复造轮子。
当然,这些方向都建立在"入口层、适配层、执行层"稳定的前提下。只要抽象设计扛得住,往上叠多少层安全、多少层智能,都只是时间问题。
最后分享一点我自己的体会:做 CLI-Anything 最让我意外的不是"命令操作比网页快"这种显而易见的事,而是它改变了团队处理问题的思路。以前遇到重复操作,大家的默认反应是"再点一遍吧,反正也不多";有了统一入口之后,大家的第一反应变成"这个能不能抽成命令,跑进自动化里"。这种从"手动做事"到"自动做系统"的转变,比工具本身的价值大得多。
如果你也想动手搞一套,我的建议非常具体:不要试图一开始就把所有系统都接进去,挑你每天重复次数最多的三个操作开始,哪怕只是把"查监控、看日志、提单"变成三条命令。等用户习惯了 CLI-Anything 的交互方式,后面接入新系统就是水到渠成的事。