
你有没有遇到过这种状态电脑里堆着几十个脚本有Python写的、有Shell写的、还有几段早忘了出处的Node小工具。想重新用的时候先得回忆它们放在哪、有什么参数、依赖装没装。我刚接触命令行自动化那几年就是这样后来我花了几个晚上做了一个统一入口把所有零散的脚本、任务、接口调用全部收敛到同一条命令下面。这个项目我把它叫作CLI-Anything——一个让“任何东西”都能变成命令行工具的小框架。CLI-Anything 不是某个单一功能的轮子而是一套把工具、脚本、API、甚至大模型能力统一收敛到命令行入口的实现方案。它能帮你把重复工作变成一条命令把散落各处的脚本收拢成一致的调用体验把人工操作变成可记录、可组合、可自动化的流水线。这篇文章适合正在做自动化改造、写内部工具、或者受够了碎片化脚本的开发者阅读。你可以直接抄走核心架构和实现思路再按需扩展自己的动作库。1. 为什么是CLI从脚本碎片化到统一入口的价值回归先聊一个反直觉的结论在图形界面越来越强的今天命令行依然是自动化场景里最值得投资的技术入口。窗口再好看它难以被另一个程序直接调用难以组合成管道难以在无人值守时稳定执行。真正需要“把事情自动做掉”的场景最后落地的形态几乎都和命令行有关。1.1 碎片化脚本的代价多数团队走到某个阶段都会遇到脚本失控的问题。运维脚本在A机器上数据清洗脚本在B仓库批量重命名工具是同事随手发的Python文件定时任务散落在cron里。你最需要的不是再写一个脚本而是有一个地方能统一描述“我有哪些能力、怎么调用它们、有没有副作用”。CLI-Anything 正是冲着这个问题去的。它强调“Anything”不是说要把所有功能都塞进一个二进制文件里而是把入口统一在一处、把注册机制打开、把约定固定下来。插件可以是任何语言写的可执行程序只要是能通过标准输入输出和命令行参数交流的东西都能被纳入这套体系。1.2 命令行的三个本质优势第一可组合。命令行工具天然支持管道和参数传递你可以把“爬取内容”和“转换格式”拆成两步再串成一条流水线。第二可记录。命令历史、日志、脚本文件本身都是执行过的证据哪天想复盘“我到底对数据做了什么”看命令记录比翻操作录屏省力得多。第三可远程。SSH到一台服务器上没有图形界面可用CLI就是最稳定的交互方式。这三个优势在人工智能工具越来越流行的当下反而被放大了。模型调用需要明确的工具定义CLI工具恰好可以作为一种“可描述、可调用”的执行单元接入到模型的工作流中。后面我会单独讲这块。1.3 CLI-Anything 的定位边界这套框架不是终端模拟器也不是高级Shell的替代品。它的核心定位是“企业内部或个人的动作总线”让使用者通过一套统一的规则快速定义新动作并安全地暴露给需要的人或程序调用。举个边界例子文件处理、数据转换、服务健康检查、定时生成报告这些适合放进来。而频繁交互的数据库客户端、编辑器这类工具强行做成CLI反而降低效率。做框架设计时先想清楚哪些“Anything”值得收进来哪些应该留在原处比急着写代码更重要。2. 核心架构拆解一个入口、三类模块CLI-Anything 的架构不复杂核心思想可以用一句话概括用统一的命令解析层接收请求用注册表来完成路由用插件机制来扩展能力边界。整体上分成三类模块入口解析层、动作注册表、执行与安全模块。2.1 入口解析层所有操作的统一大门这一层负责把用户在终端里敲入的命令解析成结构化参数。比如用户输入cli-anything logs clean --days 7 --dry-run解析层需要识别出动作名是logs clean参数是days7和dry-runtrue。实现上可以选择成熟的命令行解析库Python推荐 Click 或 ArgparseNode 生态可选 CommanderGo 有 Cobra。原则是不要自己造参数解析的轮子把精力留给动作本身。入口层还要负责一个容易被忽略的事命令补全。配置好 Shell 自动补全后用户敲一个前缀就能看到候选动作和参数提示这会把“统一入口”的体验拉到很高的水平。Click 自带click-completion之类的扩展基本配置一次就能在 zsh 和 bash 里生效。2.2 动作注册表框架的大脑注册表解决的是“这个动作到底在哪、由谁执行”的问题。每接入一个新任务就在注册表里登记一份元信息动作名、描述、参数定义、执行方式、权限级别。我把注册表设计成了一张 TOML 格式的配置文件加一个内存索引。TOML 文件负责持久化让每个新动作都像填表一样容易添加内存索引负责运行时快速查找避免每次执行都解析一遍配置。下表是我实际使用的一张注册表示例字段可以直接套用字段说明示例name动作唯一名称logs cleandescription简短描述会显示在帮助信息中清理指定天数的旧日志exec实际执行命令python3 -m tools.log_cleanerargs参数声明列表days: int7, dry_run: boolfalsepermission所需权限等级user/admin/roottimeout超时时间防止任务卡死60s注册机制最大的价值是“可发现性”。有了这张表团队成员不用猜你有哪些脚本敲一句cli-anything list就能看到所有可用动作。我在实践中发现这东西对团队效率的提升比想象中明显得多。2.3 执行与安全模块守门员角色执行模块负责按注册表信息拉起真实进程并转发参数。这里有两个关键设计。第一所有参数在传给真实进程前要做校验禁止将用户输入的原始字符串直接拼进Shell命令这个我后面会专门展开讲。第二每个动作声明自己的权限级别框架根据执行者身份做拦截。比如“删除服务器日志”和“查询日志条数”不应该拥有同样的权限。安全模块还要支持--dry-run全局参数这个参数在任何动作上都可以加。加了之后框架只打印“将要执行的命令和影响范围”不实际执行。这个习惯如果从一开始就建立会在后续接入AI时救你无数次。3. 把“任何东西”变成可调用命令核心实现细讲架构说清楚后就到了动手环节。我用 Python 做了一套最小可用的 CLI-Anything 实现包括项目结构、注册机制、参数解析和执行引擎。你完全可以照这个基础自己扩展。3.1 项目骨架与统一入口一个合理的目录结构大约是这样cli-anything/ ├── pyproject.toml ├── cli.py ├── registry.toml └── actions/ ├── __init__.py ├── log_cleaner.py ├── docs_generator.py └── health_checker.pycli.py就是所有人面对的入口文件。它读取registry.toml初始化 Click 命令组再把注册表中的每个动作映射为 Click 命令。这样写的好处是用户新增动作完全不用改cli.py本身只需要在registry.toml里加一段声明再在actions/目录下放一个实现文件。3.2 注册表驱动命令生成我用一段精简代码演示核心逻辑。注意它不是完整实现但思路可以直接迁移import click import tomllib from pathlib import Path REGISTRY_PATH Path(__file__).parent / registry.toml def load_registry(): with open(REGISTRY_PATH, rb) as f: return tomllib.load(f) click.group() def cli(): CLI-Anything 统一入口 def build_command(action): 根据注册表元信息动态构造 Click 命令 click.command(nameaction[name].split()[-1]) def cmd(): subprocess.call(action[exec].split()) return cmd registry load_registry() for action in registry[actions]: cli.add_command(build_command(action)) if __name__ __main__: cli()这只是一个雏形。实际做的时候参数声明也必须从注册表读出来动态构造click.option才能真正实现“改配置即加命令”。我已经跑通这套流程完整版的build_command会解析args字段里的类型和默认值再映射到 Click 的参数模型上。3.3 动作实现的标准协议为了让所有动作都能被框架统一调度我建议每个动作实现成可以被命令行单独运行的小程序使用结构化输出。比如日志清理工具它的标准接口长这样usage: log_cleaner.py --days7 [--dry-run] [--path...]为什么要强调“可以被单独运行”因为调试时直接执行这个动作比每次都走一遍主入口要快得多。框架只需要负责组装参数和捕获输出即可。为了避免子动作的输出污染主程序的判断逻辑我推荐两个约定普通提示输出走 stdout机器可读的结果统一输出成 JSON 结构。比如清理完成后输出{deleted_files: 120, freed_mb: 34.5}主程序就可以基于这个结果做后续通知或记录。3.4 真正的执行引擎长什么样如果只是简单转发命令这套框架和写一堆 alias 没区别。执行引擎的价值在于四个附加能力统一日志记录每次执行的动作名、参数、耗时、退出码都追加到~/.cli-anything/history.log超时控制设置超时时间超过时间杀掉子进程并标记失败防止个别动作卡住终端输出捕获与预览把子进程的 stdout 和 stderr 捕获起来超过阈值只显示摘要干跑模式预先生成完整的执行计划但不真正调用这四个能力中干跑模式和统一日志尤其重要。统一日志让我可以随时回答“这个环境今天被谁改过、跑过什么命令”干跑模式则让高风险操作在执行前多一道人工确认的环节。注意把执行引擎和动作实现分开可以避免每次新增动作都要动主框架。执行引擎是通用的动作实现是插拔的这是整个 CLI-Anything 能够“Anything”的关键。4. 接入AI出口让自然语言也能驱动命令行工具CLI-Anything 如果止步于手动敲命令就还停留在“脚本收集器”的阶段。真正让它变得现代且好用的是我后来做的 AI 接入层。这也是我一开始设计“命令可发现、参数可描述、执行有边界”的原因所有这些都是为了让大模型能够安全地调用这套工具。4.1 让模型认识你的工具接入 AI 的起点是把注册表里的动作转换成模型能理解的结构化描述。你可以将每个动作的信息生成一个 JSON包含动作名、功能说明、参数含义和使用样例。这个大列表就是“工具说明书”。例如日志清理的动作可以这样描述{ name: logs_clean, description: 清理超过指定天数的日志文件, parameters: { days: {type: integer, default: 7}, dry_run: {type: boolean, default: true} } }把这个 JSON 列表放到模型 API 的工具调用参数中模型就可以在需要时主动询问“要不要清理一下日志”或者在你用自然语言提出需求后自动组合出对应的命令行动作调用。4.2 三种自然语言驱动方案对比我在项目中试过三种方案分别有不同适用场景方案原理适用场景优缺点关键词映射将自然语言分词后匹配动作名和参数本地优先、避免外部 API 依赖实现简单但语义理解有限语义路由用向量数据库或本地 embedding 匹配用户意图与动作描述动作库较大且描述较规范时需要维护 embedding 索引增大磁盘占用LLM 函数调用调用大模型 API传入工具定义让模型返回结构化调用参数需要理解复杂意图时最灵活但每次调用都有成本和时延我的实际建议是“三级联动”先尝试关键词匹配匹配不上时用语义路由兜底实在不行才调用 LLM 函数调用。这样绝大多数高频命令可以不经过大模型省下的时延和成本非常可观。4.3 安全漏斗AI 建议、人来执行接入 AI 最大的风险不是模型答错而是模型生成的命令被直接执行带来的破坏。我给自己定了一条铁律AI 的产出只能是“建议”必须经过确认漏斗才能进入执行引擎。流程是模型根据用户意图生成动作调用参数框架将其可视化地展示成“将要执行cli-anything logs clean --days 7 --dry-run”然后等待用户按y确认。如果要做成无人值守的自动执行也必须限定在白名单动作和沙箱环境中。这个细节值得你从一开始就想清楚否则你的 CLI-Anything 会变成一个隐患而不是生产力工具。5. 实测案例三个我天天在用的 CLI-Anything 插件理论说得再多不如看几个实际跑起来的例子。我目前日常高频使用的插件有三个每个都产出了真实价值而且都是在半天内写完并接入注册表的。5.1 日志与临时文件清理第一个插件是logs clean和tmp prune解决的是服务器磁盘被日志打爆的问题。检测脚本每天早上通过定时任务执行如果发现磁盘占用超过 80%就自动生成一条清理计划并发送到运维群。动作实现逻辑不复杂遍历指定目录找修改时间超过 N 天的.log和.tmp文件按大小排序后输出准备删除的文件列表。配合--dry-run参数可以在执行前人工确认。这个插件一个月大约能清理出 20GB 到 50GB 空间核心价值是“不再需要 SSH 上去敲一串 find 命令”。5.2 批量接口健康检查第二个插件是health check批量检查多个服务的 HTTP 接口是否正常。我给它配置了一个 JSON 文件里面记录每个服务的名称、URL、期望状态码和超时时间。cli-anything health check --envprod --concurrency10执行后插件会并发请求收集结果输出一个简短的 Markdown 表格。如果有关键服务失败执行引擎会追加一条失败标记方便后续接告警。它能发现的问题比普通 ping 多得多比如接口返回 500、响应时间超过阈值、证书即将过期等。这个插件最大的启示是CLI-Anything 不仅适合管理本地脚本也适合把团队成员需要重复执行的“查询类操作”封装成统一命令比如查线上配置、查版本信息、查延迟分布。5.3 从代码注释生成项目文档第三个插件是docs gen它的功能是扫描代码仓库中的关键函数和注释结合文件结构生成一份初步的 Markdown 文档。这对需要维护内部项目的团队帮助很大新人入职时跑一条命令就能拿到项目概览。实现上用的是正则加简单分词老实说离真正的智能还有距离但作为“第一版草稿”已经足够好。配合 AI 接入层后效果更佳模型阅读源码后补全说明文字再交由人工润色。这个插件让“文档长期没人写”变成了“文档每天自动更新底稿”。6. 必须面对的坑转义、权限与依赖的实战笔记最后这部分专门写给已经决定要动手做的人。CLI-Anything 这类工具表面上是很好玩的上层封装实际工程化之后会碰上一堆真实问题。我踩过的坑希望你能提前绕开。6.1 参数转义与通配符陷阱最经典的问题用户在参数里传了一个带空格的路径或者一个包含*的字符串如果你直接用 f-string 拼命令轻则参数失效重则执行了完全不同的命令。我在 6.2 会专门讲安全哪怕只从功能完整性角度来看转义也是一个绕不过的坎。解决方案是“禁止拼接 Shell 字符串”。如果你的动作实现是 Python 或 Node 程序尽量用subprocess.run(args_list)直接传参数列表让系统替你解决转义。如果是调用现成的其他 CLI 工具也要显式指定参数而不是通过shlex.join后再交给 shell。6.2 命令注入框架最致命的安全风险现在必须把话说重一点。只要你的工具被两个人以上使用或者计划接入 AI命令注入就是你首先要堵的死角。攻击方式通常是精心构造参数值比如恶意传一个--path; rm -rf /之类的字符串如果你的执行层把它拼进 shell后果不堪设想。防御手段按顺序有三层参数列表化不走 shell对动作名称做白名单校验杜绝“用户传入任意命令”的设计权限分级高风险动作必须走二次确认我把这三条写进了代码评审纪律里。凡是新提交的动作实现第一关先看参数有没有经过--dry-run第二关看它有没有权限校验缺一不可。提示永远不要提供“直接执行用户输入字符串”的通用命令。如果确实有临时执行脚本的需求也要设计成只能从指定目录读取脚本而不是接收任意路径。6.3 跨平台路径与运行时依赖如果你的工作环境横跨 Linux 和 macOS很多细节会出问题。比如/tmp在 macOS 上的语义和 Linux 不同路径大小写敏感也不一样find命令的参数更是千奇百怪。解决思路是凡是涉及文件系统操作的动作尽量用语言内置的Path类处理路径而不是依赖 shell 命令。依赖管理同样容易失控。每个插件如果都依赖不同的第三方库最终会演变成环境冲突。我的做法是每个动作目录里放一个requirements-action.txt按动作隔离虚拟环境。虽然占一点磁盘空间但能让“加了新插件不破坏老插件”这个承诺始终成立。6.4 测试与回归策略CLI-Anything 的测试我推荐两条路。第一框架层用快照测试把注册表解析后的命令树序列化成文本任何改动造成意外变化都会在测试中暴露。第二动作层用“黄金文件”测试将输出结果与预期 JSON 进行 diff。更重要的是保持“不真实调外部服务”的测试习惯健康检查插件要允许传入本地 Mock 服务地址日志清理插件要允许在一个临时目录里创建假文件。等真实环境出了问题再调试分析成本高得多。我在做测试时还养成了一个习惯就是定期翻看history.log观察哪些动作被高频调用、哪些动作从未被调用。从未被调用的动作通常是重复造轮子的产物该删就删保持框架轻量。最后再分享一点我的实际体会做完 CLI-Anything 之后我最大的感受不是“写了多少行代码”而是工作方式被悄悄改变了。以前想给团队提供新能力需要写文档、教操作、解释依赖环境现在只需要加一条注册记录再补一个动作实现大家用一条命令就能完成。看见cli-anything list里列出的动作越来越多有一种“工具箱越来越衬手”的真实感。如果你也想动手做一套我的建议是从最小用例开始。不要一开始就规划几十个动作先挑一件你每周都会重复三遍以上的事把它接入框架养成“所有操作都从统一入口走”的习惯。等习惯成型后再慢慢扩展才会真正体会到这套模式的价值。