通用命令行设施:参数解析、命令注册、上下文与退出码。零领域依赖,不内置任何具体命令。
pnpm add @ultime/cli需要 Node >= 22.18;包发的是编译后的 ESM(dist/),类型来自 dist/*.d.ts。
包作者只写自己的命令,参数、help、未知命令与退出码交给本包:
import { flagBool, flagValue, processContext, runCli } from '@ultime/cli'
const spec = {
name: 'my-cli',
version: '0.1.0',
usage: 'my-cli <command>',
commands: {
build: {
help: 'my-cli build [--check] [--config <path>]',
flags: { check: 'boolean', config: 'string' },
run: (args, ctx) => {
ctx.out(`check=${flagBool(args, 'check')} config=${flagValue(args, 'config') ?? '-'}`)
return 0
},
},
},
}
process.exitCode = await runCli(spec, process.argv.slice(2), processContext())命令可以返回 number,也可以返回 Promise<number>;runCli 会等待结果。
| 入口 | 提供 |
|---|---|
@ultime/cli |
parseArgs / flagBool / flagValue / runCli / reportError / processContext / CliError / toErrorShape / exitCodeOf / EXIT_OK / EXIT_FAIL / EXIT_USAGE / CliContext / CliSpec / CommandSpec / FlagType / FlagSpec / ParseOptions / ParsedArgs / CliErrorOptions / CliErrorShape |
parseArgs 只认 command、--flag、--key value / --key=value;-h/--help、-v/--version 与 --json 是保留 flag。-- 之后全部按 positional 处理,--key "" 保留空字符串。
给命令加 flags 后,参数语义由声明决定,未知参数会在执行前被拒绝:
commands: {
deploy: {
flags: { 'dry-run': 'boolean', config: 'string' },
run: (args, ctx) => 0,
},
}boolean:永不消费下一个 token,只接受--dry-run;string:消费下一个 token,支持--config path/--config=path/--config '';- 未声明:传了 schema 时按布尔解析,再由
runCli以USAGE拒绝;不传 schema 时保持旧的自由解析; - 同一 flag 在所有命令中的类型必须一致;命令要么全部声明
flags,要么全部省略。
命令面有默认动作时(例如检查工具的 run),在 CliSpec 设置 defaultCommand:
const spec = {
name: 'my-cli',
version: '0.1.0',
usage: 'my-cli [command]',
defaultCommand: 'run',
commands: {
run: { flags: {}, run: (args, ctx) => 0 },
},
}无命令时执行默认命令;设置默认命令后,未注册的首个 token 也作为它的 positional(显式注册的命令优先),不再报未知命令。有默认命令时 -- 之后全部作为 positional。--help / --version 仍走全局短路,不会误执行默认命令。
命令用 CliError 抛机器可读错误;普通异常退化成 code: 'ERROR':
import { CliError } from '@ultime/cli'
throw new CliError('缺少 --config', {
code: 'USAGE',
path: '--config',
hint: '补上 --config <path>',
})| 情况 | 退出码 |
|---|---|
| 版本 / 帮助 | 0 |
| 命令返回的码 | 原样透传 |
| 命令抛普通异常 | 1,code: 'ERROR' |
CliError |
exitCode,默认 2(用法或配置错误) |
| 未知命令、未知参数、布尔参数带值、带值参数缺值 | 2 |
runCli 是异步的:版本 / 帮助 / 无命令等分支也会返回 Promise<number>。
- 未知命令给最近候选;没有候选时列出可用命令。
--json时错误只写 stderr:
{ "error": { "code": "USAGE", "message": "缺少 --config", "path": "--config", "hint": "补上 --config <path>" } }- 人类文案与 JSON 同源,由
toErrorShape统一转换。
AI 入口:llms.txt(路由)与 SKILLS.md(能力表)随包发布。
- 零领域依赖:不 import 任何领域包,也不认识具体业务对象。
- 不引第三方解析 / 着色库:解析用零依赖实现,着色直接用 ANSI。Node 内置
util.parseArgs表达不了自由--key value与命令级 flag schema,暂不切换。 - 不内置命令、不发 bin:具体命令由调用方包注册,各自发布自己的 bin;本包不做命令发现与聚合。
- 不做 IO 策略:读盘写盘、配置路径由命令自己决定;本包只提供
cwd与out/err。
src/
args.ts 参数解析(纯函数)、flag schema 与读取
context.ts 命令上下文(cwd / out / err)
error.ts 机器可读错误、退出码与统一转换
run.ts 命令注册、flags 校验、help、未知命令与退出码
index.ts 公共出口
tests/ 参数、运行时、声明校验与包内容边界
- 当前
0.4.x;0.x 阶段 API 仍可能调整。 - 退出码语义:0 成功 / 1 命令执行失败 / 2 用法或配置错误;这是 0.2 起的新约定,升级时按本节目迁移。
- 0.4 新增
CliSpec.defaultCommand:无命令时执行默认命令;未注册的首个 token 转为它的 positional,显式命令优先;有默认命令时--之后全部按 positional 处理。 - 0.3 的破坏性变更:
runCli改为异步;声明flags后未知参数与缺值会被拒绝。迁移方式:入口改成process.exitCode = await runCli(...),并给命令补flags;不补flags的命令仍按旧规则解析。 - 破坏性变更写进 CHANGELOG,并在本节目说明迁移方式。
- 发布顺序:本包是基础设施,先于依赖它的包。
MIT,见 LICENSE。