Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# AGENTS.md — @ultime/cli

> 给在本仓库工作的 AI 与协作者:入口、可写 / 只读、红线与验收。
> 适用版本:0.1.x
> 适用版本:0.2.x

## 先读

Expand All @@ -24,6 +24,7 @@
## 红线

- **零领域依赖**:不 import 任何领域包;不内置具体命令。
- **不发 bin、不做命令发现**:每个产品包自带入口;本包不聚合、不扫描其他包的 bin。
- **不做 IO 策略**:读盘写盘与配置路径由命令自己决定。
- **不引第三方解析库**:`parseArgs` 保持零依赖。
- **API 变化同步**:`runCli` / `ParsedArgs` / `CliContext` 变化时同步 README、`SKILLS.md` 与测试。
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.2.0 - 2026-11-12

### Added

- `CliError` / `toErrorShape`:机器可读错误,带 `code` / `path` / `hint` / `details` / `exitCode`。
- `--json` 作为保留布尔 flag:错误以 `{ "error": { … } }` 只写 stderr。
- 未知命令给最近候选;单命令 help 缺省时按命令名生成。

### Changed

- 退出码语义:0 成功 / 1 命令执行失败 / 2 用法或配置错误;无命令与未知命令从 1 改为 2。
- 本包不发布 bin、不做命令发现;各产品包自带入口。

## 0.1.4 - 2026-11-10

- 构建改用 `vp pack`,产物同时生成 `dist/index.js` 与 `dist/index.d.ts`。
Expand Down
45 changes: 39 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,48 @@ process.exitCode = runCli(spec, process.argv.slice(2), processContext())

| 入口 | 提供 |
|---|---|
| `@ultime/cli` | `parseArgs` / `runCli` / `processContext` / `CliContext` / `CommandSpec` / `CliSpec` |
| `@ultime/cli` | `parseArgs` / `runCli` / `processContext` / `CliError` / `toErrorShape` / `CliContext` / `CliSpec` / `CommandSpec` |

`parseArgs` 只认 `command`、`--flag`、`--key value` / `--key=value`;`-h/--help` 与 `-v/--version` 是保留 flag。
`parseArgs` 只认 `command`、`--flag`、`--key value` / `--key=value`;`-h/--help`、`-v/--version` 与 `--json` 是保留 flag。

## 错误与退出码

命令用 `CliError` 抛机器可读错误;普通异常退化成 `code: 'ERROR'`:

```ts
import { CliError } from '@ultime/cli'

throw new CliError('缺少 --config', {
code: 'USAGE',
path: '--config',
hint: '补上 --config <path>',
})
```

| 情况 | 退出码 |
|---|---|
| 版本 / 帮助 | 0 |
| 命令返回的码 | 原样透传 |
| 命令抛普通异常 | 1,`code: 'ERROR'` |
| `CliError` | `exitCode`,默认 2(用法或配置错误) |
| 无命令、未知命令 | 2 |

- 未知命令给最近候选;没有候选时列出可用命令。
- `--json` 时错误只写 stderr:

```json
{ "error": { "code": "USAGE", "message": "缺少 --config", "path": "--config", "hint": "补上 --config <path>" } }
```

- 人类文案与 JSON 同源,由 `toErrorShape` 统一转换。

AI 入口:[`llms.txt`](./llms.txt)(路由)与 [`SKILLS.md`](./SKILLS.md)(能力表)随包发布。

## 边界

- **零领域依赖**:不 import 任何领域包,也不认识具体业务对象。
- **不引第三方解析 / 着色库**:解析用零依赖实现,着色直接用 ANSI;Node 内置的 `util.parseArgs` / `util.styleText` 是后续替换首选,等出现嵌套子命令或补全需求再评估 `commander` 一类工具。
- **不内置命令**:具体命令由调用方包注册,各自发布自己的 bin。
- **不引第三方解析 / 着色库**:解析用零依赖实现,着色直接用 ANSI。Node 内置 `util.parseArgs` 表达不了自由 `--key value`,暂不切换。
- **不内置命令、不发 bin**:具体命令由调用方包注册,各自发布自己的 bin;本包不做命令发现与聚合。
- **不做 IO 策略**:读盘写盘、配置路径由命令自己决定;本包只提供 `cwd` 与 `out/err`。

## 目录
Expand All @@ -59,14 +90,16 @@ AI 入口:[`llms.txt`](./llms.txt)(路由)与 [`SKILLS.md`](./SKILLS.md)
src/
args.ts 参数解析(纯函数)
context.ts 命令上下文(cwd / out / err)
error.ts 机器可读错误与统一转换
run.ts 命令注册、help、未知命令与退出码
index.ts 公共出口
tests/ 参数与运行时分发
tests/ 参数、运行时分发与包内容边界
```

## 版本与兼容

- 当前 `0.1.x`;0.x 阶段 API 仍可能调整。
- 当前 `0.2.x`;0.x 阶段 API 仍可能调整。
- 退出码语义:0 成功 / 1 命令执行失败 / 2 用法或配置错误;这是 0.2 起的新约定,升级时按本节目迁移。
- 破坏性变更写进 [CHANGELOG](./CHANGELOG.md),并在本节目说明迁移方式。
- 发布顺序:本包是基础设施,先于依赖它的包。

Expand Down
20 changes: 13 additions & 7 deletions SKILLS.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
# @ultime/cli · 可调用能力

> 定位:AI 可调用的通用命令设施。领域命令不在这里。
> 适用版本:0.1.x
> 同步基线:0.1.x(手写;API 变化时同批更新)
> 适用版本:0.2.x
> 同步基线:0.2.x(手写;API 变化时同批更新)

---

## 入口

```ts
import { parseArgs, runCli, processContext } from '@ultime/cli'
import type { ParsedArgs, CliContext, CliSpec, CommandSpec } from '@ultime/cli'
import { parseArgs, runCli, processContext, CliError } from '@ultime/cli'
import type { ParsedArgs, CliContext, CliSpec, CommandSpec, CliErrorOptions, CliErrorShape } from '@ultime/cli'
```

| 能力 | 输入 | 输出 |
|---|---|---|
| `parseArgs(argv)` | 字符串数组 | `{ command, flags, positional }` |
| `runCli(spec, argv, ctx?)` | `CliSpec` + 参数 | 退出码;版本 / 帮助 / 未知命令 / 异常由它处理 |
| `runCli(spec, argv, ctx?)` | `CliSpec` + 参数 | 退出码;版本 / 帮助 / 未知命令 / 错误由它处理 |
| `processContext()` | — | `{ cwd, out, err }` |
| `CliError(message, options)` | 文案 + `code` / `path` / `hint` / `details` / `exitCode` | 可被 `runCli` 统一输出的错误 |
| `toErrorShape(error)` | 任意异常 | `{ code, message, path?, hint?, details? }` |

## `CliSpec`

Expand All @@ -32,11 +34,15 @@ import type { ParsedArgs, CliContext, CliSpec, CommandSpec } from '@ultime/cli'

## 错误语义

- 未知命令:`err` 写错误,`out` 写 usage,返回 1;
- 命令抛异常:`err` 写 message,返回 1;
- 未知命令:`err` 写错误与候选,`out` 写 usage,返回 2;
- 无命令:`out` 写 usage,返回 2;
- 普通异常:`err` 写 message,`code: 'ERROR'`,返回 1;
- `CliError`:带 `code` / `path` / `hint`,返回 `exitCode`(默认 2);
- `--json`:错误以 `{ "error": { … } }` 只写 stderr;
- 版本 / 帮助:返回 0,不执行命令。

## 反例

- 把 `--check` 当成 cli 内置 flag:它属于调用方的命令。
- 在 cli 里读具体项目配置:IO 与配置语义属于调用方命令。
- 在 cli 里发现或聚合其他包的 bin:每个包自带入口,本包不发 bin。
5 changes: 3 additions & 2 deletions llms.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @ultime/cli · AI 索引

适用版本:0.1.x
适用版本:0.2.x

## 去哪找

Expand All @@ -9,8 +9,9 @@

## 机器入口

- 公共出口:`@ultime/cli` 的 `parseArgs` / `runCli` / `processContext` 与类型。
- 公共出口:`@ultime/cli` 的 `parseArgs` / `runCli` / `processContext` / `CliError` / `toErrorShape` 与类型。

## 边界

- 通用设施,不含任何领域命令;读盘写盘归调用方。
- 不发 bin、不做命令发现;每个产品包发布自己的入口。
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ultime/cli",
"version": "0.1.4",
"version": "0.2.0",
"description": "Ultime · cli:通用命令行设施(参数 / 命令注册 / 上下文 / 退出码),零领域依赖",
"keywords": [
"argument-parser",
Expand Down Expand Up @@ -41,8 +41,8 @@
},
"scripts": {
"build": "vp pack",
"check": "vp check && checks run && pnpm cycles",
"checks": "checks run",
"check": "vp check && ultime-checks run && pnpm cycles",
"checks": "ultime-checks run",
"cycles": "vp lint --import-plugin --deny=import/no-cycle src",
"test": "vp test",
"size": "size-limit --json",
Expand All @@ -53,7 +53,7 @@
"@arethetypeswrong/core": "^0.18.5",
"@size-limit/preset-small-lib": "^14.3.0",
"@types/node": "^22.20.5",
"@ultime/checks": "^0.2.0",
"@ultime/checks": "^0.3.0",
"publint": "^0.3.25",
"size-limit": "^14.3.0",
"typescript": "^5.9.3",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ minimumReleaseAgeExclude:
- '@voidzero-dev/vite-plus-win32-arm64-msvc@1.5.1'
- '@voidzero-dev/vite-plus-win32-x64-msvc@1.5.1'
- vite-plus@1.5.1
- '@ultime/checks@0.1.1 || 0.2.0'
- '@ultime/checks@0.1.1'
- '@ultime/checks@0.2.0'
- '@ultime/checks@0.3.0'
12 changes: 7 additions & 5 deletions rules/01-architecture.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 01 · 架构与依赖

> 定位:CLI 设施的职责、依赖方向与目录分工。
> 适用版本:0.1.x
> 适用版本:0.2.x
> 关系:规则索引见 [`README`](./README.md);运行协议见 [`02-runner`](./02-runner.md)。

---
Expand All @@ -10,26 +10,28 @@

| 做 | 不做 |
|---|---|
| 参数解析、help、版本、未知命令与退出码 | 定义任何领域命令 |
| 参数解析、help、版本、未知命令、退出码与错误模型 | 定义任何领域命令 |
| 命令注册与分发 | 读配置、写产物、判断配置合法性 |
| 提供 `cwd` 与 `out/err` 上下文 | 决定日志格式、文件布局或 IO 策略 |
| 提供 `CliError` / `toErrorShape` 与 `--json` 错误输出 | 发布 bin、发现或聚合其他包的命令 |

依赖方向:

```text
调用方(任何上层包) → @ultime/cli
```

- 本包**不 import** 任何调用方包。
- 调用方注册命令;本包只负责通用流程。
- 本包**不 import** 任何调用方包,也不持有领域包清单。
- 各产品包自带 bin;本包只负责通用流程。

## 目录

| 路径 | 职责 |
|---|---|
| `src/args.ts` | `command` / `--flag` / `--key value` 解析 |
| `src/context.ts` | `CliContext` 与进程实现 |
| `src/run.ts` | `runCli`:版本 / 帮助 / 未知命令 / 异常转退出码 |
| `src/error.ts` | `CliError` / `toErrorShape` |
| `src/run.ts` | `runCli`:版本 / 帮助 / 未知命令 / 错误转退出码 |
| `src/index.ts` | 公共出口 |

## 反例
Expand Down
27 changes: 22 additions & 5 deletions rules/02-runner.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 02 · 参数、命令与退出码

> 定位:`runCli` 的稳定协议。
> 适用版本:0.1.x
> 适用版本:0.2.x
> 关系:架构见 [`01-architecture`](./01-architecture.md)。

---
Expand All @@ -14,7 +14,7 @@
- `--flag`:布尔;
- `--key value` / `--key=value`:带值。

保留 flag:`-h/--help`、`-v/--version`。其余 flag 原样交给命令解释。
保留 flag:`-h/--help`、`-v/--version` 与 `--json`。`--json` 固定按布尔解析,只表达输出形态;其余 flag 原样交给命令解释。

## 命令注册

Expand All @@ -29,14 +29,31 @@ runCli({
}, argv, ctx)
```

`help` 缺省时生成 `<name> <command> [options]`。

## 错误模型

命令抛 `CliError`,字段全部可选(`code` 必填):

| 字段 | 用途 |
|---|---|
| `code` | 稳定错误码,例如 `USAGE` / `CONFIG`;上层可追加 |
| `path` | 出错位置,字段路径或参数名 |
| `hint` | 可执行的修复建议 |
| `details` | 附加诊断数据,原样进入 JSON |
| `exitCode` | 默认 2;业务失败可覆盖为 1 |

普通异常没有字段信息,统一成 `{ code: 'ERROR', message }`。

## 退出码

| 情况 | 退出码 |
|---|---|
| 版本 / 帮助 | 0 |
| 无命令、未知命令 | 1 |
| 命令返回的码 | 原样透传 |
| 命令抛异常 | 1,错误写入 `ctx.err` |
| 命令抛普通异常 | 1,`code: 'ERROR'` |
| `CliError` | `exitCode`,默认 2 |
| 无命令、未知命令 | 2 |

- 未知命令先写 `err`,再输出 usage 到 `out`。
- 未知命令先写 `err`(带最近候选),再输出 usage 到 `out`;`--json` 时不写 stdout。
- 命令自己决定业务错误码;本包不定义领域错误。
2 changes: 1 addition & 1 deletion rules/03-testing.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 03 · 测试与验收

> 定位:改 CLI 设施时跑什么、什么算通过。
> 适用版本:0.1.x
> 适用版本:0.2.x
> 关系:协议见 [`02-runner`](./02-runner.md);发布检查见 [`04-release`](./04-release.md)。

---
Expand Down
2 changes: 1 addition & 1 deletion rules/04-release.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 04 · 版本与发布

> 定位:CLI 设施的编译 ESM 形态、发布顺序与检查。
> 适用版本:0.1.x
> 适用版本:0.2.x
> 关系:测试见 [`03-testing`](./03-testing.md)。

---
Expand Down
2 changes: 1 addition & 1 deletion rules/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# cli · 开发规则

> 定位:本包开发规范的索引。只写判断、边界与反例;API 事实以 README 为准。
> 适用版本:0.1.x
> 适用版本:0.2.x
> owner:维护者(repo owner)· 复核:2026-11-11 · 到期降级为参考
> 关系:入口见 [`AGENTS.md`](../AGENTS.md);怎么用见 [`README.md`](../README.md)。

Expand Down
8 changes: 8 additions & 0 deletions src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* ---------------------------------------------------------------------------
* 只认三种形态:`command`、`--flag`、`--key value` / `--key=value`;
* 不引第三方解析库 —— CLI 必须零依赖可跑。
* `--json` 是通用输出开关,固定按布尔解析,不吞后面的命令名。
*/

export interface ParsedArgs {
Expand All @@ -11,6 +12,9 @@ export interface ParsedArgs {
positional: string[]
}

/** 通用布尔 flag:只表达输出形态,不承载业务语义。 */
const BOOLEAN_FLAGS = new Set(['json'])

export function parseArgs(argv: string[]): ParsedArgs {
const flags: Record<string, string | true> = {}
const positional: string[] = []
Expand All @@ -33,6 +37,10 @@ export function parseArgs(argv: string[]): ParsedArgs {
flags[body.slice(0, equal)] = body.slice(equal + 1)
continue
}
if (BOOLEAN_FLAGS.has(body)) {
flags[body] = true
continue
}
const next = argv[i + 1]
if (next && !next.startsWith('-')) {
flags[body] = next
Expand Down
Loading
Loading