qi-cli 是什么,为什么用它
命令行工具的常见痛点不在「解析参数」,而在组织命令:你想要 待办 添加、待办 列表、待办 配置 设置 这样的命令树;想要全局开关(--详细)能被所有子命令继承;想要在命令执行前后跑钩子(鉴权、日志、清理)。手写这些会很快失控。
qi-cli 是一个完全用奇语编写的库,风格直接对标 Go 社区的 spf13/cobra。底层参数解析复用标准库的 标准库.命令行(基于 clap),上层提供三个核心类型:
应用—— 顶层 CLI 应用(名称、版本、作者、简介)命令—— 命令或子命令,可以无限嵌套成树参数—— 标志(flag)或位置参数
命令的处理函数签名是固定的 函数(上下文): 整数,返回退出码(0 表示成功)。在处理函数里通过 上下文 读取参数与标志的值。
应用值 = 应用版本(应用值, "1.0.0");。忘记回写是最常见的坑。第一个命令:根命令
先从一个只有根命令的最小程序开始。根命令是用户什么子命令都不带时执行的那个。先声明包、导入标准库输入输出和 qi-cli 的 API,然后用 创建应用 建一个应用,用 应用执行 绑定根处理函数,最后调用 执行 解析命令行并分发。
包 主程序;
导入 标准库.输入输出 作为 IO;
导入 CLI::{ 创建应用, 应用简介, 应用执行, 执行 };
函数 根执行(上下文值: 上下文) : 整数 {
IO.打印行("待办清单 CLI —— 用 --help 查看所有命令");
返回 0;
}
函数 入口() {
变量 应用值: 应用 = 创建应用("待办");
应用值 = 应用简介(应用值, "一个用 qi-cli 写的待办清单工具");
应用值 = 应用执行(应用值, 根执行);
执行(应用值); // 解析 argv 并分发
}
用 qi 编译器运行(注意 -- 之后才是传给你的 CLI 的参数):
$ qi run 待办.qi
待办清单 CLI —— 用 --help 查看所有命令添加子命令
子命令用 创建命令 创建,配上简介和处理函数后,用 应用添加命令 挂到应用上。下面给「待办」加一个 添加 子命令。
函数 执行添加(上下文值: 上下文) : 整数 {
变量 标题: 字符串 = 值(上下文值, "标题");
IO.打印行("已添加待办: " + 标题);
返回 0;
}
// 在 入口() 里:
变量 添加命令: 命令 = 创建命令("添加");
添加命令 = 命令简介(添加命令, "添加一条待办");
添加命令 = 命令执行(添加命令, 执行添加);
应用值 = 应用添加命令(应用值, 添加命令);
参数:位置参数、局部 flag、持久 flag
qi-cli 用 创建参数 创建参数,再用一系列 setter 修饰它:
参数长名(p, "优先级")/参数短名(p, "p")—— 设置--优先级/-p参数帮助/参数默认值—— 帮助文本与默认值参数必需—— 标记为必填参数标志—— 布尔开关(出现即为真,不带值)参数持久—— 继承到所有子命令(走 clap 的 global 机制)
不设长/短名、也不是标志的参数,就是位置参数(按顺序匹配,如 待办 添加 "写教程" 里的标题)。下面完整给 添加 命令配上:必需的位置参数 标题、带默认值的 --优先级、布尔标志 --紧急。
函数 执行添加(上下文值: 上下文) : 整数 {
变量 标题: 字符串 = 值(上下文值, "标题");
变量 优先级: 整数 = 整数值(上下文值, "优先级", 3); // 带默认值
IO.打印行("已添加待办: " + 标题 + "(优先级 " + 整数转字符串(优先级) + ")");
如果 (标志(上下文值, "紧急") == 1) { // 读布尔开关,0/1
IO.打印行(" -> 标记为紧急");
}
返回 0;
}
// 位置参数:必需
变量 标题参数: 参数 = 创建参数("标题");
标题参数 = 参数帮助(标题参数, "待办标题(位置参数)");
标题参数 = 参数必需(标题参数);
添加命令 = 命令添加参数(添加命令, 标题参数);
// 带值 flag:--优先级 / -p,有默认值 3
变量 优先级参数: 参数 = 创建参数("优先级");
优先级参数 = 参数长名(优先级参数, "优先级");
优先级参数 = 参数短名(优先级参数, "p");
优先级参数 = 参数默认值(优先级参数, "3");
添加命令 = 命令添加参数(添加命令, 优先级参数);
// 布尔标志:--紧急
变量 紧急参数: 参数 = 创建参数("紧急");
紧急参数 = 参数长名(紧急参数, "紧急");
紧急参数 = 参数标志(紧急参数);
添加命令 = 命令添加参数(添加命令, 紧急参数);
持久参数则挂在应用(或父命令)上,所有子命令都能读到。下面加一个全局 --详细 / -v:
变量 详细参数: 参数 = 创建参数("详细");
详细参数 = 参数长名(详细参数, "详细");
详细参数 = 参数短名(详细参数, "v");
详细参数 = 参数帮助(详细参数, "输出详细日志");
详细参数 = 参数标志(详细参数);
详细参数 = 参数持久(详细参数); // 关键:继承到所有子命令
应用值 = 应用添加参数(应用值, 详细参数);
参数标志 的参数若没设长/短名,框架会自动用参数名作长名(clap 要求 flag 必须有名)。位置参数与别名
别名让命令多个名字可触发,用 命令添加别名。给 添加 加别名 a,给 列表 加别名 ls:
添加命令 = 命令添加别名(添加命令, "a");
列表命令 = 命令添加别名(列表命令, "ls");
位置参数(无长/短名、非标志)在处理函数里同样用 值(上下文值, "名字") 读取。可选的带值参数若没传,存在值 会返回 0,可据此分支。
执行钩子:前置 / 后置 / 持久
钩子是 qi-cli 相对手写解析最值钱的特性。一共四种:
命令前置执行/命令后置执行—— 只在该命令本身执行前/后跑(局部)命令持久前置执行/命令持久后置执行—— 会传递到所有子命令,且拿到最深匹配命令的上下文
下面给 列表 命令加局部前置/后置钩子(打印列表的开始/结束分隔线),给 添加 命令挂一个持久前置钩子读取全局 --详细 决定是否打印调试信息。命令路径 返回当前完整命令路径,方便日志。
// 持久前置钩子:执行前根据 --详细 打印路径
函数 全局前置(上下文值: 上下文) : 整数 {
如果 (标志(上下文值, "详细") == 1) {
IO.打印行("[详细] 即将执行: " + 命令路径(上下文值));
}
返回 0;
}
// 列表命令的局部前置/后置钩子
函数 列表前置(上下文值: 上下文) : 整数 {
IO.打印行("--- 待办列表开始 ---");
返回 0;
}
函数 列表后置(上下文值: 上下文) : 整数 {
IO.打印行("--- 待办列表结束 ---");
返回 0;
}
// 挂钩子(在 入口() 里)
添加命令 = 命令持久前置执行(添加命令, 全局前置);
列表命令 = 命令前置执行(列表命令, 列表前置);
列表命令 = 命令后置执行(列表命令, 列表后置);
命令树:两层子命令
子命令可以继续添加子命令,形成命令树。用 命令添加子命令 把 设置 挂到 配置 下,得到 待办 配置 设置 <键> <值>。
变量 配置命令: 命令 = 创建命令("配置");
配置命令 = 命令简介(配置命令, "管理配置");
变量 设置命令: 命令 = 创建命令("设置");
设置命令 = 命令执行(设置命令, 执行配置设置);
// 两个必需位置参数:键、值
变量 键参数: 参数 = 参数必需(创建参数("键"));
设置命令 = 命令添加参数(设置命令, 键参数);
变量 值参数: 参数 = 参数必需(创建参数("值"));
设置命令 = 命令添加参数(设置命令, 值参数);
配置命令 = 命令添加子命令(配置命令, 设置命令);
应用值 = 应用添加命令(应用值, 配置命令);
运行演示(真实命令与输出)
以下全部是上面这份 教程_快速开始.qi(仓库 qi-cli/examples/ 下)用 qi 编译器真正跑出来的输出。运行格式为 qi run 文件.qi -- <子命令与参数>。
添加一条待办,带位置参数、--优先级 和 --紧急 标志:
$ qi run 教程_快速开始.qi -- 添加 "写教程" --优先级 1 --紧急
已添加待办: 写教程(优先级 1)
-> 标记为紧急用别名 a,并打开持久 flag -v——持久前置钩子被触发:
$ qi run 教程_快速开始.qi -- a "买菜" -v
[详细] 即将执行: 待办 添加
已添加待办: 买菜(优先级 3)列表命令——局部前置/后置钩子包住输出,可选 --标签 被识别:
$ qi run 教程_快速开始.qi -- 列表 --标签 工作
--- 待办列表开始 ---
1. 写 qi-cli 教程(优先级 1)
2. 跑通示例(优先级 2)
(已按标签过滤: 工作)
--- 待办列表结束 ---两层命令树 配置 设置:
$ qi run 教程_快速开始.qi -- 配置 设置 主题 暗色
配置已更新: 主题 = 暗色自动生成的 --help(命令、选项、示例全部自动排版):
$ qi run 教程_快速开始.qi -- --help
演示 qi-cli 的根命令、子命令、局部/持久参数、别名与执行钩子。
Usage: 教程_快速开始 [OPTIONS] [COMMAND]
Commands:
添加 添加一条待办
列表 列出所有待办
配置 管理配置
help Print this message or the help of the given subcommand(s)
Options:
-v, --详细 输出详细日志
-h, --help Print help (see a summary with '-h')
-V, --version Print version
示例:
待办 添加 "写教程" --优先级 1
待办 列表 --标签 工作
待办 配置 设置 主题 暗色子命令也有自己的 --help,自动列出位置参数与默认值:
$ qi run 教程_快速开始.qi -- 添加 --help
添加一条待办
Usage: 教程_快速开始 添加 [OPTIONS] <标题>
Arguments:
<标题> 待办标题(位置参数)
Options:
-p, --优先级 <优先级> 1-5,越小越重要 [default: 3]
-v, --详细 输出详细日志
--紧急 标记为紧急
-h, --help Print help--version 直接来自 应用版本:
$ qi run 教程_快速开始.qi -- --version
待办 1.0.0必需参数缺失时,clap 自动报错并给出用法提示:
$ qi run 教程_快速开始.qi -- 添加
error: the following required arguments were not provided:
<标题>
Usage: 教程_快速开始 添加 <标题>
For more information, try '--help'.教程_快速开始 而非应用名 待办。这是底层 clap 默认用 argv[0] 作为用法名的行为,编译成独立可执行文件后会显示真实的程序名。命令、参数、钩子的功能完全不受影响。下一步
你已经掌握了 qi-cli 的全部核心积木:根命令、子命令、命令树、局部/持久参数、位置参数、别名、四种执行钩子,以及自动帮助/版本。把它们组合起来,足以搭出生产级的命令行工具。
- 仓库
qi-cli/examples/下有更复杂的真实示例:工具箱(命令树 + 钩子)、文件管理(IO + 加密)、HTTP客户端、待办(持久状态落盘)、系统信息、AI助手(集成 qi-harness 做 LLM agent)。 - 处理函数里可自由调用任意标准库:用
标准库.输入输出读写文件做持久化,用标准库.HTTP发请求,用标准库.JSON序列化配置。 - 写库代码时记得:保留字(如
结果、类型、选择)不能当变量名;链式 setter 一定要回写返回值。
奇语让你用纯中文关键字写命令行工具,而 qi-cli 让这件事既地道又结构清晰。开始动手吧。