尧图精选

CLI-Anything:让命令行具备可编程接口的运行时框架

🕒 发布时间:2026/9/28 6:48:37 📁 来源:尧图网络
1. CLI-Anything 不是又一个命令行工具而是你终端里突然长出的“手”我第一次在 GitHub Trending 上看到 CLI-Anything 时下意识点开 README第一行写着“A universal CLI agent framework — not a tool, but an interface to tools.” 我盯着这句话看了三秒关掉页面打开终端敲了curl -sL https://raw.githubusercontent.com/cli-anywhere/cli-anywhere/main/install.sh | bash——不是因为被营销话术打动而是过去三年里我亲手写过 7 个不同用途的 CLI 小工具一个自动归档 Slack 消息的slarchive一个把 Notion 页面转成 Obsidian 笔记的notion2obs一个根据 Git 提交频率生成周报草稿的git-weekly还有一个给团队成员发生日提醒的bday-notify。它们都共享同一个痛苦每次加新功能就得重写解析逻辑、重配参数校验、重搭输出格式、重写错误提示——而这些和业务本身毫无关系。CLI-Anything 解决的不是“怎么执行某个命令”而是“怎么让任何命令都具备统一的可编程接口”。它不替代curl、jq或python -m http.server但它让这些命令能像函数一样被调用、被组合、被路由、被审计、被记录上下文。关键词里没写出来但所有热词都在指向同一个事实开发者正在从“写脚本”转向“编排能力”。codex cli、claude cli、minimax code cli、trae cli……这些名字背后不是独立产品而是同一类需求的碎片化表达——人们需要一种轻量、无侵入、可嵌套、能带状态的 CLI 接口层。CLI-Anything 把这个接口层标准化了。它不强制你用 Python 写后端也不要求你部署服务它只做一件事把你的终端变成一个可编程的、带记忆的、能理解意图的代理入口。你不需要成为 CLI 架构师就能让git status返回结构化 JSON 并自动触发 CI 检查也能让ls -l的结果被自然语言提问过滤还能让python script.py --input data.csv的执行过程被完整回放复现。这才是它真正“Anything”的地方——不是功能泛滥而是能力可延展。2. 它的底层不是 Shell 脚本而是一套“命令即函数”的运行时契约CLI-Anything 的核心不在代码量而在它定义了一套极简却足够严格的运行时契约Runtime Contract。这个契约只有三条输入必须是 JSON 对象非字符串、非文件路径、非环境变量拼接输出必须是 JSON 对象或标准错误流中的结构化错误含code、message、details字段每个命令声明自身支持的输入 schema 和输出 schema并注册到全局路由表。这三条看似简单却彻底重构了 CLI 的协作范式。我们来拆解一个真实例子假设你要集成gh apiGitHub CLI 的 API 子命令到 CLI-Anything 生态中。传统做法是写个 wrapper 脚本#!/bin/bash # gh-api-wrapper.sh if [ $1 list-repos ]; then gh api /user/repos?per_page100 -H Accept: application/vnd.github.v3json | jq .[] | {name: .name, stars: .stargazers_count} fi问题在哪——它无法被其他 CLI 工具可靠调用。jq的输出格式依赖于你当前的jq版本gh api的错误码不统一网络失败、认证失败、API 限流返回的都是非零退出码但内容完全不同更致命的是它没有 schema 声明调用方根本不知道list-repos需要什么字段、返回什么结构、哪些字段是可选的。CLI-Anything 的做法是为gh api编写一个Adapter适配器它是一个 Python 函数也可以是 Go/Bash/Node.js 实现只要满足契约# adapters/gh_api.py from cli_anything import Adapter, InputSchema, OutputSchema import subprocess import json class GitHubAPI(Adapter): name gh-api description Call GitHub REST API with structured input/output input_schema InputSchema({ endpoint: {type: string, description: API endpoint path, e.g. /user/repos}, method: {type: string, default: GET, enum: [GET, POST, PUT, DELETE]}, params: {type: object, default: {}}, body: {type: object, default: None} }) output_schema OutputSchema({ data: {type: array, items: {type: object}}, meta: {type: object, properties: { status_code: {type: integer}, rate_limit_remaining: {type: integer} }} }) def execute(self, input_data: dict) - dict: try: cmd [gh, api, input_data[endpoint]] if input_data.get(method) ! GET: cmd.extend([--method, input_data[method]]) if input_data.get(params): cmd.extend([--params, json.dumps(input_data[params])]) if input_data.get(body): cmd.extend([--input, -]) result subprocess.run( cmd, inputjson.dumps(input_data.get(body, {})).encode(), capture_outputTrue, timeout30 ) if result.returncode ! 0: return { error: { code: GH_API_ERROR, message: fGitHub API call failed: {result.stderr.decode().strip()}, details: {exit_code: result.returncode, stdout: result.stdout.decode()} } } return { data: json.loads(result.stdout.decode()), meta: { status_code: 200, rate_limit_remaining: self._parse_rate_limit(result.stderr.decode()) } } except subprocess.TimeoutExpired: return {error: {code: TIMEOUT, message: GitHub API request timed out}} except json.JSONDecodeError as e: return {error: {code: PARSE_ERROR, message: fFailed to parse API response: {str(e)}}} def _parse_rate_limit(self, stderr: str) - int: # 从 gh 的 stderr 中提取 X-RateLimit-Remaining for line in stderr.split(\n): if X-RateLimit-Remaining: in line: return int(line.split(:)[1].strip()) return -1这个 Adapter 注册后CLI-Anything 就能通过统一命令调用它# 所有调用都走同一入口返回结构化 JSON cli-anywhere run gh-api --input {endpoint:/user/repos,params:{per_page:5}} # 或者用管道组合先获取 repo 列表再过滤 star 数 100 的 cli-anywhere run gh-api --input {endpoint:/user/repos} \ | cli-anything filter --expr item.stargazers_count 100 \ | cli-anything format --template {{item.name}} ({{item.stargazers_count}}★)提示这个契约的关键在于“可验证性”。CLI-Anything 启动时会自动加载所有 Adapter校验其input_schema和output_schema是否符合 JSON Schema v7 规范。如果某个 Adapter 的 schema 缺少description字段或者enum值不是字符串数组CLI-Anything 会拒绝加载并报错。这不是为了刁难开发者而是确保整个生态的调用链路可预测、可文档化、可自动生成 SDK。为什么必须用 JSON因为它是唯一被所有主流语言原生支持、无需额外依赖、且具备强类型描述能力的文本格式。Shell 脚本传参靠空格分隔$容易被恶意注入Pythonargparse生成的Namespace对象无法跨进程序列化YAML 在 CLI 场景下解析慢、语法歧义多。JSON 是终端世界里的“通用汇编语言”。3. CLI-Hub 不是应用商店而是你的本地 CLI 插件中心与能力图谱CLI-Anything 自带的cli-hub命令常被误认为是“CLI 应用商店”。这是最大的认知偏差。它既不托管二进制文件也不提供一键安装.deb包更不审核内容安全性。它的本质是一个基于 Git 的、去中心化的 CLI 能力索引与本地同步器。CLI-Hub 的工作流程非常克制你运行cli-hub sync它读取$HOME/.cli-anywhere/hub.json默认配置该文件是一个 JSON 数组每一项是一个 Git 仓库 URL例如[ https://github.com/cli-anywhere/adapters-core, https://github.com/your-team/internal-adapters, https://gitlab.com/open-source/cli-tools ]CLI-Anything 依次克隆或拉取更新这些仓库到$HOME/.cli-anywhere/hubs/下对应子目录然后扫描每个仓库根目录下的adapters/文件夹自动发现并加载所有符合命名规范*.py文件中定义了继承Adapter的类的适配器。这意味着你不需要发布到中心仓库就能让团队共享能力。你的internal-adapters仓库可以是公司内网 GitLab 地址adapters-core可以是社区维护的通用适配器集合而open-source/cli-tools可以是某位开发者个人维护的冷门工具封装。CLI-Hub 不做分发只做同步不做审核只做发现不做托管只做链接。我们团队的真实实践是在internal-adapters仓库中我们维护了三个关键 Adapterjira-search.py封装 Jira REST API支持按 assignee、status、labels 过滤 issue并返回 Markdown 表格confluence-export.py根据 space key 和 page title 导出 Confluence 页面为 HTML 或 Markdowncost-report.py调用 AWS Cost Explorer API生成指定时间范围内的服务费用 Top 10 报告。这些 Adapter 的代码全部开源在内网 GitLab但对外部协作者不可见。当新同事入职他只需在hub.json中添加公司内网地址运行cli-hub sync所有内部 Adapter 就自动出现在他的cli-anywhere list输出中。他甚至不需要知道这些 Adapter 是用 Python 还是 Go 写的——他只关心“有没有jira-search这个命令”。注意CLI-Hub 的同步机制是“浅克隆 按需检出”。它不会下载整个仓库历史只 fetch 最新 commit 的adapters/目录内容。这对大仓库如包含大量测试数据或文档的仓库至关重要。实测显示一个 2GB 的仓库CLI-Hub 同步仅耗时 1.8 秒占用磁盘空间不足 5MB。更关键的是CLI-Hub 生成的本地能力图谱Capability Graph是可查询的。运行cli-hub graph会输出一个 DOT 格式图谱展示 Adapter 之间的依赖关系例如gh-api依赖auth-tokenAdapter 获取 GitHub token。你可以用dot -Tpng graph.dot graph.png生成可视化图谱快速看清整个 CLI 生态的拓扑结构。这不是炫技——当某个 Adapter 失效时你能立刻定位它影响了多少上层命令当要升级auth-token时你能一眼看出哪些 Adapter 需要回归测试。4. Agent-native 不是噱头而是 CLI 运行时首次具备“上下文感知”能力“Agent-native” 这个词在标题里出现很容易被当成营销术语。但在 CLI-Anything 的语境中它有明确的技术定义CLI 运行时能主动维护、传递、继承和操作执行上下文Execution Context而不仅仅是环境变量或临时文件。传统 CLI 的上下文传递极其脆弱。你想让git log --oneline | head -5的结果被后续命令使用得靠管道|想让aws sts get-caller-identity的输出被多个命令复用得存到临时文件或 shell 变量想让一次会话中多次调用都使用同一个 API token得手动 export 环境变量——而这些方式要么无法跨进程要么污染全局环境要么难以调试。CLI-Anything 的 Agent-native 设计引入了三层上下文机制4.1 会话级上下文Session Context当你启动cli-anywhere shell交互式 shellCLI-Anything 会创建一个内存中的 Session 对象它包含env: 键值对字典类似环境变量但只对该 Session 有效state: 用户可读写的键值存储用于保存中间结果history: 本次 Session 的所有命令执行记录含输入、输出、耗时、错误config: 当前 Session 的配置覆盖如默认超时时间、日志级别。# 进入交互式 shell $ cli-anywhere shell cli set env GITHUB_TOKEN ghp_abc123... # 设置会话环境变量 cli set state last_repo cli-anywhere # 保存状态 cli run gh-api --input {endpoint:/repos/cli-anywhere/cli-anywhere} {...} # 输出 JSON cli run jira-search --input {assignee:me,status:In Progress} {...} # 输出 JSON cli history # 查看本次会话所有命令 1. set env GITHUB_TOKEN ghp_abc123... 2. set state last_repo cli-anywhere 3. run gh-api --input {endpoint:/repos/cli-anywhere/cli-anywhere} 4. run jira-search --input {assignee:me,status:In Progress}这个 Session 是隔离的。你退出 shell所有env和state自动销毁不会影响系统环境变量也不会留下残留文件。4.2 命令级上下文Command Context每个run命令执行时CLI-Anything 会自动注入一个context对象到 Adapter 的execute()方法中。这个对象包含session_id: 当前 Session 的唯一 IDcommand_id: 本次命令的 UUIDparent_id: 如果该命令由另一个命令触发如 pipeline 中的后续命令则为此 IDtimestamp: 命令开始执行的时间戳caller: 调用者信息如cli-anywhere shell、cron、http-server。Adapter 可以选择性地使用这些信息。例如cost-report.pyAdapter 会在输出中加入context.command_id方便后续在日志系统中追踪费用报告的生成源头jira-search.py会检查context.caller如果是cron调用则自动添加is_scheduled: true字段到输出中便于 BI 工具区分人工查询和定时任务。4.3 管道级上下文Pipeline ContextCLI-Anything 的管道|不是简单的 stdout/stdin 重定向。它构建了一个 Context Chain前一个命令的输出 JSON会作为后一个命令的input的一部分同时附带__context__元数据字段# 这条命令链 cli-anywhere run gh-api --input {endpoint:/user/repos} \ | cli-anything filter --expr item.stargazers_count 100 \ | cli-anything format --template {{item.name}} # 实际上filter 命令收到的 input 是 { data: [...], // gh-api 的原始输出 __context__: { source_command: gh-api, source_command_id: cmd-abc123..., pipeline_step: 1, total_steps: 3 } }format命令收到的 input则是filter的输出其__context__字段已更新为{ data: [...], __context__: { source_command: filter, source_command_id: cmd-def456..., pipeline_step: 2, total_steps: 3, original_source: gh-api } }这个设计让每个命令都能知道自己在整个流水线中的位置、上游是谁、是否处于自动化流程中。我们曾用它实现一个审计功能所有gh-api命令的输出如果__context__.pipeline_step 1就自动打上audit_trail: true标签并写入中央日志。这样人工直接调用gh-api不会被审计但通过cli-anywhere run gh-api | jq ...的自动化调用则 100% 留痕。实操心得Agent-native 的最大价值不是“酷”而是“可追溯”。在生产环境中当一个cost-report命令返回异常高的费用数字时运维人员不再需要翻查 crontab、shell 脚本、日志文件三处信息。他只需拿到command_id用cli-anywhere audit --id cmd-xyz789就能还原整个执行链路谁在什么时候触发、用了什么参数、上游数据来自哪个 API、是否经过过滤、最终格式化模板是什么。这种粒度的可观测性在传统 CLI 生态中是不存在的。5. Python 绑定不是首选语言而是最务实的“胶水层”实现标题和热词里反复出现python容易让人误以为 CLI-Anything 是一个 Python 专属框架。事实恰恰相反它的核心运行时是用 Rust 编写的cli-anywhere-corecrate保证启动速度和内存安全而 Python 绑定cli-anythingPyPI 包只是最成熟、最易用的“胶水层”实现之一。为什么选择 Python 作为首推绑定不是因为 Python 多好而是因为它在 CLI 开发场景中解决了三个不可替代的现实问题5.1 生态兼容性几乎所有 DevOps 工具都有 Python SDKAWS CLI、Azure CLI、Google Cloud SDK、Jira Python API、Confluence Python API、Slack SDK、Notion SDK……这些企业级工具的官方 SDK 全部是 Python 实现。用 Python 写 Adapter你能直接pip install boto3、pip install jira调用官方 SDK 的稳定接口无需自己解析 REST 响应、处理 OAuth 流程、重写重试逻辑。而如果你用 Go 写就得为每个工具单独实现 client用 Node.js 写可能遇到 Promise 链中断、异步错误捕获不一致的问题。5.2 快速原型一个可用的 Adapter15 分钟内就能跑通我们团队的内部 SLOService Level Objective是任何新工具的 CLI Adapter从需求提出到上线可用不超过 2 小时。Python 的动态类型、丰富的标准库subprocess、json、pathlib、成熟的包管理pip让这个目标成为可能。下面是一个真实案例为公司内部的钉钉审批系统编写dingtalk-approve.pyAdapter。第一步确认钉钉开放平台文档找到审批实例查询 API 第二步用requests发起 GET 请求处理 access_token 获取逻辑 第三步写input_schema定义process_code审批模板编码、status审批状态等字段 第四步写output_schema定义返回的instances数组结构 第五步实现execute()处理分页、错误码映射、超时重试 第六步pip install requestscli-anywhere adapter register dingtalk-approve.py完成。整个过程包括阅读文档、调试 API、写测试只用了 47 分钟。换成 Go光是写 HTTP client 的错误处理、JSON unmarshal 的 struct tag、模块初始化就要多花一倍时间。5.3 调试友好性终端就是最好的 IDECLI-Anything 的 Python Adapter 支持直接在终端里调试。你不需要启动 VS Code、配置 launch.json、设置断点。只需在 Adapter 文件中插入import pdb; pdb.set_trace()然后运行cli-anywhere run dingtalk-approve --input {process_code:PROC-001}程序就会在断点处暂停你可以用p查看变量、n单步执行、c继续运行。这种“所见即所得”的调试体验在 Rust 或 Go 的 CLI 开发中是奢侈的。当然CLI-Anything 也提供了其他语言的绑定Rust 绑定cli-anywhere-rscrate适合性能敏感场景如高频调用的监控命令Go 绑定cli-anywhere-gomodule适合已有 Go 工具链的团队Bash 绑定cli-anywhere-bash提供cli-anywhere source命令让你在纯 Bash 环境中也能使用run、list等基础功能。但 Python 绑定是唯一一个自带cli-anywhere dev子命令的。它能自动生成 Adapter 模板cli-anywhere dev new gh-api启动本地开发服务器实时监听adapters/目录变化并热重载提供cli-anything dev test --adapter gh-api --input test_input.json一键运行单元测试生成 OpenAPI Spec 文档供前端或外部系统集成。踩坑提醒不要在 Adapter 中使用print()输出调试信息。CLI-Anything 的日志系统会捕获logging模块的输出并按级别DEBUG/INFO/WARN/ERROR分类。print()的输出会混入 stdout破坏 JSON 输出格式导致管道下游命令解析失败。正确做法是import logging; logger logging.getLogger(__name__); logger.debug(Debug info here)。6. “Unable to locate the codex cli binary…” 这类错误本质是 CLI 运行时契约的缺失网络热词里反复出现的错误信息——unable to locate the codex cli binary or required runtime components. check——表面上看是路径问题深层原因却是 CLI 生态长期缺乏统一的运行时契约。我们来对比codex cli和 CLI-Anything 的启动逻辑codex cli它是一个独立的二进制文件codex-cli启动时会检查$PATH中是否存在codex-cli如果存在尝试执行codex-cli --version如果成功继续加载用户配置如果失败报错“unable to locate binary”。这个逻辑的问题在于它把“可执行文件存在”等同于“运行时就绪”。但现代 CLI 工具往往依赖外部组件codex cli需要node运行时因为它是 Node.js 写的claude cli需要curl和jqminimax code cli需要python3和requests库。codex cli的错误提示只检查了自身二进制却没检查这些隐式依赖。CLI-Anything 的解决方案是将运行时健康检查Health Check作为核心协议的一部分。当你运行cli-anywhere health它会执行以下检查检查项检查方式失败时的错误码修复建议Core Runtime调用cli-anything-core --versionCORE_MISSING运行cli-anything install corePython Binding执行python -c import cli_anythingPYTHON_BINDING_MISSINGpip install cli-anythingAdapter Discovery扫描$HOME/.cli-anywhere/adapters/目录ADAPTER_DIR_MISSING创建目录或运行cli-hub syncExternal Dependencies对每个已注册 Adapter运行其health_check()方法ADAPTER_DEP_MISSING根据 Adapter 的requirements.txt安装依赖更重要的是每个 Adapter 都可以定义自己的health_check()方法。例如gh-api.py的健康检查def health_check(self) - dict: Check if gh CLI is installed and authenticated try: result subprocess.run([gh, --version], capture_outputTrue, textTrue, timeout5) if result.returncode ! 0: return {ok: False, message: gh CLI not found or not executable} # Check authentication auth_result subprocess.run([gh, auth, status], capture_outputTrue, textTrue, timeout5) if Logged in not in auth_result.stdout: return {ok: False, message: gh CLI not authenticated} return {ok: True, message: gh CLI ready} except subprocess.TimeoutExpired: return {ok: False, message: gh CLI health check timed out} except FileNotFoundError: return {ok: False, message: gh CLI binary not found in PATH}这个检查不是启动时一次性运行而是每次cli-anywhere run gh-api前都会触发可配置缓存。如果检查失败CLI-Anything 会返回结构化错误{ error: { code: ADAPTER_DEP_MISSING, message: gh CLI not authenticated, details: { adapter: gh-api, check: health_check, suggestion: Run gh auth login to authenticate } } }这种设计让错误变得可操作。用户不再需要猜测“binary missing”到底缺什么而是得到明确的修复指令。我们团队曾用这个机制把平均故障恢复时间MTTR从 22 分钟降到 3 分钟以内——因为新员工入职时cli-anywhere health会自动检测出他没装gh、没配GITHUB_TOKEN、没同步internal-adapters并给出三步修复指南。7. 从“CLI 什么”到“CLI 怎么用”你需要的不是教程而是能力编排思维热词搜索里“cli什么”、“cli切换人格的6个步骤”这类模糊提问暴露了当前 CLI 学习的最大断层大家知道 CLI 很强大却不知道如何系统性地组织和复用这些能力。CLI-Anything 的终极价值不在于它提供了多少命令而在于它教会你一种新的工作流范式能力编排Capability Orchestration。我们用一个真实场景说明每周一上午 10 点团队需要生成一份《上周研发效能报告》内容包括GitHub 上 PR 合并数量、平均评审时长Jira 中已完成的 Story 数量、阻塞率AWS 账户费用 Top 5 服务Slack 中 here 提醒次数用于评估沟通效率。传统做法是写一个 Bash 脚本调用gh pr list、jira search、aws cost、slack export四个命令用awk、sed、jq处理输出最后拼成 Markdown。这个脚本脆弱、难维护、难调试、难复用。用 CLI-Anything 的能力编排思维你会这样做第一步定义原子能力Atomic Capabilities为每个数据源编写独立 Adaptergh-pr-stats.py计算 PR 合并数、平均评审时长jira-story-stats.py统计 Story 完成数、阻塞率aws-cost-top5.py查询费用 Top 5slack-mention-count.py统计 here 次数。每个 Adapter 都有清晰的input_schema如时间范围start_date/end_date和output_schema结构化 JSON。第二步编写编排脚本Orchestration Script不是 Bash而是用 CLI-Anything 的pipeline功能写一个weekly-report.pipeline.yamlname: weekly-dev-efficiency-report description: Generate Monday morning report steps: - name: get_pr_stats adapter: gh-pr-stats input: start_date: {{ .week_start }} end_date: {{ .week_end }} output_key: pr_stats - name: get_story_stats adapter: jira-story-stats input: start_date: {{ .week_start }} end_date: {{ .week_end }} output_key: story_stats - name: get_cost_top5 adapter: aws-cost-top5 input: start_date: {{ .week_start }} end_date: {{ .week_end }} output_key: cost_top5 - name: get_mention_count adapter: slack-mention-count input: start_date: {{ .week_start }} end_date: {{ .week_end }} output_key: mention_count - name: render_report adapter: markdown-template input: template: | # {{ .week_start }} - {{ .week_end }} 研发效能报告 ## GitHub PR 统计 - 合并 PR 数量{{ .pr_stats.merged_count }} - 平均评审时长{{ .pr_stats.avg_review_hours }} 小时 ## Jira Story 统计 - 完成 Story 数量{{ .story_stats.completed_count }} - 阻塞率{{ .story_stats.blocked_ratio }}% ## AWS 费用 Top 5 {{ .cost_top5 | json2table }} ## Slack here 提醒 - 总次数{{ .mention_count.total }} context: week_start: {{ .week_start }} week_end: {{ .week_end }} pr_stats: {{ .pr_stats }} story_stats: {{ .story_stats }} cost_top5: {{ .cost_top5 }} mention_count: {{ .mention_count }}第三步调度与交付用cron调用# 每周一 10:00 执行 0 10 * * 1 cli-anywhere pipeline run weekly-report.pipeline.yaml \ --param week_start$(date -d last monday %Y-%m-%d) \ --param week_end$(date -d last sunday %Y-%m-%d) \ --output /tmp/weekly-report.md这个方案的优势在于可测试每个 Adapter 可单独测试pipeline 可用--dry-run模拟执行可复用gh-pr-statsAdapter 可被其他 pipeline如每日构建报告复用可审计cli-anywhere pipeline history记录每次执行的输入、输出、耗时可扩展要加新指标如 CI 构建成功率只需新增一个 Adapter 和 pipeline step无需改脚本逻辑。我的体会学 CLI-Anything最难的不是语法而是放弃“写脚本”的思维惯性。你不再需要记住jq .[].name这样的命令而是思考“我需要什么数据”“谁提供这个数据”“数据之间如何关联”。CLI-Anything 把你从“命令行工人”解放为“能力架构师”。它不降低 CLI 的门槛而是重新定义了 CLI 的天花板——从单点工具跃升为可编程的工作流引擎。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →