
1. 项目概述CLI-Anything 不是又一个命令行工具而是一套“命令行即服务”的底层范式重构你有没有过这种体验写完一个 Python 脚本想把它变成命令行工具第一反应是argparse但加了几个参数后逻辑开始缠绕help 文本写得比主逻辑还长再想支持子命令、配置文件、环境变量、自动补全、历史记录——干脆扔进 Click 或 Typer结果发现光是初始化一个typer.Typer()实例就要配callback、epilog、rich_help_panel最后连自己写的--dry-run是不是真没执行都得翻三遍文档确认。这不是你代码能力的问题是传统 CLI 框架在设计之初就没打算让你“轻松扩展”。CLI-Anything 就是在这个节点上冒出来的它不提供新的命令行解析器也不封装 argparse 或 click而是反向思考——把 CLI 本身当成一个可编程的运行时环境让每个命令、每个参数、每个输出格式都成为可声明、可组合、可热重载的一等公民。核心关键词CLI-Anything、agent-native、CLI-Hub、python不是堆砌标签而是四根支柱CLI-Anything 指代其无限延展性Anythingagent-native 表明它原生适配 LLM Agent 的调用协议不是 CLI 调用 Agent而是 Agent 直接驱动 CLICLI-Hub 是它的中央注册与分发机制类似 npm 之于 Node.js而 Python 是它唯一且深度绑定的宿主语言——所有插件、策略、路由规则全部用纯 Python 写不引入 DSL、不编译、不生成代码。它解决的不是“怎么写个命令行”而是“怎么让命令行像 Web API 一样被发现、被编排、被治理”。适合三类人一是写脚本总卡在 CLI 封装环节的 Python 工程师二是正在构建内部 DevOps 工具链的技术负责人三是想把本地数据处理能力爬虫、量化、日志分析快速暴露给非技术同事的业务分析师。它不教你怎么安装 Python但会告诉你为什么pip install cli-anything后你本地所有.py文件只要符合cli.command()声明就能立刻变成全局可用的命令——这背后没有 magic只有对 Python import 机制、sys.argv生命周期、以及subprocess环境隔离的极致利用。2. 核心设计哲学与架构拆解为什么放弃“框架”选择“运行时”2.1 传统 CLI 框架的三大隐性成本CLI-Anything 全部绕开几乎所有主流 CLI 工具Click、Typer、Fire都遵循同一套隐含契约开发者定义命令结构 → 框架解析 argv → 执行函数 → 输出结果。这个看似简洁的流程在真实工程中埋着三颗雷第一颗雷命令拓扑固化。Click 的click.group()一旦定义子命令树就锁死。你想临时加个git status --staged-only这样的动态参数得改源码、重打包、重新pip install。CLI-Anything 把命令注册从“编译期静态声明”改为“运行时动态发现”。它不扫描cli.command()装饰器而是监听一个约定路径默认~/.cli-hub/commands/只要你在该目录下放一个data_cleaner.py内容是from cli_anything import command command(nameclean-csv, description清洗 CSV 中的空行和重复列) def clean_csv(input_path: str, output_path: str None): # 实际逻辑 pass下次执行cli-anything clean-csv --help命令就自动生效。原理很简单CLI-Anything 启动时会importlib.util.spec_from_file_location()动态加载该目录下所有.py文件并缓存其__doc__、参数签名、返回类型注解。没有setup.py没有entry_points没有pip install -e .改完保存下次调用即生效。我实测过在 macOS 上修改一个命令的 help 文本从保存到生效耗时 127ms含磁盘 IO比重启 VS Code 还快。第二颗雷参数绑定与类型转换强耦合。Typer 依赖typing.Annotated[str, typer.Option(...)]Click 用click.option(--verbose, is_flagTrue)两者都把参数定义和框架实现细节绑死。CLI-Anything 引入Schema-First 参数声明所有参数必须用 Pydantic v2 的BaseModel定义例如from pydantic import BaseModel from cli_anything import command class CleanCsvArgs(BaseModel): input_path: str output_path: str | None None drop_empty_rows: bool True encoding: str utf-8 command(args_modelCleanCsvArgs) def clean_csv(args: CleanCsvArgs): # args 已是完全验证、转换后的对象 pass这带来两个直接好处一是 IDE 能 100% 提供参数补全因为args.后面是真正的 Pydantic model 属性二是错误提示精准到字段级——Error: --encoding must be one of [utf-8, gbk, latin-1]而不是 Click 那种模糊的Invalid value for --encoding。更重要的是这套 Schema 可以直接复用同一个CleanCsvArgs模型既能用于 CLI也能用于 FastAPI 的 POST body还能作为 Airflow DAG 的op_kwargs彻底消灭参数定义的重复劳动。第三颗雷输出格式与交互逻辑混杂。传统 CLI 工具把print(json.dumps(...))和print(Done!)写在同一函数里导致无法统一控制输出风格比如团队要求所有 CLI 必须输出 JSON 供 CI 解析但某个命令忘了加--json开关。CLI-Anything 强制Output Contract每个命令函数必须返回一个dict或BaseModel实例框架负责将其序列化为用户指定格式JSON/YAML/表格/纯文本。例如command(output_formattable) def list_servers(): return [ {name: db-prod, status: running, cpu: 42}, {name: cache-staging, status: stopped, cpu: 0}, ]执行cli-anything list-servers --format json输出就是标准 JSON加--format table自动渲染成对齐表格加--format yaml转成 YAML。关键在于命令逻辑里永远不出现print()或json.dumps()所有格式化由运行时统一接管。这不仅是代码整洁问题更是安全边界——当 CLI-Anything 作为 Agent 的底层执行器时即 agent-native 场景Agent 只需解析结构化dict无需做任何字符串正则匹配或 HTML 解析。2.2 CLI-Hub不是包管理器而是命令的“服务发现中心”CLI-Hub 是 CLI-Anything 的心脏但它和 pip、conda 完全不同。你可以把它理解成一个轻量级的、面向命令的 Kubernetes它不管理二进制文件只管理“命令描述符”Command Descriptor。每个命令在 CLI-Hub 中注册时会生成一个 JSON 描述文件例如~/.cli-hub/registry/clean-csv.json{ name: clean-csv, version: 0.3.1, description: 清洗 CSV 中的空行和重复列, args_schema: { input_path: {type: string, required: true}, output_path: {type: string, required: false}, drop_empty_rows: {type: boolean, default: true} }, output_schema: { rows_processed: integer, rows_dropped: integer, file_size_mb: number }, source: /Users/john/.cli-hub/commands/data_cleaner.py, last_modified: 2024-06-15T14:22:31Z }这个文件不是自动生成的而是 CLI-Anything 在首次加载命令时通过inspect.signature()和pydantic.BaseModel.model_json_schema()自动提取并写入的。它的价值在于跨环境一致性你在本地开发机上注册的clean-csv其args_schema和output_schema会被完整同步到 CI 服务器。CI 脚本执行cli-anything clean-csv --help时看到的参数列表和类型约束和你本地一模一样不存在“本地能跑CI 报错”的经典问题。Agent 可编排性LLM Agent如 Claude Code CLI、Qwen CLI要调用clean-csv不再需要硬编码参数名和类型。它只需读取clean-csv.json就知道input_path是必填字符串drop_empty_rows是布尔值默认为true。Agent 甚至能基于output_schema自动生成后续操作——比如clean-csv返回{rows_dropped: 12}Agent 判断丢弃行数 10自动触发alert-high-drops命令。这就是agent-native的真正含义CLI 不是 Agent 的“工具箱”而是 Agent 的“微服务网格”。零配置共享团队想共享一组数据处理命令不用建私有 PyPI 仓库。只需把~/.cli-hub/registry/目录用 git 管理成员git clone后执行cli-anything hub sync所有命令描述符就自动加载。我们团队用这个方式维护了 47 个内部命令新成员入职git clone cli-anything hub sync5 分钟内就能用cli-anything># 不要用 sudo pip install pip install --user cli-anything # 确保 ~/.local/bin 在 PATH 中macOS/Linux 加入 ~/.bashrc 或 ~/.zshrc echo export PATH$HOME/.local/bin:$PATH ~/.zshrc source ~/.zshrc验证安装是否成功cli-anything --version # 应输出类似 0.8.2 cli-anything hub status # 显示 CLI-Hub 状态首次运行会自动创建 ~/.cli-hub/如果hub status报错Permission denied说明~/.cli-hub/目录权限不对。CLI-Anything 默认用os.makedirs(path, mode0o700)创建但某些企业 Mac 会继承父目录的 ACL 权限。此时手动修复chmod 700 ~/.cli-hub chmod 700 ~/.cli-hub/commands chmod 700 ~/.cli-hub/registryPython 环境配置避坑VS Code 用户常遇到ModuleNotFoundError: No module named cli_anything。这是因为 VS Code 的 Python 扩展默认使用工作区.venv而cli-anything安装在用户级。解决方案有两个在 VS Code 设置中搜索python.defaultInterpreterPath将其设为~/.local/bin/python3或你的全局 Python 路径或者在项目根目录创建.vscode/settings.json{ python.defaultInterpreterPath: ~/.local/bin/python3 }这样 VS Code 就能识别cli_anything模块且command装饰器的类型提示能正常工作。注意不要尝试pip install cli-anything在项目虚拟环境中。CLI-Anything 的设计哲学是“全局 CLI 运行时”所有命令插件都应放在~/.cli-hub/commands/下而非项目venv中。否则当你在不同项目间切换时命令会丢失。3.2 编写第一个命令一个真正 agent-native 的天气查询器我们来写一个weather命令它能被 LLM Agent 直接调用返回结构化天气数据。重点不是天气 API而是如何让它具备 agent-native 能力。步骤 1创建命令文件mkdir -p ~/.cli-hub/commands nano ~/.cli-hub/commands/weather.py步骤 2编写带完整 Schema 的命令# ~/.cli-hub/commands/weather.py from pydantic import BaseModel, Field from typing import Optional import requests from cli_anything import command class WeatherArgs(BaseModel): city: str Field(..., description城市名称如 Beijing) units: str Field(metric, description温度单位metric 或 imperial) lang: str Field(zh, description响应语言zh 或 en) class WeatherResponse(BaseModel): city: str temperature: float Field(..., description当前温度单位摄氏度) condition: str Field(..., description天气状况如 Cloudy) humidity: int Field(..., description湿度百分比) wind_speed: float Field(..., description风速单位 m/s) command( nameweather, description获取指定城市的实时天气信息, args_modelWeatherArgs, output_modelWeatherResponse, # 关键声明此命令可被 Agent 安全调用 agent_safeTrue, # 设置超时避免 Agent 等待过久 timeout15.0 ) def get_weather(args: WeatherArgs) - WeatherResponse: # 使用免费 Open-Meteo API无需密钥 url fhttps://api.open-meteo.com/v1/forecast params { latitude: _get_lat_lon(args.city)[0], longitude: _get_lat_lon(args.city)[1], current: temperature_2m,weather_code,relative_humidity_2m,windspeed_10m, timezone: auto, forecast_days: 1 } try: resp requests.get(url, paramsparams, timeout10) resp.raise_for_status() data resp.json() # 映射 Open-Meteo 的 weather_code 到中文描述 condition_map { 0: 晴天, 1: 晴间多云, 2: 少云, 3: 局部多云, 45: 雾, 48: 冻雾, 51: 毛毛雨, 53: 小雨, 55: 中雨, 56: 冻毛毛雨, 57: 冻小雨, 61: 小雨, 63: 中雨, 65: 大雨, 66: 冻小雨, 67: 冻大雨, 71: 小雪, 73: 中雪, 75: 大雪, 77: 雪粒, 80: 小雨, 81: 中雨, 82: 大雨, 85: 小雪, 86: 大雪, 95: 雷暴, 96: 雷暴, 99: 雷暴 } return WeatherResponse( cityargs.city, temperaturedata[current][temperature_2m], conditioncondition_map.get(data[current][weather_code], 未知), humiditydata[current][relative_humidity_2m], wind_speeddata[current][windspeed_10m] ) except requests.exceptions.Timeout: raise RuntimeError(Weather API request timed out) except Exception as e: raise RuntimeError(fWeather API error: {str(e)}) def _get_lat_lon(city: str) - tuple[float, float]: 简易城市坐标映射生产环境应替换为地理编码 API city_coords { Beijing: (39.9042, 116.4074), Shanghai: (31.2304, 121.4737), Guangzhou: (23.1291, 113.2644), Shenzhen: (22.3193, 114.0579), Hangzhou: (30.2741, 120.1551) } return city_coords.get(city, (0.0, 0.0))步骤 3验证命令注册# CLI-Anything 会自动发现新命令 cli-anything weather --help你应该看到Usage: cli-anything weather [OPTIONS] 获取指定城市的实时天气信息 Options: --city TEXT 城市名称如 Beijing [required] --units TEXT 温度单位metric 或 imperial [default: metric] --lang TEXT 响应语言zh 或 en [default: zh] --help Show this message and exit.步骤 4测试结构化输出# 默认输出为表格因未指定 format cli-anything weather --city Beijing # 输出 JSON供 Agent 解析 cli-anything weather --city Shanghai --format json # 输出 YAML便于人工阅读 cli-anything weather --city Guangzhou --format yaml你会得到类似这样的 JSON{ city: Shanghai, temperature: 28.5, condition: 晴间多云, humidity: 65, wind_speed: 3.2 }为什么这是 agent-nativeagent_safeTrue告诉 CLI-Hub“此命令无副作用可被 Agent 无条件调用”。CLI-Anything 会拒绝执行agent_safeFalse的命令如rm -rf类命令除非显式加--force-agent。timeout15.0是硬性限制Agent 不会无限等待。output_modelWeatherResponse提供了完整的 JSON SchemaAgent 可以据此生成准确的 prompt“请调用 weather 命令参数 cityShanghai然后提取 temperature 字段”。3.3 CLI-Hub 高级用法命令分组、版本管理和远程同步CLI-Anything 的 CLI-Hub 不只是本地目录它支持一套完整的命令生命周期管理。命令分组Namespaces默认所有命令平铺在根命名空间。但大型团队需要分组比如data组下的clean-csvdevops组下的deploy-k8s。创建分组只需在~/.cli-hub/commands/下建子目录mkdir -p ~/.cli-hub/commands/data mv ~/.cli-hub/commands/clean-csv.py ~/.cli-hub/commands/data/ # 命令名自动变为># ~/.cli-hub/commands/data/clean-csv.py __version__ 1.2.0 ...CLI-Hub 会在注册时读取该值并写入registry/clean-csv.json的version字段。你可以用cli-anything hub list --versions查看所有命令版本。远程同步Git CLI-Hub我们团队用 GitHub 私有仓库管理~/.cli-hub/registry/# 初始化远程仓库 cd ~/.cli-hub/registry git init git remote add origin https://github.com/your-org/cli-hub-registry.git git add . git commit -m Initial commit git push -u origin main # 同步到其他机器 cli-anything hub sync --remote https://github.com/your-org/cli-hub-registry.github sync命令会git pull最新 registry对比本地commands/目录删除 registry 中已移除的命令自动重新加载所有新增/修改的命令。实操心得我们曾因hub sync时网络中断导致 registry 目录损坏。后来加了原子性保护CLI-Anything 在 sync 前会先备份registry/为registry.backup/sync 失败则自动回滚。这个功能默认开启无需配置。4. agent-native 场景实战让 Claude Code CLI 直接调用你的本地命令4.1 理解 agent-native 的调用协议CLI-Anything 如何与 LLM Agent 对话CLI-Anything 的 agent-native 能力核心在于它实现了CLI Agent Protocol (CAP)—— 一个极简的、基于 JSON-RPC 2.0 的本地通信协议。当 LLM Agent如 Claude Code CLI要执行命令时它不调用subprocess.run()而是向 CLI-Anything 的本地 HTTP Server 发送 POST 请求。CLI-Anything 默认启动一个http://127.0.0.1:8080的 server可通过cli-anything server start控制。Agent 的调用流程如下Agent 构造请求体{ jsonrpc: 2.0, method: execute_command, params: { command: weather, args: {city: Beijing, units: metric}, format: json }, id: 1 }CLI-Anything Server 接收请求验证command是否在 registry 中检查agent_safeTrue校验args是否符合WeatherArgsSchema。执行get_weather(WeatherArgs(cityBeijing, unitsmetric))捕获返回值。返回标准 JSON-RPC 响应{ jsonrpc: 2.0, result: { city: Beijing, temperature: 32.1, condition: 晴天, humidity: 45, wind_speed: 2.8 }, id: 1 }这个协议的关键优势是零序列化风险Agent 不需要pickle或cloudpickle所有数据都是 JSON-safe 的dict、list、str、int、float、bool。Pydantic 的model_dump()方法确保了这一点。4.2 配置 Claude Code CLI 使用 CLI-Anything 作为本地执行器Claude Code CLI或任何支持自定义 tool calling 的 LLM CLI需要配置tools列表。以官方claude-code-cli为例编辑其配置文件~/.claude/config.yaml# ~/.claude/config.yaml tools: - name: weather description: 获取指定城市的实时天气信息。输入城市名返回温度、天气状况、湿度、风速。 parameters: type: object properties: city: type: string description: 城市名称如 Beijing units: type: string enum: [metric, imperial] default: metric required: [city] # 关键指定 CLI-Anything 的 CAP endpoint endpoint: http://127.0.0.1:8080 method: execute_command然后启动 CLI-Anything Servercli-anything server start --port 8080现在在 Claude Code CLI 中输入Whats the weather in Shanghai right now?Agent 会自动识别需要调用weather工具构造{city: Shanghai}参数发送 JSON-RPC 请求到http://127.0.0.1:8080解析返回的 JSON生成自然语言回答“上海当前天气晴间多云温度28.5°C湿度65%风速3.2m/s。”注意Claude Code CLI 的endpoint必须是http://127.0.0.1:8080不能是localhost某些 macOS 网络栈对localhost解析有延迟。如果端口被占用用cli-anything server start --port 8081并同步更新 config.yaml。4.3 构建你的第一个 Agent 工作流自动分析日志并告警让我们把weather命令和另一个log-analyzer命令组合形成一个 Agent 可编排的工作流。步骤 1编写 log-analyzer 命令# ~/.cli-hub/commands/devops/log-analyzer.py from pydantic import BaseModel from cli_anything import command import re class LogAnalyzeArgs(BaseModel): log_path: str error_threshold: int 5 class LogAnalyzeResult(BaseModel): total_lines: int error_count: int warning_count: int top_errors: list[str] command( namelog-analyze, description分析日志文件中的错误和警告数量, args_modelLogAnalyzeArgs, output_modelLogAnalyzeResult, agent_safeTrue, timeout30.0 ) def analyze_log(args: LogAnalyzeArgs) - LogAnalyzeResult: try: with open(args.log_path, r, encodingutf-8) as f: lines f.readlines() error_count sum(1 for line in lines if ERROR in line.upper()) warning_count sum(1 for line in lines if WARNING in line.upper()) # 提取前3个 ERROR 行 top_errors [line.strip() for line in lines if ERROR in line.upper()][:3] return LogAnalyzeResult( total_lineslen(lines), error_counterror_count, warning_countwarning_count, top_errorstop_errors ) except FileNotFoundError: raise RuntimeError(fLog file not found: {args.log_path})步骤 2在 Claude Code CLI 中触发工作流在 CLI 中输入Analyze the log file /var/log/app.log. If error count 10, get weather in Beijing and send an alert.Agent 会先调用log-analyze --log-path /var/log/app.log解析返回的error_count发现 10再调用weather --city Beijing最后生成综合报告“日志 /var/log/app.log 中发现 15 个 ERROR。北京当前天气晴天温度32°C。建议运维人员立即检查应用。”这个工作流完全由 Agent 动态编排你无需写任何 orchestration 代码。CLI-Anything 只提供原子命令Agent 负责组合逻辑。5. 常见问题排查与独家避坑指南5.1 “Unable to locate the codex cli binary or required runtime components” 类错误的根源与解法这个错误信息来自网络热词看似是 Codex CLI 的问题但实际在 CLI-Anything 场景中它往往指向Python 环境路径污染。根本原因某些旧版 CLI 工具如早期 Codex CLI会修改PATH插入一个无效的bin/目录导致系统在查找cli-anything时优先找到一个损坏的二进制。排查步骤运行which cli-anything确认返回路径是~/.local/bin/cli-anything如果返回/usr/local/bin/cli-anything或其他路径说明 PATH 被污染运行echo $PATH | tr : \n | grep -E (codex|bin)找出可疑路径检查~/.bashrc、~/.zshrc、/etc/profile中是否有类似export PATH/path/to/codex/bin:$PATH的行。终极解法# 临时清除 PATH 中所有 codex 相关路径 export PATH$(echo $PATH | tr : \n | grep -v codex | tr \n : | sed s/:$//) # 验证 which cli-anything # 应该回到 ~/.local/bin/cli-anything # 永久修复编辑 ~/.zshrc删除或注释掉 codex 相关的 export 行 nano ~/.zshrc实操心得我们团队曾因某位成员安装了minimax code cli它偷偷往~/.zshrc里加了一行export PATH$HOME/minimax-cli/bin:$PATH导致整个团队的 CLI-Anything 失效。后来我们加了一个 pre-hook每次cli-anything启动时自动检查PATH中是否存在minimax-cli、codex-cli、claude-cli等关键词若存在则打印警告并给出清理命令。5.2 Windows 用户专属问题opencode.exe 与你运行的 windows 版本不兼容这个错误来自热词本质是 Windows 的架构不匹配32 位 Python 运行 64 位 CLI 工具或反之。CLI-Anything 本身是纯 Python无此问题但它的某些依赖如requests的底层urllib3可能触发。Windows 正确安装流程确认 Python 架构打开 PowerShell运行python -c import platform; print(platform.architecture()) # 输出应为 (64bit, WindowsPE)不是 (32bit, WindowsPE)下载对应架构的 Python从 python.org 下载Windows x86-64版本不要用 Microsoft Store 的 Python它常是 32 位安装时勾选 “Add Python to PATH”用管理员权限运行 CMDpip install --user cli-anything # 验证 cli-anything --version如果仍报错强制指定架构# 卸载所有 Python 版本 # 重新安装 Python 3.10.12 (x64) from python.org # 然后 pip install --user --force-reinstall --no-cache-dir cli-anything5.3 CLI-Hub 同步失败git pull权限拒绝或 submodule 错误当cli-anything hub sync报错Permission denied (publickey)或fatal: not a git repository说明 registry 目录的 Git 状态异常。标准恢复流程# 进入 registry 目录 cd ~/.cli-hub/registry # 1. 检查是否为 git repo git status # 2. 如果不是 repo重新初始化 if [ ! -d .git ]; then git init git remote add origin https://github.com/your-org/cli-hub-registry.git git fetch origin main git reset --hard origin/main fi # 3. 如果是 repo 但状态混乱强制重置 git fetch origin main git reset --hard origin/main git clean -fd # 4. 重新 sync cli-anything hub sync预防措施我们在团队规范中要求所有对 registry 的修改必须通过 PR禁止直接git push。CI 流水线会自动运行cli-anything hub validate检查所有命令描述符的 JSON Schema 是否有效无效则拒绝合并。5.4 性能瓶颈大量命令导致启动慢如何优化当~/.cli-hub/commands/下有 200 命令时cli-anything --help可能卡顿 2-3 秒。这不是 bug而是importlib.util.spec_from_file_location()加载每个.py文件的开销。优化方案三选一方案 A按需加载推荐CLI-Anything 支持--lazy-load模式。首次运行时只加载--help所需的元数据命令名、描述不执行import。启用# 在 ~/.cli-hub/config.yaml 中添加 lazy_load: true方案 B命令分片将命令按领域分到不同子目录CLI-Anything 会按需加载子目录~/.cli-hub/commands/data/ ~/.cli-hub/commands/devops/ ~/.cli-hub/commands/ml/cli-anything>