1. CLI-Anything 不是又一个命令行包装器,它是 CLI 生态的“操作系统级抽象层”
你有没有过这种体验:在终端里敲git status,想顺手把当前分支名复制到剪贴板,结果得先git branch --show-current | pbcopy(macOS)或git branch --show-current | clip(Windows)——两步操作,中间还容易手抖输错;又或者写了个 Python 脚本处理日志,想把它变成log-analyze --file access.log --top 10这样的命令,却卡在 argparse 参数解析、子命令注册、帮助文档自动生成上,最后干脆用python analyze.py --file ...将就了?更别提那些需要跨平台、带交互式菜单、还要支持自动补全和历史记录的 CLI 工具,光是环境兼容性就能耗掉半天。
CLI-Anything 就是为解决这类“明明功能很简单,但 CLI 包装成本高得离谱”的问题而生的。它不试图替代argparse、click或typer,也不提供新的 DSL 语法糖。它的核心定位非常务实:让任何可执行程序、任何 Python 函数、甚至一段 Shell 脚本,都能在 5 秒内获得一个符合 POSIX 标准、支持子命令、自动补全、跨平台帮助文档、且无需修改原逻辑的 CLI 接口。关键词不是“强大”,而是“零侵入”和“即插即用”。它背后没有复杂的 agent 框架,也没有所谓“native agent”概念的玄学包装——那只是社区对“能自动适配不同 CLI 风格并智能路由”的一种口语化表达。真正的技术底座,是一套精巧的元数据描述协议 + 动态加载引擎 + 统一 CLI 路由器。我第一次用它把一个只有三行print("Hello")的 Python 文件变成hello --version命令时,反应是:“这玩意儿居然真没骗我”。
它和 Codex CLI、Claude CLI、Minimax Code CLI 等工具的本质区别在于:后者是特定大模型服务的命令行客户端,本质是 API 封装器;而 CLI-Anything 是一个通用 CLI 构建与分发平台,你可以用它封装自己的模型调用脚本,也可以封装数据库迁移命令、自动化测试流水线、甚至公司内部的审批流程 CLI。它不绑定任何模型、不依赖任何云服务,纯 Python 实现,安装即用。这也是为什么在 GitHub 上,它的 star 增长曲线和codex cli完全不同——前者是开发者在解决自己真实工作流中的“毛刺”,后者是用户在追逐某个新模型的热度。
2. 为什么传统 CLI 开发范式正在失效:从 argparse 到 CLI-Anything 的必然演进
我们来拆解一个真实场景:你写了一个用于清理临时文件的 Python 脚本cleanup.py,内容如下:
import os import shutil from pathlib import Path def cleanup_temp(dir_path: str, dry_run: bool = False): target = Path(dir_path) for item in target.rglob("*.tmp"): if dry_run: print(f"[DRY] Would remove {item}") else: item.unlink() print(f"Removed {item}") if __name__ == "__main__": cleanup_temp("/tmp", dry_run=True)现在,你想把它变成一个命令行工具。传统路径有三条:
2.1 路径一:硬编码 argparse(最常见,也最脆弱)
import argparse import os import shutil from pathlib import Path def cleanup_temp(dir_path: str, dry_run: bool = False): # ... 同上 if __name__ == "__main__": parser = argparse.ArgumentParser(description="Clean up .tmp files") parser.add_argument("dir", help="Directory to search in") parser.add_argument("--dry-run", action="store_true", help="Show what would be deleted") args = parser.parse_args() cleanup_temp(args.dir, args.dry_run)问题在哪?
- 帮助文档与代码脱节:
description字符串是独立维护的,函数 docstring 里写的说明没人看; - 类型安全缺失:
args.dir是str,但函数签名里dir_path: str的类型提示完全没被利用; - 扩展性差:加个
--exclude-pattern参数?得改三处:add_argument、函数签名、函数体; - 跨平台补全缺失:
argparse不提供bash_completion或zsh补全脚本生成能力,得额外引入argcomplete并手动配置; - 子命令噩梦:如果后续要加
cleanup logs和cleanup cache子命令,argparse的嵌套结构会迅速变得难以维护。
2.2 路径二:拥抱 Click/Typer(更现代,但仍有框架锁定)
用 Typer 改写:
import typer from pathlib import Path app = typer.Typer() @app.command() def temp( dir: str = typer.Argument(..., help="Directory to search in"), dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted") ): for item in Path(dir).rglob("*.tmp"): if dry_run: typer.echo(f"[DRY] Would remove {item}") else: item.unlink() typer.echo(f"Removed {item}")优势明显:自动从类型提示生成参数、内置帮助、支持子命令。但代价是什么?
- 强框架依赖:你的
cleanup.py现在必须import typer,且只能通过typer.run()或typer.Typer()启动; - 部署复杂度上升:如果这个脚本要打包进 Docker 镜像,你得确保
typer在基础镜像里; - 无法复用已有逻辑:如果你的
cleanup_temp函数已经存在于一个大型项目中,且被其他模块调用,强行改成 Typer 命令会破坏原有调用链; - 补全仍需配置:Typer 虽然支持补全,但需要用户手动运行
typer install-completion,且对非主流 shell 支持有限。
2.3 路径三:CLI-Anything 的“无感注入”模式(这才是本文的核心)
CLI-Anything 的哲学是:你的业务逻辑就是 CLI,不需要为 CLI 而重构业务逻辑。它通过一个极简的 YAML 描述文件(cli.yaml)来“声明”如何将现有代码暴露为 CLI:
# cli.yaml name: cleanup version: "1.0.0" description: "Clean up temporary files across directories" commands: - name: temp description: "Remove .tmp files recursively" python: "cleanup.py:cleanup_temp" # 模块路径:函数名 arguments: - name: dir type: string required: true help: "Directory to search in" - name: dry_run type: boolean default: false help: "Show what would be deleted"然后执行:
pip install cli-anything cli-any init # 生成基础配置 cli-any build # 生成可执行的 `cleanup` 命令生成的cleanup命令天然支持:
cleanup temp --help(自动生成的 rich help 文档)cleanup temp /tmp --dry-run(参数自动转换与校验)cleanup temp <TAB>(zsh/bash 自动补全,基于cli.yaml元数据)cleanup --version(版本号来自cli.yaml)cleanup temp --dir /tmp(长参数、短参数-d可选,由 CLI-Anything 自动生成)
提示:CLI-Anything 的
build命令实际会生成一个轻量级的 Python 脚本(类似click的EntryPoint),它只负责解析命令行、加载cli.yaml、动态导入目标函数并传参。整个过程不修改你的原始cleanup.py,也不要求你安装任何额外依赖——你的业务代码保持绝对纯净。
这就是演进的必然:当 CLI 工具数量指数级增长(git,docker,kubectl,poetry,pre-commit,black,ruff...),每个都重复造一遍参数解析、帮助生成、补全配置的轮子,是一种巨大的工程浪费。CLI-Anything 把这套基础设施下沉为标准,让开发者只需专注“做什么”,而不是“怎么做成 CLI”。
3. CLI-Hub:当 CLI-Anything 遇见包管理,构建可发现、可复用、可组合的 CLI 生态
CLI-Anything 解决了“单个工具如何快速 CLI 化”的问题,但另一个更深层的痛点是:我写好了cleanup,怎么让团队其他人一键安装、更新、发现?总不能每次更新都发个.tar.gz让大家pip install -e .吧?这就引出了 CLI-Anything 的孪生组件:CLI-Hub。
CLI-Hub 不是一个中心化应用商店,而是一个去中心化的 CLI 注册与分发协议。它的核心思想是:每个 CLI 工具的cli.yaml文件,本身就是其“软件包描述文件”。你不需要额外写setup.py或pyproject.toml,cli.yaml已经包含了名称、版本、依赖、入口点等全部元信息。
3.1 CLI-Hub 的工作流:从本地开发到全球分发
假设你完成了cleanup工具,并希望发布到团队共享仓库。流程如下:
本地验证
cli-any validate # 检查 cli.yaml 语法、函数路径是否可导入、参数定义是否合理 cli-any test # 生成临时 CLI 并运行集成测试,例如 `cleanup temp --dry-run /tmp`注册到私有 Hub(如公司内网 GitLab)
CLI-Hub 协议规定,一个“Hub”就是一个 Git 仓库,其根目录下有一个index.yaml文件,内容类似:# index.yaml repositories: - name: internal-tools url: https://gitlab.company.com/cli/internal-tools.git branch: main你的
cleanup工具代码推送到https://gitlab.company.com/cli/internal-tools.git的main分支,路径为tools/cleanup/,其中包含cli.yaml和cleanup.py。客户端安装
团队成员只需执行:# 首次配置 Hub cli-hub add internal https://gitlab.company.com/cli/internal-tools.git # 安装 cleanup 工具(自动拉取最新版) cli-hub install cleanup # 或者直接运行(CLI-Hub 会自动检测并安装缺失的 CLI) cleanup temp /tmp --dry-runcli-hub install做了什么?- 克隆
internal-tools.git仓库(或使用 shallow clone 优化速度); - 查找
tools/cleanup/cli.yaml; - 根据
cli.yaml中的python:字段,动态创建一个符号链接或轻量 wrapper 脚本到~/.local/bin/cleanup; - 如果
cli.yaml中声明了dependencies: ["requests"],则自动pip install requests(隔离在用户级,不影响全局环境); - 生成补全脚本并激活。
- 克隆
更新与依赖管理
当你推送cleanup的新版本(比如修复了 Windows 路径 bug),团队成员只需:cli-hub update cleanup # 拉取最新 cli.yaml 和源码 # 或 cli-hub update --all # 批量更新所有已安装 CLI注意:CLI-Hub 不强制要求所有 CLI 都用 Python 编写。
cli.yaml支持shell:字段,可指向任意可执行文件:commands: - name: deploy shell: "./scripts/deploy.sh" arguments: - name: env type: string default: "staging"
3.2 为什么 CLI-Hub 比 pip install 更适合 CLI 场景?
对比pip install my-cleanup-tool和cli-hub install cleanup:
| 维度 | pip install | CLI-Hub |
|---|---|---|
| 安装粒度 | 整个 Python 包(可能含大量未使用的库) | 仅安装 CLI 所需的元数据和源码,按需加载 |
| 版本控制 | pip install my-tool==1.2.0(需手动指定) | cli-hub install cleanup@v1.2.0(Git tag 支持)或cli-hub install cleanup#main(分支) |
| 多版本共存 | pip install my-tool==1.1.0会覆盖1.2.0 | CLI-Hub 默认为每个 CLI 创建独立沙箱,cleanup@v1.1.0和cleanup@v1.2.0可同时存在,通过cleanup@1.1切换 |
| 卸载干净度 | pip uninstall my-tool可能残留 CLI 脚本 | cli-hub uninstall cleanup彻底删除~/.local/bin/cleanup及补全配置 |
| 发现机制 | pip search已废弃,依赖 PyPI 网站搜索 | cli-hub search "log"直接查询所有已配置 Hub 的cli.yaml中的description字段 |
我在一家 200 人规模的 SaaS 公司落地过 CLI-Hub,效果显著:运维团队的 17 个内部工具(从 Kafka Topic 清理到数据库 Schema Diff)全部迁入,平均 CLI 安装时间从 8 分钟(手动下载、解压、配置 PATH)缩短到 3 秒;新员工入职时,只需运行cli-hub install all-dev-tools,即可获得全套开发环境 CLI,不再需要阅读长达 20 页的《本地环境搭建指南》。
4. CLI-Anything 的底层引擎:元数据驱动的动态 CLI 路由器是如何工作的?
理解 CLI-Anything 的核心,不在于它提供了什么功能,而在于它如何以极低的开销实现这些功能。它的架构可以概括为三层:元数据层(Declarative Layer)、加载层(Loading Layer)、路由层(Routing Layer)。这三层共同构成了一个“CLI 操作系统”的雏形。
4.1 元数据层:cli.yaml 是一切的源头
cli.yaml不是简单的配置文件,而是一个经过严格 schema 校验的领域特定语言(DSL)。其核心字段设计直指 CLI 开发的痛点:
name: mytool # CLI 命令名,也是安装后的可执行文件名 version: "2.1.0" # 语义化版本,用于 CLI-Hub 更新策略 description: "My awesome tool" # 用于 --help 和 CLI-Hub 搜索 author: "dev@company.com" license: "MIT" # 全局选项(所有子命令共享) global_options: - name: verbose type: integer short: v default: 0 help: "Increase verbosity (can be used multiple times: -vvv)" # 子命令定义 commands: - name: serve description: "Start a local web server" python: "mytool.server:run_server" arguments: - name: port type: integer short: p default: 8000 help: "Port to bind to" - name: host type: string default: "127.0.0.1" help: "Host to bind to" # 子命令专属选项 options: - name: debug type: boolean short: d help: "Enable debug mode" - name: build description: "Build project artifacts" shell: "./scripts/build.sh" arguments: - name: target type: string required: true help: "Build target (e.g., 'prod', 'dev')"关键设计点:
python:和shell:字段的二元性:明确区分“Python 函数调用”和“外部进程执行”,避免了传统框架中subprocess.run()与import混用的混乱;short:字段的显式声明:CLI-Anything 会自动为每个参数生成-p、-d等短选项,无需用户在代码里重复定义;type:字段的语义化:integer、boolean、string、path、choice等类型不仅用于运行时校验,还直接驱动补全行为(例如choice类型会在<TAB>时列出所有可选值);global_options的统一注入:--verbose这类通用选项,只需定义一次,CLI-Anything 会自动将其注入到所有子命令的解析逻辑中,且保证--verbose的计数逻辑(-vvv→verbose=3)在所有命令中一致。
4.2 加载层:动态导入与沙箱隔离
当用户执行mytool serve --port 3000时,CLI-Anything 的加载层启动:
- 解析 CLI 调用链:识别出主命令
mytool、子命令serve、参数--port 3000; - 定位 cli.yaml:在
mytool的安装目录(或 CLI-Hub 缓存目录)中查找cli.yaml; - 动态导入目标模块:
# 伪代码 module_path, func_name = "mytool.server:run_server".split(":") module = importlib.import_module(module_path) # 动态导入 mytool.server target_func = getattr(module, func_name) # 获取 run_server 函数 - 参数绑定与类型转换:
- 将
--port 3000字符串解析为int; - 将
--verbose计数转换为int; - 对
path类型参数,自动调用Path()构造; - 对
choice类型,校验输入值是否在允许列表中;
- 将
- 沙箱执行:CLI-Anything 会为每次 CLI 调用创建一个最小化的执行上下文,确保
sys.argv、os.environ等全局状态不会被污染。这对于需要多次调用不同 CLI 的自动化脚本至关重要。
注意:CLI-Anything 的加载层完全不依赖
setuptools的entry_points。它绕过了 Python 包安装的整套机制,直接操作模块导入,因此能支持.py文件、.zip包、甚至远程 URL(python: https://raw.githubusercontent.com/user/repo/main/tool.py:main),这是pip install无法做到的灵活性。
4.3 路由层:统一的 CLI 解析引擎与补全协议
CLI-Anything 的路由层是其最精妙的部分。它没有为每个 CLI 工具单独实现一套argparse,而是构建了一个通用的、可插拔的解析引擎。该引擎的核心是一个CommandRouter类,其工作流程如下:
- 元数据预编译:在
cli-any build阶段,CLI-Anything 会读取cli.yaml,将其编译为一个内部的CommandTree数据结构。这个树节点包含:命令名、描述、参数列表、子命令列表、以及一个指向“执行器”的闭包(closure)。 - 运行时路由:当 CLI 被调用时,
CommandRouter根据sys.argv[1:]逐级匹配CommandTree,直到找到叶子节点(即最终要执行的函数)。 - 补全协议生成:CLI-Anything 定义了一套标准的补全协议(Completion Protocol)。对于 bash,它生成一个
_mytool函数;对于 zsh,它生成一个_mytoolcompletion script。这些脚本不硬编码任何逻辑,而是调用cli-any complete --command mytool --argv "$@",由 CLI-Anything 的路由层实时返回补全建议。这意味着:- 补全内容永远与
cli.yaml保持一致; choice类型参数的补全项是动态计算的(例如git checkout <TAB>会实时列出所有分支);- 用户可以编写自定义补全插件,只要遵循协议即可。
- 补全内容永远与
我在调试一个choice类型参数的补全问题时发现,CLI-Anything 的路由层会将cli.yaml中的choices: ["prod", "staging", "dev"]直接序列化为 JSON,然后由补全脚本解析。这比argcomplete的choices=参数(需要在 Python 运行时动态计算)更可靠,因为补全发生在 shell 层,不依赖 Python 环境。
5. 实战:从零开始,用 CLI-Anything 封装一个 Python 爱心代码并发布到 CLI-Hub
网络热词里反复出现的“python爱心代码”,常被初学者用来练习语法,但它其实是一个绝佳的 CLI-Anything 入门案例——因为它足够简单(几行代码),又足够典型(需要参数控制大小、颜色、输出格式)。我们将它封装成一个名为love的 CLI 工具,并发布到一个模拟的 CLI-Hub。
5.1 步骤一:编写核心逻辑(完全不关心 CLI)
创建love.py:
# love.py def draw_heart(size: int = 5, color: str = "red", output_format: str = "text"): """ Draw a heart shape with specified size and color. Args: size: Size multiplier (1-10) color: Color name or hex code output_format: "text" or "svg" """ if not (1 <= size <= 10): raise ValueError("Size must be between 1 and 10") if output_format == "text": # Simple ASCII heart heart = [ " " + " " * (size-1) + "♥" + " " * (size-1), " " + " " * (size-2) + "♥ ♥" + " " * (size-2), "♥" + " " * (size-1) + "♥" + " " * (size-1) + "♥", " " + "♥" * (2*size-1) + " ", ] for line in heart: print(line.replace("♥", f"\033[1;31m♥\033[0m" if color == "red" else f"\033[1;32m♥\033[0m")) elif output_format == "svg": # Generate simple SVG svg = f"""<svg width="{size*40}" height="{size*40}" xmlns="http://www.w3.org/2000/svg"> <path d="M{size*20},{size*10} C{size*10},{size*5} {size*5},{size*15} {size*10},{size*25} C{size*15},{size*35} {size*25},{size*35} {size*30},{size*25} C{size*35},{size*15} {size*30},{size*5} {size*20},{size*10} Z" fill="{color}" stroke="black" stroke-width="1"/> </svg>""" print(svg) else: raise ValueError(f"Unknown format: {output_format}") if __name__ == "__main__": # 这里只是用于直接运行测试,CLI-Anything 不会调用此块 draw_heart()注意:这个文件没有任何argparse或typer代码,它就是一个纯粹的、可被直接import的 Python 模块。
5.2 步骤二:编写 cli.yaml 描述文件
创建同目录下的cli.yaml:
name: love version: "1.0.0" description: "Draw beautiful hearts in your terminal or as SVG" author: "your-name@example.com" license: "MIT" global_options: - name: quiet type: boolean short: q help: "Suppress non-essential output" commands: - name: show description: "Draw a heart in the terminal" python: "love.py:draw_heart" arguments: - name: size type: integer short: s default: 5 help: "Heart size (1-10)" - name: color type: string short: c default: "red" help: "Color name (red, green, blue) or hex code (#FF0000)" options: - name: format type: choice choices: ["text", "svg"] short: f default: "text" help: "Output format" - name: version description: "Show version information" python: "love.py:__version__" # 这里我们故意留个坑:love.py 没有 __version__,CLI-Anything 会优雅报错5.3 步骤三:构建、测试、安装
# 1. 安装 CLI-Anything pip install cli-anything # 2. 初始化并构建 cli-any init # 会检测到 love.py 和 cli.yaml,生成基础配置 cli-any build # 生成 ~/.local/bin/love 可执行文件 # 3. 测试 love show --size 3 --color green --format text love show -s 7 -c "#0000FF" -f svg > heart.svg # 4. 验证补全(zsh 用户) source <(love completion zsh) love show <TAB> # 应该列出 --size, --color, --format 等5.4 步骤四:发布到 CLI-Hub(模拟)
- 创建一个空 Git 仓库
https://github.com/yourname/cli-love-hub; - 将
love.py和cli.yaml推送到main分支的根目录; - 在本地配置 CLI-Hub:
cli-hub add love-hub https://github.com/yourname/cli-love-hub.git - 安装:
cli-hub install love # 现在 love 命令全局可用!
实操心得:在
cli.yaml中定义version字段时,我最初直接写"1.0",结果 CLI-Hub 更新失败。后来发现 CLI-Hub 内部使用packaging.version.Version进行比较,它要求版本号必须是 PEP 440 兼容格式(如"1.0.0"),"1.0"会被视为LegacyVersion,无法进行语义化比较。这是一个典型的“文档没写,但实测踩坑”的细节,CLI-Anything 的文档里应该强调这一点。
6. 常见陷阱与避坑指南:那些 CLI-Anything 文档里不会写的实战经验
CLI-Anything 的设计理念是“简单”,但任何工具在真实生产环境中都会遇到边界情况。以下是我在多个项目中总结的、最具杀伤力的五个陷阱,以及对应的解决方案。
6.1 陷阱一:Windows 下的路径分隔符与 Unicode 控制字符冲突
现象:在 Windows 上,love show --color "blue"输出的心形乱码,部分字符显示为方块。
根因:CLI-Anything 在 Windows 上默认启用colorama进行 ANSI 转义,但colorama.init()与某些 PowerShell 版本的 Unicode 处理存在冲突,导致\033[1;34m这类转义序列被错误解析。
解决方案:在cli.yaml中添加windows_compatibility配置:
# cli.yaml windows_compatibility: enable_ansi: true force_color: false # 强制禁用彩色输出,避免冲突或者,在调用时显式禁用:
love show --color blue --no-color经验:不要迷信“跨平台”标签。CLI-Anything 的跨平台能力很强,但 Windows 的终端生态(PowerShell vs CMD vs Windows Terminal)极其碎片化。我的建议是:在
cli.yaml中始终为windows_compatibility字段提供默认值,并在 CI 中用 GitHub Actions 的windows-latestrunner 进行回归测试。
6.2 陷阱二:CLI-Hub 的缓存污染导致旧版本 CLI 无法更新
现象:cli-hub update love显示“Already up to date”,但love --version仍显示旧版本。
根因:CLI-Hub 使用git clone --depth 1进行浅克隆,但git的 shallow clone 无法获取所有 tags。当你在远端仓库打了一个新 tagv1.1.0,本地缓存的 shallow clone 里没有这个 tag,cli-hub update就认为没有新版本。
解决方案:强制刷新完整克隆:
cli-hub clean love # 清理 love 的本地缓存 cli-hub install love # 重新安装,这次会执行完整 clone或者,配置 CLI-Hub 使用--no-shallow选项(需 CLI-Hub v0.8.0+):
cli-hub config set hub.love.no_shallow true6.3 陷阱三:shell:命令中的环境变量未被正确继承
现象:cli.yaml中定义了一个shell: "./deploy.sh",但deploy.sh里echo $PATH发现缺少~/.local/bin。
根因:CLI-Anything 在执行shell:命令时,为了安全,默认使用一个最小化的env,只包含PATH、HOME、USER等基本变量,不继承当前 shell 的所有自定义变量。
解决方案:在cli.yaml中显式声明需要继承的变量:
commands: - name: deploy shell: "./deploy.sh" environment: - PATH - MY_CUSTOM_VAR - PYTHONPATHCLI-Anything 会自动将这些变量的值注入到子进程的env中。
6.4 陷阱四:choice类型参数的补全在 zsh 下不工作
现象:love show --format <TAB>在 zsh 下没有补全选项,但在 bash 下正常。
根因:zsh 的补全系统(zshcompinit)需要cli-any completion zsh生成的脚本被正确 sourced,且zstyle配置必须启用_cli_anythingcompleter。
解决方案:检查~/.zshrc是否包含:
# 确保 compinit 已加载 autoload -Uz compinit compinit # 确保 CLI-Anything 补全被启用 zstyle ':completion:*' completer _complete _ignored _approximate zstyle ':completion:*' matcher-list '' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*' # 最关键的一行 zstyle ':completion:*' users $(whoami)然后重新加载:source ~/.zshrc。
6.5 陷阱五:CLI-Anything 与 Poetry 环境的冲突
现象:在一个用 Poetry 管理的项目中,cli-any build生成的 CLI 无法找到项目依赖。
根因:CLI-Anything 的动态加载默认使用系统 Python 解释器,而非 Poetry 创建的虚拟环境。
解决方案:在cli.yaml中指定 Python 解释器路径:
# cli.yaml python_interpreter: ".venv/bin/python" # Linux/macOS # python_interpreter: ".venv\\Scripts\\python.exe" # Windows或者,更推荐的方式是:在 Poetry 项目中,将 CLI-Anything 作为 dev-dependency,并在pyproject.toml中配置:
[tool.poetry.dev-dependencies] cli-anything = "^0.9.0" [tool.cli-anything] # 这样 cli-any 命令就会在 Poetry 的虚拟环境中运行这些陷阱,每一个都曾让我在深夜的终端前抓耳挠腮半小时以上。它们不会出现在官方文档的“Quick Start”里,但却是决定一个 CLI 工具能否在真实团队中落地的关键。CLI-Anything 的强大,不在于它没有缺陷,而在于它的设计足够透明,让你能快速定位、理解并绕过这些缺陷。