@agenzo/merchant-cli 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,197 +1,197 @@
1
1
  # @agenzo/merchant-cli
2
2
 
3
- > **商户履约产品线 CLI** — binary `agenzo-merchant-cli`,落地于 monorepo `agenzo-cli/apps/merchant-cli`。
4
- > 覆盖两个 noun group:能力发现(`services`)与叫车业务闭环(`ride-elife`),共 7 条命令。
3
+ > **Merchant-fulfillment product-line CLI** — binary `agenzo-merchant-cli`, lives in the monorepo `agenzo-cli/apps/merchant-cli`.
4
+ > Covers two noun groups: capability discovery (`services`) and the ride-hailing business loop (`ride-elife`), 7 commands in total.
5
5
 
6
6
  ---
7
7
 
8
- ## 项目简介
8
+ ## Overview
9
9
 
10
- `agenzo-merchant-cli` 是 Agent 接入 Agenzo 商户履约能力的命令行入口。本期上线两个 noun group:
10
+ `agenzo-merchant-cli` is the command-line entry point for Agents to access Agenzo's merchant-fulfillment capabilities. This iteration ships two noun groups:
11
11
 
12
- - **`services`**(能力发现):让 Agent 发现可用的商户履约能力(该调哪个 noun/verb、调用流程)。本期数据源为 **CLI 内置注册表**(后端 `/services` discovery endpoint 属 pending impl)。
13
- - **`ride-elife`**(叫车,唯一在线 provider):报价 → 下单 → 查询/轮询 → 取消 → 列单的完整闭环。
12
+ - **`services`** (capability discovery): lets an Agent discover available merchant-fulfillment capabilities (which noun/verb to call, and the call flow). The data source this iteration is the **CLI-bundled registry** (the backend `/services` discovery endpoint is pending impl).
13
+ - **`ride-elife`** (ride hailing, the only online provider): the full loop of quote → book → query/poll → cancel → list-orders.
14
14
 
15
- 所有通用基础设施(HTTP 客户端、输出渲染、错误体系、退出码、版本协商、配置)均复用 `@agenzo/cli-core`,app 内只保留命令处理与商户域专属逻辑(ride body 组装、NDJSON watch、verb-schema help、services 注册表、幂等键策略)。
15
+ All shared infrastructure (HTTP client, output rendering, error system, exit codes, version negotiation, config) is reused from `@agenzo/cli-core`; the app only keeps command handling and merchant-domain-specific logic (ride body assembly, NDJSON watch, verb-schema help, services registry, idempotency-key policy).
16
16
 
17
- > 本 CLI 与 `agenzo-admin-cli` / `agenzo-token-cli` 结构、命名、构建配置保持一致(4-CLI consistency)。
17
+ > This CLI keeps its structure, naming, and build configuration consistent with `agenzo-admin-cli` / `agenzo-token-cli` (4-CLI consistency).
18
18
 
19
- ## 功能特性
19
+ ## Features
20
20
 
21
- | Noun | Verb | 类型 | HTTP | 说明 |
21
+ | Noun | Verb | Type | HTTP | Description |
22
22
  |---|---|---|---|---|
23
- | `services` | `list` | Read | —(内置注册表) | 列出可用商户履约能力 |
24
- | `services` | `get <service-id>` | Read | —(内置注册表) | 查看单个能力完整元数据(命中失败 → `SERVICE_NOT_FOUND`) |
25
- | `ride-elife` | `quote` | Read | `POST /ride/quote` | 两点间报价(`vehicle_classes[]` + meet-and-greet) |
26
- | `ride-elife` | `book` | Write/Y | `POST /ride/book` | 按 quote 下单(monthly_settlement-aware,见下) |
27
- | `ride-elife` | `get` | Read | `GET /ride/<id>/status` | 查询订单状态;`--watch` 输出 NDJSON 轮询流 |
28
- | `ride-elife` | `cancel` | Write/Y | `POST /ride/<id>/cancel` | 取消订单(无 body,可能产生费用) |
29
- | `ride-elife` | `list-orders` | Read | `GET /ride/orders` | 列出已下单的行程(分页 + 过滤) |
23
+ | `services` | `list` | Read | — (bundled registry) | List available merchant-fulfillment capabilities |
24
+ | `services` | `get <service-id>` | Read | — (bundled registry) | View the full metadata of a single capability (miss → `SERVICE_NOT_FOUND`) |
25
+ | `ride-elife` | `quote` | Read | `POST /ride/quote` | Point-to-point quote (`vehicle_classes[]` + meet-and-greet) |
26
+ | `ride-elife` | `book` | Write/Y | `POST /ride/book` | Book against a quote (monthly_settlement-aware, see below) |
27
+ | `ride-elife` | `get` | Read | `GET /ride/<id>/status` | Query order status; `--watch` emits an NDJSON polling stream |
28
+ | `ride-elife` | `cancel` | Write/Y | `POST /ride/<id>/cancel` | Cancel an order (no body, may incur a fee) |
29
+ | `ride-elife` | `list-orders` | Read | `GET /ride/orders` | List booked trips (pagination + filtering) |
30
30
 
31
- > 写命令(`book` / `cancel`)受幂等键规则约束(见 [幂等键](#幂等键-idempotency-key))。
32
- > 未实现:`ride-elife track`(pending impl,后端无位置流接口)——`get --watch` 已覆盖订单状态轮询。
31
+ > Write commands (`book` / `cancel`) are subject to the idempotency-key rules (see [Idempotency-Key](#idempotency-key)).
32
+ > Not implemented: `ride-elife track` (pending impl, the backend has no location-stream interface) — `get --watch` already covers order-status polling.
33
33
 
34
- ## 技术栈
34
+ ## Tech Stack
35
35
 
36
- - TypeScript(ESM) + [commander@14](https://github.com/tj/commander.js) + [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js)
37
- - 构建:[tsup](https://tsup.egoist.dev/)(esm / node18 / dts,banner `#!/usr/bin/env node`)
38
- - 测试:[vitest](https://vitest.dev/) + [fast-check](https://fast-check.dev/)(PBT)
39
- - 共享底包:`@agenzo/cli-core`(唯一横向依赖;**禁止** import 其它 app)
36
+ - TypeScript (ESM) + [commander@14](https://github.com/tj/commander.js) + [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js)
37
+ - Build: [tsup](https://tsup.egoist.dev/) (esm / node18 / dts, banner `#!/usr/bin/env node`)
38
+ - Testing: [vitest](https://vitest.dev/) + [fast-check](https://fast-check.dev/) (PBT)
39
+ - Shared base package: `@agenzo/cli-core` (the only horizontal dependency; importing other apps is **forbidden**)
40
40
 
41
- ## 快速开始
41
+ ## Quick Start
42
42
 
43
- ### 环境要求
43
+ ### Requirements
44
44
 
45
45
  - Node.js 18+
46
- - monorepo 已 `npm install`
46
+ - `npm install` has been run in the monorepo
47
47
 
48
- ### 构建
48
+ ### Build
49
49
 
50
50
  ```bash
51
- # 在 monorepo 根目录
52
- npm run build -w @agenzo/cli-core # 必须先构建共享底包
53
- npm run build -w @agenzo/merchant-cli # tsup 产出 dist/index.js(= bin agenzo-merchant-cli)
51
+ # in the monorepo root
52
+ npm run build -w @agenzo/cli-core # must build the shared base package first
53
+ npm run build -w @agenzo/merchant-cli # tsup produces dist/index.js (= bin agenzo-merchant-cli)
54
54
  agenzo-merchant-cli --version
55
55
  ```
56
56
 
57
- ### 认证
57
+ ### Authentication
58
58
 
59
- 运行面 CLI,使用 **API Key**(`--api-key`,作为 `X-Api-Key` 头逐命令携带):
59
+ A runtime-plane CLI that uses an **API Key** (`--api-key`, carried per command as the `X-Api-Key` header):
60
60
 
61
- - API Key 由 `agenzo-admin-cli keys create --scope merchant` 签发,需含 `merchant` scope。
62
- - 缺省 `--api-key` 时交互索取(password 输入)。
63
- - 无 Bearer Token / AuthService / keystore——与 `agenzo-token-cli` 一致。
61
+ - The API Key is issued by `agenzo-admin-cli keys create --scope merchant` and must include the `merchant` scope.
62
+ - When `--api-key` is omitted, it is requested interactively (password input).
63
+ - No Bearer Token / AuthService / keystore — consistent with `agenzo-token-cli`.
64
64
 
65
- ### 端到端示例
65
+ ### End-to-end example
66
66
 
67
67
  ```bash
68
- # 1. 发现能力
68
+ # 1. Discover capabilities
69
69
  agenzo-merchant-cli services list
70
70
  agenzo-merchant-cli services get ride-elife
71
71
 
72
- # 2. 报价(产出 quote_id)
72
+ # 2. Quote (produces quote_id)
73
73
  agenzo-merchant-cli ride-elife quote --api-key "$API_KEY" \
74
74
  --pickup-lat 37.7937 --pickup-lng -122.3956 --pickup-name "1 Market St" \
75
75
  --dropoff-lat 37.6213 --dropoff-lng -122.3790 --dropoff-name "SFO Airport" \
76
76
  --pickup-time now
77
77
 
78
- # 3. 下单(写操作:--yes 自动确认 + 必须提供 --idempotency-key)
78
+ # 3. Book (write op: --yes auto-confirms + --idempotency-key must be provided)
79
79
  agenzo-merchant-cli ride-elife book --api-key "$API_KEY" --yes \
80
80
  --quote-id "$QUOTE_ID" --vehicle-class Sedan --price-amount 42.50 \
81
81
  --passenger-name "Alice" --passenger-phone "+14155551234" \
82
82
  --idempotency-key "book-$(date +%s)"
83
83
 
84
- # 4. 查询 / 轮询(--watch 输出逐行 NDJSON)
84
+ # 4. Query / poll (--watch emits line-by-line NDJSON)
85
85
  agenzo-merchant-cli ride-elife get --api-key "$API_KEY" --order-id "$RIDE_ID"
86
86
  agenzo-merchant-cli ride-elife get --api-key "$API_KEY" --order-id "$RIDE_ID" \
87
87
  --watch --watch-interval 5 --watch-timeout 600
88
88
 
89
- # 5. 列单 / 取消
89
+ # 5. List orders / cancel
90
90
  agenzo-merchant-cli ride-elife list-orders --api-key "$API_KEY" --status Pending --page 1 --page-size 20
91
91
  agenzo-merchant-cli ride-elife cancel --api-key "$API_KEY" --yes \
92
92
  --order-id "$RIDE_ID" --idempotency-key "cancel-$(date +%s)"
93
93
  ```
94
94
 
95
- ## 全局 flag
95
+ ## Global flags
96
96
 
97
- | Flag | 默认 | 说明 |
97
+ | Flag | Default | Description |
98
98
  |---|---|---|
99
- | `--format <json\|table>` | **`json`** | 输出格式。merchant-cli 是 agent-first 入口,**默认 `json`**(刻意偏离 cli-core 的 `table` 默认,D2);亦读环境变量 `AGENZO_FORMAT` |
100
- | `--api-key <key>` | —(交互索取) | API Key,作为 `X-Api-Key` 头逐命令携带 |
101
- | `--yes` | `false` | 跳过交互式确认(供 CI / Agent);写命令在 `--yes` 且缺幂等键时为硬错误 |
102
- | `--verbose` | `false` | 详细日志(写 stderr) |
99
+ | `--format <json\|table>` | **`json`** | Output format. merchant-cli is an agent-first entry point, so it **defaults to `json`** (a deliberate deviation from cli-core's `table` default, D2); also reads the `AGENZO_FORMAT` environment variable |
100
+ | `--api-key <key>` | — (requested interactively) | API Key, carried per command as the `X-Api-Key` header |
101
+ | `--yes` | `false` | Skip interactive confirmation (for CI / Agent); for write commands, `--yes` with a missing idempotency key is a hard error |
102
+ | `--verbose` | `false` | Verbose logging (written to stderr) |
103
103
 
104
- ## 输出与错误契约
104
+ ## Output and error contract
105
105
 
106
- - **`--format json`(非 watch 命令)**:stdout 仅含**单个合法 JSON**(业务 payload + `profile`/`endpoint` 信封,endpoint 仅 host 不含 path);所有状态/进度/spinner 行写 stderr,且在 json 模式下静音。
107
- - **`--format table`**:业务输出走 stdout,状态行/spinner 走 stderr。
108
- - **`get --watch`**:stdout 为逐行 NDJSON 流,**不套** profile/endpoint 信封;轮询命中终态集合(`At destination` / `Cancelled` / `Rejected` / `Customer no show` / `Driver no show`,大小写敏感)或超时停止;超时末行为 `{ "watch_status": "timeout", ... }`。
109
- - **错误信封**(cli-core §8):`json` → `{ "error": { "code", "code_num", "message", "request_id"? } }`;`table` → `✗ [<code_num>] <message>`。
106
+ - **`--format json` (non-watch commands)**: stdout contains only a **single valid JSON** (business payload + `profile`/`endpoint` envelope, where endpoint is host-only without a path); all status/progress/spinner lines go to stderr and are silenced in json mode.
107
+ - **`--format table`**: business output goes to stdout, status lines/spinner go to stderr.
108
+ - **`get --watch`**: stdout is a line-by-line NDJSON stream, **not wrapped** in the profile/endpoint envelope; polling stops when it hits the terminal-status set (`At destination` / `Cancelled` / `Rejected` / `Customer no show` / `Driver no show`, case-sensitive) or times out; on timeout the final line is `{ "watch_status": "timeout", ... }`.
109
+ - **Error envelope** (cli-core §8): `json` → `{ "error": { "code", "code_num", "message", "request_id"? } }`; `table` → `✗ [<code_num>] <message>`.
110
110
 
111
- 退出码(由 cli-core `exitCodeFor` 映射):
111
+ Exit codes (mapped by cli-core's `exitCodeFor`):
112
112
 
113
- | 退出码 | 含义 | 代表错误码 |
113
+ | Exit code | Meaning | Representative error codes |
114
114
  |---|---|---|
115
- | `0` | 成功 | — |
116
- | `1` | 业务 / 参数 | `PARAM_INVALID`(2101) · `PARAM_IDEMPOTENCY_KEY_REQUIRED`(2102) · `SERVICE_NOT_FOUND`(4101) · `VEHICLE_UNAVAILABLE`(4201) · `QUOTE_EXPIRED`(4202) · `BOOKING_FAILED`(4203) · `CANCELLATION_NOT_ALLOWED`(4204) · `BILLING_MODE_MISMATCH`(3001) · `ACCOUNT_*`(31xx) · `PAYMENT_ORDER_*`(32xx) |
117
- | `2` | 需升级 | `UPGRADE_REQUIRED`(9008) |
118
- | `3` | 认证 / 无效 key | `KEY_INVALID`(1101) · `KEY_SCOPE_DENIED`(1102) |
119
- | `4` | 网络 / 5xx | `RATE_LIMITED`(5001) · `UPSTREAM_ERROR`(5101) · `INTERNAL_ERROR`(5201) |
120
- | `5` | 用户取消 | `CLIENT_ABORTED`(9007) · SIGINT |
115
+ | `0` | Success | — |
116
+ | `1` | Business / param | `PARAM_INVALID`(2101) · `PARAM_IDEMPOTENCY_KEY_REQUIRED`(2102) · `SERVICE_NOT_FOUND`(4101) · `VEHICLE_UNAVAILABLE`(4201) · `QUOTE_EXPIRED`(4202) · `BOOKING_FAILED`(4203) · `CANCELLATION_NOT_ALLOWED`(4204) · `BILLING_MODE_MISMATCH`(3001) · `ACCOUNT_*`(31xx) · `PAYMENT_ORDER_*`(32xx) |
117
+ | `2` | Upgrade required | `UPGRADE_REQUIRED`(9008) |
118
+ | `3` | Auth / invalid key | `KEY_INVALID`(1101) · `KEY_SCOPE_DENIED`(1102) |
119
+ | `4` | Network / 5xx | `RATE_LIMITED`(5001) · `UPSTREAM_ERROR`(5101) · `INTERNAL_ERROR`(5201) |
120
+ | `5` | User cancel | `CLIENT_ABORTED`(9007) · SIGINT |
121
121
 
122
- > api-key 鉴权映射:HTTP 401 → `KEY_INVALID`、403 → `KEY_SCOPE_DENIED`。后端返回的已知 §8 字符串码(如 `QUOTE_EXPIRED`)优先保真,否则回退 HTTP-status 映射。
122
+ > api-key auth mapping: HTTP 401 → `KEY_INVALID`, 403 → `KEY_SCOPE_DENIED`. A known §8 string code returned by the backend (e.g. `QUOTE_EXPIRED`) takes priority and is preserved; otherwise it falls back to the HTTP-status mapping.
123
123
 
124
- ## 幂等键 (Idempotency-Key)
124
+ ## Idempotency-Key
125
125
 
126
- 写命令 `ride-elife book` / `cancel` 接受 `--idempotency-key`:
126
+ The write commands `ride-elife book` / `cancel` accept `--idempotency-key`:
127
127
 
128
- - 格式:`[A-Za-z0-9_-]{1,128}`。
129
- - **CLI 永不自动生成**:`--yes` 且缺键 → 硬错误 `PARAM_IDEMPOTENCY_KEY_REQUIRED`(exit 1),不发起请求;非 `--yes` 缺键 → 交互索取。
130
- - 作为 `Idempotency-Key` HTTP **头**发送,**绝不进 body**。
131
- - 同一逻辑请求重试用同一键;新请求用新键。
128
+ - Format: `[A-Za-z0-9_-]{1,128}`.
129
+ - **The CLI never auto-generates one**: `--yes` with a missing key → hard error `PARAM_IDEMPOTENCY_KEY_REQUIRED` (exit 1), and no request is sent; a missing key without `--yes` → requested interactively.
130
+ - Sent as the `Idempotency-Key` HTTP **header**, **never in the body**.
131
+ - Reuse the same key to retry the same logical request; use a new key for a new request.
132
132
 
133
- ## book 的计费模型(monthly_settlement-aware)
133
+ ## Billing model for book (monthly_settlement-aware)
134
134
 
135
- `ride-elife book` **不接受** `--payment-method-id` 或任何卡信息——商户域不持有支付句柄。funding 由后端按 Developer 的 `billing_mode` 决定:
135
+ `ride-elife book` **does not accept** `--payment-method-id` or any card information — the merchant domain holds no payment handle. Funding is decided by the backend according to the Developer's `billing_mode`:
136
136
 
137
- - **monthly_settlement**:从月结账户扣减,无支付句柄;响应 `payment_status=ON_ACCOUNT`,含 `billing_entry_id`。
138
- - **pay_per_call**:可选透传 `--payment-order-id`(payment-cli 已扣款的 PAID 订单号);响应回显 `payment_order_id`。
137
+ - **monthly_settlement**: deducted from the monthly-settlement account, no payment handle; the response has `payment_status=ON_ACCOUNT` and includes `billing_entry_id`.
138
+ - **pay_per_call**: optionally pass through `--payment-order-id` (a PAID order number charged separately, out-of-band); the response echoes `payment_order_id`.
139
139
 
140
- 请求 body 至多含可选 `payment_order_id`。
140
+ The request body contains at most an optional `payment_order_id`.
141
141
 
142
- ## profile / host 模型
142
+ ## profile / host model
143
143
 
144
- merchant-cli **没有任何 host / config 命令**,不治理环境。它复用 `@agenzo/cli-core` 的 `ConfigManager` 默认配置(即 `agenzo-admin-cli` 统一治理的环境,默认落盘 `~/.agenzo-admin-cli/`):
144
+ merchant-cli **has no host / config commands** and does not govern environments. It reuses `@agenzo/cli-core`'s `ConfigManager` default configuration (i.e. the environment governed centrally by `agenzo-admin-cli`, persisted by default to `~/.agenzo-admin-cli/`):
145
145
 
146
- - 为 `ApiClient` 提供 `baseUrl`(host + `/api/merchant/v1`)。
147
- - 为 json 输出的 `profile` / `endpoint` 信封提供环境名 + host。
146
+ - Provides the `baseUrl` for `ApiClient` (host + `/api/merchant/v1`).
147
+ - Provides the environment name + host for the `profile` / `endpoint` envelope in json output.
148
148
 
149
- **环境治理(设置 host / 切换环境)归 `agenzo-admin-cli`**(`agenzo-admin-cli config set` / `set-host`)——与 `agenzo-token-cli` 一致。
149
+ **Environment governance (setting the host / switching environments) belongs to `agenzo-admin-cli`** (`agenzo-admin-cli config set` / `set-host`) — consistent with `agenzo-token-cli`.
150
150
 
151
- ## 机器可读的 verb schema
151
+ ## Machine-readable verb schema
152
152
 
153
- 每个 `ride-elife` verb 支持 `--help --format json`,输出该 verb 的机器可读 schema(`cli` / `noun` / `verb` / `description` / `flags` / `response` / `example`,部分含 `error_recovery` / `polling`):
153
+ Every `ride-elife` verb supports `--help --format json`, emitting that verb's machine-readable schema (`cli` / `noun` / `verb` / `description` / `flags` / `response` / `example`, some with `error_recovery` / `polling`):
154
154
 
155
155
  ```bash
156
156
  agenzo-merchant-cli ride-elife quote --help --format json
157
157
  agenzo-merchant-cli ride-elife book --help --format json
158
158
  ```
159
159
 
160
- ## 开发
160
+ ## Development
161
161
 
162
162
  ```bash
163
- npm run build -w @agenzo/merchant-cli # tsup 构建
164
- npm run test -w @agenzo/merchant-cli # vitest(含 fast-check PBT)
163
+ npm run build -w @agenzo/merchant-cli # tsup build
164
+ npm run test -w @agenzo/merchant-cli # vitest (incl. fast-check PBT)
165
165
  ```
166
166
 
167
- 源码结构:
167
+ Source structure:
168
168
 
169
169
  ```text
170
170
  apps/merchant-cli/
171
171
  src/
172
- index.ts # commander 装配 + 顶层 reportError + SIGINT
173
- services/{list,get}.ts # 能力发现(本期:CLI 内置注册表)
174
- services/registry.ts # 内置静态注册表(商户域)
172
+ index.ts # commander assembly + top-level reportError + SIGINT
173
+ services/{list,get}.ts # capability discovery (this iteration: CLI-bundled registry)
174
+ services/registry.ts # bundled static registry (merchant domain)
175
175
  ride-elife/{quote,book,get,cancel,list-orders}.ts
176
- ride-elife/watch.ts # NDJSON 轮询引擎(商户域)
177
- verb-schema.ts # --help --format json 的 verb schema(商户域)
178
- idempotency.ts # 幂等键 resolve / normalize(商户域)
179
- types/api.ts # ride/service 响应类型(仅 merchant 消费,不提升 cli-core)
180
- docs/ # 测试设计文档(见下)
181
- tests/ # vitest 单测 + PBT
176
+ ride-elife/watch.ts # NDJSON polling engine (merchant domain)
177
+ verb-schema.ts # verb schema for --help --format json (merchant domain)
178
+ idempotency.ts # idempotency key resolve / normalize (merchant domain)
179
+ types/api.ts # ride/service response types (consumed by merchant only, not promoted to cli-core)
180
+ docs/ # test design docs (see below)
181
+ tests/ # vitest unit tests + PBT
182
182
  ```
183
183
 
184
- ## 文档索引
184
+ ## Documentation index
185
185
 
186
- - [测试设计文档(命令矩阵 v1)](docs/test-design-command-matrix-v1.md) — 7 条命令 × 需求/Property 覆盖矩阵、L1–L4 分层测试策略、L1 单元 / L3 命令级用例表、L4 手动可执行冒烟用例。
187
- - 架构与规范:`architecture-upgrade/v1/`(cli-standard / cli-design §4+§8 / cli-guide)。
188
- - monorepo 总览:[`agenzo-cli/README.md`](../../README.md)。
186
+ - [Test design doc (command matrix v1)](docs/test-design-command-matrix-v1.md) — a 7-command × requirement/Property coverage matrix, the L1–L4 layered testing strategy, L1 unit / L3 command-level case tables, and L4 manually executable smoke cases.
187
+ - Architecture and standards: `architecture-upgrade/v1/` (cli-standard / cli-design §4+§8 / cli-guide).
188
+ - monorepo overview: [`agenzo-cli/README.md`](../../README.md).
189
189
 
190
- ## 更新日志
190
+ ## Changelog
191
191
 
192
192
  ### 2026-06
193
193
 
194
- - 于 monorepo `agenzo-cli/apps/merchant-cli` 落地 merchant-cli(按 cli-design §4 重写,复用 `@agenzo/cli-core`)。
195
- - 上线 `services`(list / get)+ `ride-elife`(quote / book / get [+`--watch`] / cancel / list-orders)共 7 条命令。
196
- - 默认输出 `json`(agent-first);API Key 鉴权(`X-Api-Key`);写命令强制 `--idempotency-key`。
197
- - 新增测试设计文档 `docs/test-design-command-matrix-v1.md`。
194
+ - Landed merchant-cli in the monorepo `agenzo-cli/apps/merchant-cli` (rewritten per cli-design §4, reusing `@agenzo/cli-core`).
195
+ - Shipped `services` (list / get) + `ride-elife` (quote / book / get [+`--watch`] / cancel / list-orders), 7 commands in total.
196
+ - Output defaults to `json` (agent-first); API Key auth (`X-Api-Key`); write commands require `--idempotency-key`.
197
+ - Added the test design doc `docs/test-design-command-matrix-v1.md`.
package/dist/index.js CHANGED
@@ -175,7 +175,7 @@ var STABLE_MESSAGE = {
175
175
  INTERNAL_ERROR: "Something went wrong on the server. Please retry and note the request_id.",
176
176
  PARAM_INVALID: "One or more parameters are invalid. Please check your input and retry.",
177
177
  PARAM_IDEMPOTENCY_KEY_CONFLICT: "A request with this idempotency key was already processed with different parameters.",
178
- TOKEN_FEATURE_DISABLED: "This token feature is currently unavailable.",
178
+ TOKEN_FEATURE_DISABLED: "VCN creation is not supported yet. Coming soon.",
179
179
  BILLING_MODE_MISMATCH: "The billing mode does not match this operation. Please verify your account billing configuration.",
180
180
  ACCOUNT_NOT_FOUND: "The settlement account was not found.",
181
181
  ACCOUNT_SUSPENDED: "The settlement account is suspended. Please contact support.",
@@ -560,6 +560,29 @@ var STATUS_ICONS = {
560
560
  warning: "\u26A0",
561
561
  loading: "\u280B"
562
562
  };
563
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
564
+ function createSpinner(message, intervalMs = 60) {
565
+ let frameIdx = 0;
566
+ let currentMessage = message;
567
+ const render2 = () => {
568
+ process.stdout.write(`\r\x1B[K${SPINNER_FRAMES[frameIdx]} ${currentMessage}`);
569
+ frameIdx = (frameIdx + 1) % SPINNER_FRAMES.length;
570
+ };
571
+ render2();
572
+ const timer = setInterval(render2, intervalMs);
573
+ return {
574
+ update(msg) {
575
+ currentMessage = msg;
576
+ },
577
+ stop(type, finalMessage) {
578
+ clearInterval(timer);
579
+ process.stdout.write("\r\x1B[K");
580
+ if (type && finalMessage) {
581
+ console.log(Formatter.status(type, finalMessage));
582
+ }
583
+ }
584
+ };
585
+ }
563
586
  var Formatter = class _Formatter {
564
587
  /** Get display width of a string (CJK characters count as 2) */
565
588
  static displayWidth(str) {
@@ -669,12 +692,6 @@ function render(result, opts) {
669
692
  process.stdout.write(`${result.text()}
670
693
  `);
671
694
  }
672
- function notify(format, type, message) {
673
- if (format === "json") {
674
- return;
675
- }
676
- console.error(Formatter.status(type, message));
677
- }
678
695
  async function renderWithContext(result, opts, configManager) {
679
696
  if (opts.format !== "json") {
680
697
  render(result, opts);
@@ -1134,12 +1151,13 @@ function registerQuoteCommand(parent, deps) {
1134
1151
  if (opts.passengerEmail !== void 0) {
1135
1152
  body.passenger_email = opts.passengerEmail;
1136
1153
  }
1137
- notify(format, "loading", "Fetching quotes...");
1154
+ const spinner = format === "json" ? null : createSpinner("Fetching quotes...");
1138
1155
  const result = await deps.apiClient.post(
1139
1156
  "/ride/quote",
1140
1157
  { type: "api-key", key: apiKey },
1141
1158
  body
1142
1159
  );
1160
+ spinner?.stop();
1143
1161
  if (!result.success) {
1144
1162
  throw CliError.fromApi(result, { auth: "api-key" });
1145
1163
  }
@@ -1306,13 +1324,14 @@ function registerBookCommand(parent, deps) {
1306
1324
  yes: isYes,
1307
1325
  commandPath: "ride-elife book"
1308
1326
  });
1309
- notify(format, "loading", "Booking ride...");
1327
+ const spinner = format === "json" ? null : createSpinner("Booking ride...");
1310
1328
  const result = await deps.apiClient.post(
1311
1329
  "/ride/book",
1312
1330
  { type: "api-key", key: apiKey },
1313
1331
  body,
1314
1332
  { "Idempotency-Key": idempotencyKey }
1315
1333
  );
1334
+ spinner?.stop();
1316
1335
  if (!result.success) {
1317
1336
  throw CliError.fromApi(result, { auth: "api-key" });
1318
1337
  }
@@ -1401,11 +1420,12 @@ function registerRideGetCommand(parent, deps) {
1401
1420
  });
1402
1421
  return;
1403
1422
  }
1404
- notify(format, "loading", "Fetching ride status...");
1423
+ const spinner = format === "json" ? null : createSpinner("Fetching ride status...");
1405
1424
  const result = await deps.apiClient.get(
1406
1425
  `/ride/${encodeURIComponent(orderId)}/status`,
1407
1426
  { type: "api-key", key: apiKey }
1408
1427
  );
1428
+ spinner?.stop();
1409
1429
  if (!result.success) {
1410
1430
  throw CliError.fromApi(result, { auth: "api-key" });
1411
1431
  }
@@ -1470,13 +1490,14 @@ function registerCancelCommand(parent, deps) {
1470
1490
  yes: isYes,
1471
1491
  commandPath: "ride-elife cancel"
1472
1492
  });
1473
- notify(format, "loading", "Cancelling ride...");
1493
+ const spinner = format === "json" ? null : createSpinner("Cancelling ride...");
1474
1494
  const result = await deps.apiClient.post(
1475
1495
  `/ride/${encodeURIComponent(orderId)}/cancel`,
1476
1496
  { type: "api-key", key: apiKey },
1477
1497
  void 0,
1478
1498
  { "Idempotency-Key": idempotencyKey }
1479
1499
  );
1500
+ spinner?.stop();
1480
1501
  if (!result.success) {
1481
1502
  throw CliError.fromApi(result, { auth: "api-key" });
1482
1503
  }
@@ -1539,12 +1560,13 @@ function registerListOrdersCommand(parent, deps) {
1539
1560
  };
1540
1561
  if (opts.status !== void 0) params.status = opts.status;
1541
1562
  if (opts.orderType !== void 0) params.order_type = opts.orderType;
1542
- notify(format, "loading", "Fetching orders...");
1563
+ const spinner = format === "json" ? null : createSpinner("Fetching orders...");
1543
1564
  const result = await deps.apiClient.get(
1544
1565
  "/ride/orders",
1545
1566
  { type: "api-key", key: apiKey },
1546
1567
  params
1547
1568
  );
1569
+ spinner?.stop();
1548
1570
  if (!result.success) {
1549
1571
  throw CliError.fromApi(result, { auth: "api-key" });
1550
1572
  }