
1. 项目概述当我把一切日常操作都赶进了终端1.1 从一个真实痛点说起先坦白一个让人脸红的事实在我动手写 CLI-Anything 之前我的电脑桌面和工作目录乱得像个菜市场。截图随手丢在桌面下载的文档散落在各个文件夹临时生成的 JSON 文件堆了一列又一列。每周我都要花不少时间手动整理这些文件在图形界面里点开这个文件夹、拖拽那个文件重复劳动到怀疑人生。后来我意识到这些操作本质上都是有明确规则、重复且无趣的任务。既然规则是固定的那为什么不能让程序来执行这就是 CLI-Anything 的起点——我想把日常操作里“不需要人做创意判断”的部分全部收编到命令行里。它不是什么了不起的发明说白了就是一套以终端为统一入口的工具集合用来处理文件整理、文本转换、网络请求、批量图片处理这类高频但枯燥的日常任务。CLI-Anything 的核心价值不是把某个单点功能做到极致而是提供一个统一框架让我可以随时把新的日常操作“塞进”这个工具集里。遇到一个重复性任务我先想想它能不能用命令做能做就加一条命令进去日积月累这套工具就越来越贴近我的真实工作流。适合谁来参考所有对命令行感兴趣、想提升日常操作效率、愿意花一点时间把重复动作固化成脚本和命令的人。1.2 为什么选择命令行而不是继续留在图形界面图形界面当然好拖着鼠标点几下可视化地看到操作反馈没什么学习成本。但它的本质问题是“手动操作通道”——每个操作都需要人盯着屏幕、移动光标、点击确认。一次操作只要几秒但乘以每天几十次、上百次累积起来就是一大块时间。我第一次强烈感受到这种差距是处理一批 200 多张图片的重命名。在图形界面里右键、重命名、输入名字、回车每张图平均要花十几秒200 张跑下来差不多 40 分钟。后来我写了一个简单的命令行循环跑完整个流程只要几秒钟而且我全程不需要盯屏幕命令跑完回来一看结果就行。当然命令行并非适合所有场景。需要自由发挥、视觉判断、探索性质的任务图形界面依然有不可替代的优势。CLI-Anything 的想法从来不是“取代图形界面”而是“把能固化的任务固化下来”。天天都要做的重复动作丢给终端偶尔一次、需要创造性判断的事情留在图形界面。这样用下来既不累效率也高。2. 核心设计拆解如何组织一个可生长的命令行工具集2.1 参数解析是所有 CLI 工具的骨架一个命令行工具不管功能多复杂骨架都是参数解析。参数解析做得差命令用起来处处别扭做得好则每个命令都清晰可控。CLI-Anything 在设计初期就参考了传统命令行工具的惯例同时支持短参数-o、长参数--output、位置参数和子命令。我用 Python 标准库的 argparse 作为第一版的基础。为什么不直接上更重的框架因为初始阶段最重要的是快速迭代argparse 内置、零依赖支持子命令定义错误提示也友好。命令多起来之后我换成了 Click理由是它的装饰器风格可读性更好而且能自动生成帮助信息。但对于一个刚开始做 CLI 工具集的人来说完全没必要一开始就引入复杂框架。举个例子注册一条命令的基本模式是这样的import argparse def cmd_organize(args): # 实际整理逻辑省略 print(整理完成) def main(): parser argparse.ArgumentParser(progcli-anything) sub parser.add_subparsers(titlecommands, destcommand) organize sub.add_parser(organize, help按规则整理文件) organize.add_argument(--input, requiredTrue) organize.add_argument(--target, defaultorganized) organize.set_defaults(handlercmd_organize) args parser.parse_args() if hasattr(args, handler): args.handler(args) else: parser.print_help() if __name__ __main__: main()这段代码的要点在于每条命令都通过set_defaults(handlerxxx)绑定自己的处理函数主程序只负责把参数解析结果分发给对应处理函数。这样每条命令之间没有交叉依赖新增命令时只要在main()里追加一小段定义然后新写一个处理函数即可。2.2 插件机制让“Anything”真正成立如果 CLI-Anything 只是一个固定的命令列表那它很快就会被需求撞穿天花板。日常操作太零碎了今天需要一个图片压缩命令明天需要一个格式转换命令后天又可能需要一个网络抓取命令。为了适应这种不断变化的需求我在设计里加入了一个轻量级插件机制。插件的定义规则很简单每个插件是一个 Python 文件放在commands/目录下文件里必须包含一个register函数。框架启动时扫描整个目录逐个加载并注册。以图片压缩命令为例# commands/image_resize.py def register(registry): def handler(args): # 利用 Pillow 做图片缩放 return 图片处理完成 registry.register(image-resize, handler, 用 PIL 规模化处理图片)主框架负责扫描目录并动态加载import importlib.util from pathlib import Path PLUGIN_DIR Path(__file__).parent / commands def discover_plugins(registry): for module_file in PLUGIN_DIR.glob(*.py): if module_file.name.startswith(_): continue spec importlib.util.spec_from_file_location(module_file.stem, module_file) module importlib.util.module_from_spec(spec) try: spec.loader.exec_module(module) if hasattr(module, register): module.register(registry) except Exception as e: print(f插件加载失败: {module_file.name}: {e})这个机制的精髓在于“不修改主流程就能扩展命令”。需要新增功能时只丢一个文件进目录重启工具就能识别。相比引入 weight 级插件框架如类似容器概念的框架这种目录扫描方式简单粗暴但非常有效。唯一要注意的是插件之间要有一定自律性——避免模块级全局数据互相污染否则排错会非常头疼我在后面踩过一个相关的坑一会儿细说。2.3 输出设计就是 CLI 的“人机交互界面”GUI 有窗口、有按钮、有动画命令行只有一行行的文本。正因如此CLI 工具的输出设计往往比功能本身更影响使用体验。一个命令跑完用户凭什么判断它是成功还是失败完全靠输出信息。在 CLI-Anything 里我养成了几条固定习惯正常处理中不刷屏只输出关键节点信息成功时用绿色文字提示警告用黄色错误用红色。单纯调包一个颜色库花不了多少时间但对使用者来说这个反馈非常直观。另一个重要的点是“可被程序继续消费”。CLI 工具的输出应该尽量结构化让结果可以被管道传递给下一个命令。比如cli-anything report --depth 2 daily_report.txt cli-anything convert input.yaml --from yaml --to json前者把报告写入文件后者把转换结果输出到标准输出。这样的好处是命令之间可以自由组合形成更长的处理流水线。如果每个命令都把自己的结果打印成经过修饰的彩色文本那用户就没办法把结果继续交给下一个命令处理了。所以我在 CLI-Anything 里特意约定默认输出是干净的结构化文本只有检测到标准输出是终端时才加上颜色修饰。这个逻辑可以通过判断sys.stdout.isatty()来实现后面会详细说。3. 实操过程从文件整理到日常流水线的一步步实现3.1 最常用的“按规则整理文件”命令是怎么做出来的每天都要面对的问题工作目录越来越乱。于是我把 CLII-Anything 的第一个核心命令定成了organize——按文件后缀分类移动到对应目录并顺手清理命名中的空格。实际命令长这样cli-anything organize --input ~/Downloads --target ~/Downloads/organized --recursive命令内部处理逻辑大致可以拆成四步遍历输入目录视--recursive决定是否进入子目录根据文件后缀确定目标类别比如图片、文档、压缩包、临时文件生成目标目录并移动文件遇到同名冲突时自动添加序号最后在终端输出每个文件的移动结果和耗时时长。处理 200 多个文件整个过程只要一两秒。也许有人会说手动整理也就十几分钟有必要吗问题不在这一次而在于这个操作每天都在重复发生。一次节省十几分钟一个月就是好几个小时。而且脚本永远稳定、不遗漏人手动操作偶尔还会把文件拖进错误的文件夹脚本不会。我在实际使用中给这个命令加了一个“安全模式”。该模式下命令不会真的移动文件只会打印将要执行的移动动作。这个功能的价值体现在当你面对一堆不确定要不要整理的文件时先跑一次安全模式预览结果确认没问题再正式执行。这算是 CLI 工具里“干大事之前先演练”的通用技巧。3.2 把网页内容变成 Markdown一条抓取命令的诞生第二个让我频繁受益的功能是网页抓取与文本转换。写技术笔记、收集资料的时候我经常需要把一篇文章的正文转成本地 Markdown 文件慢慢读。在浏览器里复制粘贴当然也行但体验很差网页上还有导航栏、侧边栏、广告和各种零碎信息手动清理格式非常浪费时间。CLI-Anything 里的read命令解决的就是这个问题。底层使用requests库抓取网页再用html2text把 HTML 正文转成 Markdown同时支持通过 CSS 选择器限定抓取范围cli-anything read https://example.com/docs/guide --selector article --output guide.md这条命令实际执行时会先请求网页然后根据选择器提取对应的 HTML 片段再做转换最后写入输出文件。操作过程中命令会在终端打印请求状态、内容长度和写入路径方便用户确认抓取是否正常。这类命令非常适合配合作者阅读或资料整理。它是终端和互联网之间的一座小桥让“读取网页内容”这个动作脱离浏览器也能完成并且天然生成结构化文本方便后续处理。3.3 格式转换命令与“stdout 优先”的设计习惯CLI 工具之间之所以能互相配合很大程度依赖“标准输出优先”的设计习惯。我先看一个具体的格式转换命令用法cli-anything convert input.yaml --from yaml --to json config.json这里convert命令把 YAML 格式的配置文件转成 JSON但并没有自己创建文件而是把转换结果输出到标准输出由用户在 shell 层重定向到config.json。这样做的好处是灵活——用户可以把结果直接打印出来预览也可以重定向落到文件还可以通过管道传给其他命令继续加工。这个设计习惯我从 Unix 工具链上学到后来一直沿用下来。写命令的时候默认输出到终端绝不自作主张写文件除非用户显式传入输出路径参数。这样命令的行为就变得可预测组合能力也更强。CLI-Anything 后续加的每条命令都遵循这个原则这也让整个工具集有了一种统一的“气质”。3.4 配置文件和别名机制降低记忆负担命令行工具一旦命令多起来最大的问题不是功能缺失而是记不住。今天用这个命令明天用那个命令参数一多就乱。我给 CLI-Anything 加了一个简单的配置文件和别名机制定义一个config.yamlaliases: lsdir: file list --depth 1 fetch: read --selector body rpt: report调用cli-anything rpt时框架先在配置里搜索别名找到后把原始命令替换成完整命令再执行。这样做的好处是完整命令可以作为“全名”随时使用提供了可读性和完整性别名则作为“小名”满足高频操作时的便捷需求。从实际使用来看真正高频使用的命令数量其实有限给它们配置别名以后每天节省的记忆和输入成本非常可观。这次经验也让我明白CLI 工具集的设计不只要考虑“能不能做”还要考虑“日常用起来顺不顺”。别名机制是提升顺手度的关键配置。3.5 尽早做参数预检永远不要在未知状态下修改文件在 CLI 工具开发过程中我吃过一次很大的亏。早期版本的organize命令没有对输入目录是否存在做检查某次运行使用了错误参数命令吭哧吭哧跑起来把一批文件移动到了一个我没有预料到的路径里。等到发现问题时恢复原状花了不少时间。这次教训让我在框架层加入了“参数预检”的机制。CLI-Anything 的每条命令在执行前都会先调用一个可选的preflight函数做基础的环境检查输入目录是否存在、是否可读输出目录是否可写或者能否自动创建网络命令是否可连通目标服务依赖库是否已经导入和可用。如果 preflight 返回异常状态命令会在执行任何有副作用的操作之前停下def preflight(args): input_path Path(args.input) if not input_path.exists(): return False, 输入目录不存在 if not input_path.is_dir(): return False, 输入路径不是目录 target Path(args.target) try: target.mkdir(parentsTrue, exist_okTrue) except Exception as e: return False, f无法创建目标目录: {e} return True, 预检通过预检代码本身非常朴素但它的意义非常重大让命令在“可预见的错误”发生时快速失败而不是在改到一半时才暴露问题。修改类命令尤其需要这样的保护。这条经验后来也贯彻到 CLI-Anything 几乎所有命令中成了我写 CLI 工具的一个重要原则。4. 进阶实战从别名配置到批量图片处理的完整场景4.1 在 shell 层给 CLI-Anything 建一个“总控开关”CLI-Anything 本身是个命令行工具但真正让它融入日常操作的是系统 shell 层设置的别名。我在~/.bashrc和~/.zshrc里都写进了类似下面的配置alias clycli-anything alias clyrcli-anything report alias clyncli-anything note这些 shell 别名的意义与 CLI-Anything 内部的别名机制不同shell 别名在更高一层相当于给整个工具集建立了一个简短的总入口。我在使用过程中发现如果一条命令的输入成本超过 15 个字符人就会潜意识地减少使用频率。缩短到两三个字符后使用意愿会明显提升。值得注意的是shell 别名默认只对交互式 shell 生效。如果你在脚本或 cron 任务中直接使用别名大概率会失效。这是因为非交互式 shell 不会加载用户的交互配置。实际编写自动化任务时要么在脚本开头重新加载配置文件要么直接使用cli-anything全名或者干脆写全路径。4.2 一晚上处理 1200 张图片批量操作实战记录有一次我需要把 1200 多张产品图片压缩到适合网页展示的尺寸原图每张在 3MB 到 8MB 之间。如果用图形界面工具逐张处理工作量不可想象手动写 Python 脚本倒是可行但做完之后图片格式差异、错误处理、日志输出统统都要自己考虑。最终我用 CLI-Anything 的image batch-resize命令完成了这个任务cli-anything image batch-resize --input ./raw --output ./web --width 1280 --quality 80 --format jpg命令内部的处理逻辑是遍历输入目录下所有图片用 Pillow 读取统一转换到目标宽度并按指定质量参数压缩最后输出为 JPG。处理过程中遇到无法识别的图片格式时框架会把对应的错误记录到日志文件而不是中断整个任务。这样即使几百张图片里混入了几个损坏文件任务也能照常跑完。最终结果1200 多张图片大约花了 40 多秒压缩比约为 70% 到 80%。输出目录里的所有文件都变成了适合网页展示的合理体积。这次经历让我体会到批量任务设计的两个铁律一是单文件失败不能中断整体流程二是体积与质量的平衡参数必须能让用户从命令行控制而不是每次修改源码。类似的应用还包括日志归档、旧文件清理、备份校验等等。这些任务本质上都很像规则明确、数据量大、操作重复。只要想清楚“成功标准”和“失败容忍度”把它们变成 CLI 命令是水到渠成的事。4.3 把多个命令串成自动化流水线CLI 工具如果只用于单次手动执行那它还没有完全发挥潜力。真正让它成为“Anything”级别工具的是它作为自动化流程节点的能力。我用一个简单的 shell 脚本就可以把多个命令串起来#!/usr/bin/env bash cli-anything organize --input ~/Downloads --recursive cli-anything report --depth 2 daily_report.txt cli-anything note --push daily_report.txt这段脚本有明确的执行顺序先整理下载目录里的文件再生成当天工作目录的报告最后把报告推送到笔记记录。在实际使用中我通过系统定时任务调度它每天早上自动执行一次完全不需要人工干预。这个场景在每次定期任务触发时会跑一遍给了我巨大的时间回馈。不过也要提醒一句自动化脚本一旦跑起来人就不在回路里了。所以脚本里的每条命令都要尽量做足预检和日志输出否则出问题时排查会很难受。我在实践中学到的办法是让每个命令在关键节点输出日志并且把日志统一写入固定目录。这样即使定时任务半夜跑完第二天也可以快速查看执行记录。4.4 把 CLI-Anything 变成团队协作的公共标准个人工具之后我又把它扩展到了小团队的使用场景。团队里最典型的问题是每个人做同一份“打包上传”操作时步骤都不太一样遇到问题很难沟通。用统一命令以后所有人都跑同一条命令输出也是一致的报告格式沟通成本明显下降。要知道团队统一命令行工具是最佳实践但也承担额外的维护责任。你不能再像维护个人工具那样三天改一个参数要考虑兼容性、错误提示、升级节奏。如果团队里已经有人用旧版命令跑出了结果你不能简单粗暴地改变输出格式而是要确保升级路径平滑。正因为这样把工具“团队化”之前要想清楚维护承诺不能只图一时方便。5. 常见问题与排查技巧实录5.1 “command not found”排查路径命令行工具大概率会遇到的第一个问题就是命令找不到。出问题时按照下面的顺序排查通常都能解决type cli-anythingtype命令可以检查当前 shell 是否能定位到目标程序。如果提示 not found继续确认以下几项安装是否进入全局 PATH。用 Python 的包管理器方式安装时可以检查安装目录是否在 PATH 中当前 shell 是否加载了正确的初始化文件。可以运行echo $PATH查看如果安装目录不在列表里需要在配置文件中追加路径非交互式环境如 cron中找不到命令多半是 PATH 没继承。此时脚本里应使用命令绝对路径或显式加载用户环境如果是远程服务器或容器环境还要检查目标环境是否安装了运行时依赖。有时命令可执行但一运行就提示导入某个库失败这也是类似问题。排查这类问题最有效的方式就是先看“命令在哪个环境里能跑、哪个环境里不能跑”对比两者的差异通常很快能找到原因。5.2 中文乱码与终端格式化错乱CLI 工具一旦涉及中文等非 ASCII 文本就很容易遇到乱码。我遇到过的几种情况各有对应解法Python 在非 UTF-8 的终端环境运行时可能出现输出乱码解决方法是重设编码比如export PYTHONIOENCODINGutf-8并在代码里读文件时显式使用 UTF-8Windows 自带终端对 ANSI 颜色控制码的支持比其它环境弱如果不支持会把颜色码显示成乱码字符。我给 CLI-Anything 增加了一个开关检测到不是终端输出或终端不支持 ANSI 时自动关闭颜色等宽文本对齐方面中文全角字符和英文半角字符的占位宽度不同直接按字符串长度对齐会错位。解决方式是计算“显示宽度”而不是字节长度管道重定向时颜色码混入文件也是常见烦心事。标准做法是判断sys.stdout.isatty()非终端输出时自动禁用颜色。把这些问题都处理好以后跨平台的输出稳定性会提升很多。最核心的原则是不要只在好看的终端里开发完就觉得万事大吉要主动在默认终端、重定向、Windows 等多种环境里各跑一遍。5.3 子命令冲突与插件加载异常当插件越来越多就有可能遇到两个插件注册同一个命令名的情况。CLI-Anything 的默认策略是后加载的插件覆盖先加载的插件并打印一条警告。这个设计是有意为之的因为用户对插件目录的排序是可预期的通过文件命名可以控制加载顺序。我自己遇到过一次冲突一个内置命令和一个第三方插件命令重名后来我通过调整插件目录前缀的方式解决了冲突让期望的版本生效。至于插件加载时抛异常我在示例代码中已经用 try/except 包住了每个模块的加载过程。这样做的好处是某个插件模块代码写得有问题时只会打印错误日志别的命令依然能正常工作。这在一开始设计时不算重点但用过几次之后就能体会到价值——插件机制的鲁棒性决定了工具集的整体稳定性。5.4 在 Mac、Linux 和 Windows 之间游走跨平台开发总是充满“惊喜”。CLI 工具要跨平台运行至少要关注这么几个点路径分隔符Windows 用反斜杠Linux/macOS 用正斜杠永远不要手工去拼路径字符串要用pathlib.Path这样的跨平台组件文件系统语义Windows 对文件占用、锁定、改名等操作与 Unix 系统差异较大移动删除文件前要确认目标文件处于可操作状态换行符Windows 默认\r\nUnix 默认\n。文本处理时如果没注意这个问题重定向出来的文件里会出现^M或者多余空行子进程调用在 Windows 上调用系统命令时参数传递方式与 Unix 类似但是细节很不一样尤其是涉及带空格的路径时要使用参数数组而不是直接拼接命令行字符串。这里分享一个我自己的习惯工具代码层面尽量做到跨平台但实际自动化部署还是优先在同一个家族系统内跑不要把一次性脚本强行做到“三平台通吃”。跨平台兼容是有成本的如果只是个人工具在一个目标平台上跑通再用另一平台做兼容验证就足够了。6. 扩展思路与实用心得把 CLI 工具做成个人数字助理6.1 多工具联动不是替代而是统一入口首先要澄清一点CLI-Anything 不打算替代一切优秀工具。比如项目里已有的监听文件变化的fswatch、跑测试的pytest、构建工程的make它们各自都非常专业但使用方式各不相同。CLI-Anything 给它们做的是“统一入口 可记忆的命令封装”让这些底层工具通过一套一致的参数风格暴露给用户。实际使用中我经常这么组合cli-anything watch --path ./src -- script make test这条命令会把./src目录里的文件变化作为触发器一旦有修改就运行make test。本质上它还是调用系统自带的fswatch但它提供了统一的命令规则把记忆负担限制在最低。类似的组合还可以把“编辑器保存后自动格式化文档”等操作拉进统一的工作流中。另一个好处是“命令发现能力”。当环境里装了大量小工具时时间久了容易记混命令参数也容易忘。当所有命令都汇入 CLI-Anything 的--help体系后随时可以查看完整命令列表相当于在终端里内置了一个命令索引。这种方式对工具积累特别友好。6.2 简化入门让非资深用户也能用起来真正的 CLI 工具如果只服务开发者自己那还谈不上“Anything”。要让更多人能用起来关键是降低发现成本和使用门槛。我给 CLI-Anything 做的第一个简化措施是完善的帮助系统每个命令都配有用途说明、参数列表、最少一个使用示例。执行cli-anything help command就能查看详细帮助。另一个非常有用的设计是“交互式补全”。如果命令缺少必选参数且当前终端支持交互输入CLI-Anything 会主动用提示的方式请用户补全。例如运行cli-anything organize但没指定输入目录时它会询问需要整理哪个目录。这种设计对新手非常友好相当于“有引导的命令行”。加上这一层之后工具不仅限于开发者使用也可以给那些对终端有些畏惧、但又愿意尝试的同事使用。命令行不需要变得每一条都很暴力它可以叠加上温和的交互层让不懂参数语法的人也能逐步上手。6.3 沉淀团队最佳实践一句话讲清楚“该跑哪条命令”团队协作时最怕的就是“同一种操作十个人有十种做法”。如果有人习惯手动编译有人习惯 IDE 里的操作按钮遇到问题根本说不清是谁的步骤出了问题。CLI 工具能让团队协作变得清晰前提是大家都愿意接受统一命令。我参与的一个小工程团队就把“环境检查”、“构建前检查”、“运行测试”这三件事封装成了统一的 CLI 入口。任何人先说“跑一下 cli-anything build-check”大家立刻知道指的是什么操作。通过参数和输出格式的统一问题定位也变得更直接。需要提醒的是团队化意味着维护责任的提升。命令发布后不能频繁地改变参数和输出格式必须考虑已有使用者的兼容性。上线之前最好经过一段试用期让早期使用者反馈意见。工具是在服务团队不是反过来被团队供养——这个心态要有。7. 我在 CLI-Anything 开发中最看重的一些心得体会做了这个项目之后我对“如何让一个命令行工具真正被长期使用”有了不少新的认识。与其说 CLI-Anything 是一个技术成果不如说它是我个人工作流里的一道长期作业。这里挑几条我认为最有代表性的经验。第一条输出必须诚实。终端里每一行输出都是用户判断执行状态的依据。命令成功就明确输出成功信息失败就尽量打印原因不要留下含糊不清的中间状态。我总是记住一个原则与其输出一堆花哨但无用的字符串不如输出几行准确、有意义的反馈。第二条参数要少、默认值要多。一个命令如果必填参数超过五个用户很容易出错。我的策略是给高频场景设置合理的默认值。比如文件整理默认整理当前目录网络读取默认超时时间 10 秒。默认值的意义不是降低工具灵活性而是让 80% 的使用场景直接免去思考成本。第三条真正的修改动作之前必须有预检。CLI 工具的“重操作”最容易出问题的不是开始而是执行到一半。先做全量检查确认环境没问题之后再开始修改文件或发起网络请求可以避免大多数后果严重的错误。这条经验说起来很简单但真正吃亏经历过一次之后才会刻进代码习惯里。第四条CLI 工具要慢慢长出“自动化”的面貌。如果你只是把单条命令封装成一个脚本那它还不够通用。当它支持别名、可配置、可组合、可被其他脚本调用它才能真正向“Anything”靠近。我在设计每条新命令时都会下意识问一句它能不能被脚本引用它的输出是不是结构化的如果答案都是否那就需要调整设计。CLI-Anything 这个项目做到现在我最大的感受就是它越来越像一个“个人数字助理”只不过交互界面不是语音也不是窗口而是终端和脚本。它解决的问题不是某一个具体场景里的某一个操作而是“怎么把日常重复操作从手动劳动里解放出来”这样一个更宏观的问题。每个人都有自己独特的重复劳动清单与其等一个通用工具来拯救不如花点时间做一个属于自己的命令行工具集。工具不一定叫 CLI-Anything但思路是相通的。希望这篇笔记能帮到你也欢迎分享你自己的命令行工作流经验。