@agenzo/merchant-cli 0.4.1 → 0.4.3
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 +78 -123
- package/dist/index.js +54 -67
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,197 +1,152 @@
|
|
|
1
1
|
# @agenzo/merchant-cli
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> 覆盖两个 noun group:能力发现(`services`)与叫车业务闭环(`ride-elife`),共 7 条命令。
|
|
3
|
+
[](https://www.npmjs.com/package/@agenzo/merchant-cli) [](../../LICENSE) 
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
> **Merchant-fulfillment CLI** for the Agenzo platform — binary `agenzo-merchant-cli`.
|
|
6
|
+
> Agents fulfill commerce services here. Each capability is a noun and the set grows over time — use `services` to discover what's available; today's capability is ride-hailing (`ride-elife`).
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**[Overview](#overview)** · **[Features](#features)** · **[Quick Start](#quick-start)** · **[Global flags](#global-flags)** · **[Errors](#output-and-error-contract)** · **[Idempotency-Key](#idempotency-key)**
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
---
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- **`ride-elife`**(叫车,唯一在线 provider):报价 → 下单 → 查询/轮询 → 取消 → 列单的完整闭环。
|
|
12
|
+
## Overview
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
`agenzo-merchant-cli` is the command-line entry point for Agents to fulfill commerce services on the Agenzo platform. Each fulfillment capability is exposed as a **noun**, and the set grows over time:
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
- **`services`** (capability discovery): list what's currently available and how to call it — run this first to learn the current capability set.
|
|
17
|
+
- **`ride-elife`** (ride-hailing): the capability available today — the full loop of quote → book → query/poll → cancel → list-orders.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
More fulfillment capabilities are added as new nouns over time; `services list` always reflects the current set.
|
|
20
20
|
|
|
21
|
-
|
|
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` | 列出已下单的行程(分页 + 过滤) |
|
|
21
|
+
## Features
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
| Noun | Verb | Type | HTTP | Description |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| `services` | `list` | Read | — (local) | List available merchant-fulfillment capabilities |
|
|
26
|
+
| `services` | `get <service-id>` | Read | — (local) | View the full metadata of a single capability (miss → `SERVICE_NOT_FOUND`) |
|
|
27
|
+
| `ride-elife` | `quote` | Read | `POST /ride/quote` | Point-to-point quote (`vehicle_classes[]` + meet-and-greet) |
|
|
28
|
+
| `ride-elife` | `book` | Write/Y | `POST /ride/book` | Book against a quote (monthly_settlement-aware, see below) |
|
|
29
|
+
| `ride-elife` | `get` | Read | `GET /ride/<id>/status` | Query order status; `--watch` emits an NDJSON polling stream |
|
|
30
|
+
| `ride-elife` | `cancel` | Write/Y | `POST /ride/<id>/cancel` | Cancel an order (no body, may incur a fee) |
|
|
31
|
+
| `ride-elife` | `list-orders` | Read | `GET /ride/orders` | List booked trips (pagination + filtering) |
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
- 测试:[vitest](https://vitest.dev/) + [fast-check](https://fast-check.dev/)(PBT)
|
|
39
|
-
- 共享底包:`@agenzo/cli-core`(唯一横向依赖;**禁止** import 其它 app)
|
|
33
|
+
> Write commands (`book` / `cancel`) are subject to the idempotency-key rules (see [Idempotency-Key](#idempotency-key)).
|
|
34
|
+
> `ride-elife track` is not available — use `get --watch` for order-status polling.
|
|
40
35
|
|
|
41
|
-
##
|
|
36
|
+
## Quick Start
|
|
42
37
|
|
|
43
|
-
###
|
|
38
|
+
### Requirements
|
|
44
39
|
|
|
45
|
-
- Node.js
|
|
46
|
-
- monorepo 已 `npm install`
|
|
40
|
+
- Node.js 22+
|
|
47
41
|
|
|
48
|
-
###
|
|
42
|
+
### Installation
|
|
49
43
|
|
|
50
44
|
```bash
|
|
51
|
-
|
|
52
|
-
npm run build -w @agenzo/cli-core # 必须先构建共享底包
|
|
53
|
-
npm run build -w @agenzo/merchant-cli # tsup 产出 dist/index.js(= bin agenzo-merchant-cli)
|
|
45
|
+
npm install -g @agenzo/merchant-cli
|
|
54
46
|
agenzo-merchant-cli --version
|
|
55
47
|
```
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
Upgrade later with `npm install -g @agenzo/merchant-cli@latest`.
|
|
58
50
|
|
|
59
|
-
|
|
51
|
+
### Authentication
|
|
60
52
|
|
|
61
|
-
- API Key
|
|
62
|
-
- 缺省 `--api-key` 时交互索取(password 输入)。
|
|
63
|
-
- 无 Bearer Token / AuthService / keystore——与 `agenzo-token-cli` 一致。
|
|
53
|
+
A runtime-plane CLI that uses an **API Key** (`--api-key`, carried per command as the `X-Api-Key` header):
|
|
64
54
|
|
|
65
|
-
|
|
55
|
+
- The API Key is issued by `agenzo-admin-cli keys create --scope merchant` and must include the `merchant` scope.
|
|
56
|
+
- When `--api-key` is omitted, it is requested interactively (password input).
|
|
57
|
+
- No Bearer Token or local keystore — every command carries the API Key.
|
|
58
|
+
|
|
59
|
+
### End-to-end example
|
|
66
60
|
|
|
67
61
|
```bash
|
|
68
|
-
# 1.
|
|
62
|
+
# 1. Discover capabilities
|
|
69
63
|
agenzo-merchant-cli services list
|
|
70
64
|
agenzo-merchant-cli services get ride-elife
|
|
71
65
|
|
|
72
|
-
# 2.
|
|
66
|
+
# 2. Quote (produces quote_id)
|
|
73
67
|
agenzo-merchant-cli ride-elife quote --api-key "$API_KEY" \
|
|
74
68
|
--pickup-lat 37.7937 --pickup-lng -122.3956 --pickup-name "1 Market St" \
|
|
75
69
|
--dropoff-lat 37.6213 --dropoff-lng -122.3790 --dropoff-name "SFO Airport" \
|
|
76
70
|
--pickup-time now
|
|
77
71
|
|
|
78
|
-
# 3.
|
|
72
|
+
# 3. Book (write op: --yes auto-confirms + --idempotency-key must be provided)
|
|
79
73
|
agenzo-merchant-cli ride-elife book --api-key "$API_KEY" --yes \
|
|
80
74
|
--quote-id "$QUOTE_ID" --vehicle-class Sedan --price-amount 42.50 \
|
|
81
75
|
--passenger-name "Alice" --passenger-phone "+14155551234" \
|
|
82
76
|
--idempotency-key "book-$(date +%s)"
|
|
83
77
|
|
|
84
|
-
# 4.
|
|
78
|
+
# 4. Query / poll (--watch emits line-by-line NDJSON)
|
|
85
79
|
agenzo-merchant-cli ride-elife get --api-key "$API_KEY" --order-id "$RIDE_ID"
|
|
86
80
|
agenzo-merchant-cli ride-elife get --api-key "$API_KEY" --order-id "$RIDE_ID" \
|
|
87
81
|
--watch --watch-interval 5 --watch-timeout 600
|
|
88
82
|
|
|
89
|
-
# 5.
|
|
83
|
+
# 5. List orders / cancel
|
|
90
84
|
agenzo-merchant-cli ride-elife list-orders --api-key "$API_KEY" --status Pending --page 1 --page-size 20
|
|
91
85
|
agenzo-merchant-cli ride-elife cancel --api-key "$API_KEY" --yes \
|
|
92
86
|
--order-id "$RIDE_ID" --idempotency-key "cancel-$(date +%s)"
|
|
93
87
|
```
|
|
94
88
|
|
|
95
|
-
##
|
|
89
|
+
## Global flags
|
|
96
90
|
|
|
97
|
-
| Flag |
|
|
91
|
+
| Flag | Default | Description |
|
|
98
92
|
|---|---|---|
|
|
99
|
-
| `--format <json\|table>` | **`json`** |
|
|
100
|
-
| `--api-key <key>` |
|
|
101
|
-
| `--yes` | `false` |
|
|
102
|
-
| `--verbose` | `false` |
|
|
93
|
+
| `--format <json\|table>` | **`json`** | Output format. Defaults to `json`; pass `table` for tabular output. Also reads the `AGENZO_FORMAT` environment variable |
|
|
94
|
+
| `--api-key <key>` | — (requested interactively) | API Key, carried per command as the `X-Api-Key` header |
|
|
95
|
+
| `--yes` | `false` | Skip interactive confirmation (non-interactive automation); for write commands, `--yes` with a missing idempotency key is a hard error |
|
|
96
|
+
| `--verbose` | `false` | Verbose logging (written to stderr) |
|
|
103
97
|
|
|
104
|
-
##
|
|
98
|
+
## Output and error contract
|
|
105
99
|
|
|
106
|
-
- **`--format json
|
|
107
|
-
- **`--format table
|
|
108
|
-
- **`get --watch
|
|
109
|
-
-
|
|
100
|
+
- **`--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.
|
|
101
|
+
- **`--format table`**: business output goes to stdout, status lines/spinner go to stderr.
|
|
102
|
+
- **`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", ... }`.
|
|
103
|
+
- **Error envelope**: `json` → `{ "error": { "code", "code_num", "message", "request_id"? } }`; `table` → `✗ [<code_num>] <message>`. When a request fails, retry with the same `--idempotency-key`; if it persists, contact support with the `request_id` from the error output.
|
|
110
104
|
|
|
111
|
-
|
|
105
|
+
Exit codes:
|
|
112
106
|
|
|
113
|
-
|
|
|
107
|
+
| Exit code | Meaning | Representative error codes |
|
|
114
108
|
|---|---|---|
|
|
115
|
-
| `0` |
|
|
116
|
-
| `1` |
|
|
117
|
-
| `2` |
|
|
118
|
-
| `3` |
|
|
119
|
-
| `4` |
|
|
120
|
-
| `5` |
|
|
109
|
+
| `0` | Success | — |
|
|
110
|
+
| `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) |
|
|
111
|
+
| `2` | Upgrade required | `UPGRADE_REQUIRED`(9008) |
|
|
112
|
+
| `3` | Auth / invalid key | `KEY_INVALID`(1101) · `KEY_SCOPE_DENIED`(1102) |
|
|
113
|
+
| `4` | Network / 5xx | `RATE_LIMITED`(5001) · `UPSTREAM_ERROR`(5101) · `INTERNAL_ERROR`(5201) |
|
|
114
|
+
| `5` | User cancel | `CLIENT_ABORTED`(9007) · SIGINT |
|
|
121
115
|
|
|
122
|
-
> api-key
|
|
116
|
+
> api-key auth mapping: HTTP 401 → `KEY_INVALID`, 403 → `KEY_SCOPE_DENIED`. A known string error code (e.g. `QUOTE_EXPIRED`) takes priority and is preserved; otherwise it falls back to the HTTP-status mapping.
|
|
123
117
|
|
|
124
|
-
##
|
|
118
|
+
## Idempotency-Key
|
|
125
119
|
|
|
126
|
-
|
|
120
|
+
The write commands `ride-elife book` / `cancel` accept `--idempotency-key`:
|
|
127
121
|
|
|
128
|
-
-
|
|
129
|
-
- **CLI
|
|
130
|
-
-
|
|
131
|
-
-
|
|
122
|
+
- Format: `[A-Za-z0-9_-]{1,128}`.
|
|
123
|
+
- **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.
|
|
124
|
+
- Sent as the `Idempotency-Key` HTTP **header**, **never in the body**.
|
|
125
|
+
- Reuse the same key to retry the same logical request; use a new key for a new request.
|
|
132
126
|
|
|
133
|
-
## book
|
|
127
|
+
## Billing model for book (monthly_settlement-aware)
|
|
134
128
|
|
|
135
|
-
`ride-elife book`
|
|
129
|
+
`ride-elife book` **does not accept** `--payment-method-id` or any card information — the merchant domain holds no payment handle. Funding is decided according to the Developer's `billing_mode`:
|
|
136
130
|
|
|
137
|
-
- **monthly_settlement
|
|
138
|
-
- **pay_per_call
|
|
131
|
+
- **monthly_settlement**: deducted from the monthly-settlement account, no payment handle; the response has `payment_status=ON_ACCOUNT` and includes `billing_entry_id`.
|
|
132
|
+
- **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
133
|
|
|
140
|
-
|
|
134
|
+
The request body contains at most an optional `payment_order_id`.
|
|
141
135
|
|
|
142
|
-
## profile / host
|
|
136
|
+
## profile / host model
|
|
143
137
|
|
|
144
|
-
merchant-cli
|
|
138
|
+
merchant-cli **has no host / config commands** and does not govern environments. It reuses the environment configured by `agenzo-admin-cli` (persisted under `~/.agenzo-admin-cli/`):
|
|
145
139
|
|
|
146
|
-
-
|
|
147
|
-
-
|
|
140
|
+
- Determines the API host that requests are sent to.
|
|
141
|
+
- Provides the environment name + host for the `profile` / `endpoint` envelope in json output.
|
|
148
142
|
|
|
149
|
-
|
|
143
|
+
**Setting the host / switching environments belongs to `agenzo-admin-cli`** (`agenzo-admin-cli config set-host`).
|
|
150
144
|
|
|
151
|
-
##
|
|
145
|
+
## Machine-readable verb schema
|
|
152
146
|
|
|
153
|
-
|
|
147
|
+
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
148
|
|
|
155
149
|
```bash
|
|
156
150
|
agenzo-merchant-cli ride-elife quote --help --format json
|
|
157
151
|
agenzo-merchant-cli ride-elife book --help --format json
|
|
158
152
|
```
|
|
159
|
-
|
|
160
|
-
## 开发
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
npm run build -w @agenzo/merchant-cli # tsup 构建
|
|
164
|
-
npm run test -w @agenzo/merchant-cli # vitest(含 fast-check PBT)
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
源码结构:
|
|
168
|
-
|
|
169
|
-
```text
|
|
170
|
-
apps/merchant-cli/
|
|
171
|
-
src/
|
|
172
|
-
index.ts # commander 装配 + 顶层 reportError + SIGINT
|
|
173
|
-
services/{list,get}.ts # 能力发现(本期:CLI 内置注册表)
|
|
174
|
-
services/registry.ts # 内置静态注册表(商户域)
|
|
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
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## 文档索引
|
|
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)。
|
|
189
|
-
|
|
190
|
-
## 更新日志
|
|
191
|
-
|
|
192
|
-
### 2026-06
|
|
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`。
|
package/dist/index.js
CHANGED
|
@@ -270,10 +270,6 @@ var ApiClient = class {
|
|
|
270
270
|
const headers = {
|
|
271
271
|
"User-Agent": `agenzo-admin-cli/${getCurrentVersion()}`
|
|
272
272
|
};
|
|
273
|
-
const traceparent = process.env.TRACEPARENT;
|
|
274
|
-
if (traceparent) {
|
|
275
|
-
headers["traceparent"] = traceparent;
|
|
276
|
-
}
|
|
277
273
|
if (auth.type === "bearer") {
|
|
278
274
|
headers["Authorization"] = `Bearer ${auth.token}`;
|
|
279
275
|
} else if (auth.type === "api-key") {
|
|
@@ -564,6 +560,29 @@ var STATUS_ICONS = {
|
|
|
564
560
|
warning: "\u26A0",
|
|
565
561
|
loading: "\u280B"
|
|
566
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
|
+
}
|
|
567
586
|
var Formatter = class _Formatter {
|
|
568
587
|
/** Get display width of a string (CJK characters count as 2) */
|
|
569
588
|
static displayWidth(str) {
|
|
@@ -673,12 +692,6 @@ function render(result, opts) {
|
|
|
673
692
|
process.stdout.write(`${result.text()}
|
|
674
693
|
`);
|
|
675
694
|
}
|
|
676
|
-
function notify(format, type, message) {
|
|
677
|
-
if (format === "json") {
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
console.error(Formatter.status(type, message));
|
|
681
|
-
}
|
|
682
695
|
async function renderWithContext(result, opts, configManager) {
|
|
683
696
|
if (opts.format !== "json") {
|
|
684
697
|
render(result, opts);
|
|
@@ -696,17 +709,11 @@ async function renderWithContext(result, opts, configManager) {
|
|
|
696
709
|
render({ ...result, data: payload }, opts);
|
|
697
710
|
}
|
|
698
711
|
var PromptEngine = class {
|
|
699
|
-
/** Return flagValue if provided,
|
|
712
|
+
/** Return flagValue directly if provided, otherwise prompt interactively */
|
|
700
713
|
static async resolveInput(flagValue, config) {
|
|
701
714
|
if (flagValue !== void 0) {
|
|
702
715
|
return flagValue;
|
|
703
716
|
}
|
|
704
|
-
if (config.envVar) {
|
|
705
|
-
const envValue = process.env[config.envVar];
|
|
706
|
-
if (envValue !== void 0 && envValue !== "") {
|
|
707
|
-
return envValue;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
717
|
if (config.type === "password") {
|
|
711
718
|
return password({ message: config.message, mask: "*" });
|
|
712
719
|
}
|
|
@@ -1108,15 +1115,14 @@ function formatQuote(data) {
|
|
|
1108
1115
|
${Formatter.keyValue(lines)}`;
|
|
1109
1116
|
}
|
|
1110
1117
|
function registerQuoteCommand(parent, deps) {
|
|
1111
|
-
const cmd = parent.command("quote").description("Request fare quotes for a ride between two points").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--pickup-lat <lat>", "Pickup latitude").option("--pickup-lng <lng>", "Pickup longitude").option("--pickup-name <name>", "Pickup location name").option("--dropoff-lat <lat>", "Dropoff latitude").option("--dropoff-lng <lng>", "Dropoff longitude").option("--dropoff-name <name>", "Dropoff location name").option("--pickup-time <time>", 'Pickup time: epoch seconds, or "now"').option("--passenger-name <name>", "Lead passenger full name").option("--passenger-phone <phone>", "Passenger phone (E.164)").option("--passenger-count <n>", "Passenger count").option("--luggage-count <n>", "Luggage count").option("--passenger-email <email>", "Passenger email").option("--children-count <n>", "Children count");
|
|
1118
|
+
const cmd = parent.command("quote").description("Request fare quotes for a ride between two points").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--pickup-lat <lat>", "Pickup latitude").option("--pickup-lng <lng>", "Pickup longitude").option("--pickup-name <name>", "Pickup location name").option("--dropoff-lat <lat>", "Dropoff latitude").option("--dropoff-lng <lng>", "Dropoff longitude").option("--dropoff-name <name>", "Dropoff location name").option("--pickup-time <time>", 'Pickup time: epoch seconds, or "now"').option("--passenger-name <name>", "Lead passenger full name").option("--passenger-phone <phone>", "Passenger phone (E.164)").option("--passenger-count <n>", "Passenger count").option("--luggage-count <n>", "Luggage count").option("--passenger-email <email>", "Passenger email").option("--children-count <n>", "Children count").option("--infant-count <n>", "Infant count (passenger breakdown)").option("--toddler-count <n>", "Toddler count (passenger breakdown)");
|
|
1112
1119
|
attachSchemaHelp(cmd, quoteSchema);
|
|
1113
1120
|
cmd.action(async () => {
|
|
1114
1121
|
const opts = cmd.optsWithGlobals();
|
|
1115
1122
|
const format = resolveFormat(opts.format);
|
|
1116
1123
|
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
1117
1124
|
message: "API Key:",
|
|
1118
|
-
type: "password"
|
|
1119
|
-
envVar: "AGENZO_API_KEY"
|
|
1125
|
+
type: "password"
|
|
1120
1126
|
});
|
|
1121
1127
|
const body = {
|
|
1122
1128
|
pickup: {
|
|
@@ -1142,15 +1148,22 @@ function registerQuoteCommand(parent, deps) {
|
|
|
1142
1148
|
if (opts.childrenCount !== void 0) {
|
|
1143
1149
|
body.children_count = num(opts.childrenCount, "children-count");
|
|
1144
1150
|
}
|
|
1151
|
+
if (opts.infantCount !== void 0) {
|
|
1152
|
+
body.infant_count = num(opts.infantCount, "infant-count");
|
|
1153
|
+
}
|
|
1154
|
+
if (opts.toddlerCount !== void 0) {
|
|
1155
|
+
body.toddler_count = num(opts.toddlerCount, "toddler-count");
|
|
1156
|
+
}
|
|
1145
1157
|
if (opts.passengerEmail !== void 0) {
|
|
1146
1158
|
body.passenger_email = opts.passengerEmail;
|
|
1147
1159
|
}
|
|
1148
|
-
|
|
1160
|
+
const spinner = format === "json" ? null : createSpinner("Fetching quotes...");
|
|
1149
1161
|
const result = await deps.apiClient.post(
|
|
1150
1162
|
"/ride/quote",
|
|
1151
1163
|
{ type: "api-key", key: apiKey },
|
|
1152
1164
|
body
|
|
1153
1165
|
);
|
|
1166
|
+
spinner?.stop();
|
|
1154
1167
|
if (!result.success) {
|
|
1155
1168
|
throw CliError.fromApi(result, { auth: "api-key" });
|
|
1156
1169
|
}
|
|
@@ -1244,8 +1257,7 @@ function registerBookCommand(parent, deps) {
|
|
|
1244
1257
|
const isYes = Boolean(opts.yes);
|
|
1245
1258
|
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
1246
1259
|
message: "API Key:",
|
|
1247
|
-
type: "password"
|
|
1248
|
-
envVar: "AGENZO_API_KEY"
|
|
1260
|
+
type: "password"
|
|
1249
1261
|
});
|
|
1250
1262
|
const quoteId = need2(opts.quoteId, "quote-id");
|
|
1251
1263
|
const body = {
|
|
@@ -1318,13 +1330,14 @@ function registerBookCommand(parent, deps) {
|
|
|
1318
1330
|
yes: isYes,
|
|
1319
1331
|
commandPath: "ride-elife book"
|
|
1320
1332
|
});
|
|
1321
|
-
|
|
1333
|
+
const spinner = format === "json" ? null : createSpinner("Booking ride...");
|
|
1322
1334
|
const result = await deps.apiClient.post(
|
|
1323
1335
|
"/ride/book",
|
|
1324
1336
|
{ type: "api-key", key: apiKey },
|
|
1325
1337
|
body,
|
|
1326
1338
|
{ "Idempotency-Key": idempotencyKey }
|
|
1327
1339
|
);
|
|
1340
|
+
spinner?.stop();
|
|
1328
1341
|
if (!result.success) {
|
|
1329
1342
|
throw CliError.fromApi(result, { auth: "api-key" });
|
|
1330
1343
|
}
|
|
@@ -1397,8 +1410,7 @@ function registerRideGetCommand(parent, deps) {
|
|
|
1397
1410
|
const format = resolveFormat(opts.format);
|
|
1398
1411
|
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
1399
1412
|
message: "API Key:",
|
|
1400
|
-
type: "password"
|
|
1401
|
-
envVar: "AGENZO_API_KEY"
|
|
1413
|
+
type: "password"
|
|
1402
1414
|
});
|
|
1403
1415
|
const orderId = need3(opts.orderId, "order-id");
|
|
1404
1416
|
if (opts.watch) {
|
|
@@ -1414,11 +1426,12 @@ function registerRideGetCommand(parent, deps) {
|
|
|
1414
1426
|
});
|
|
1415
1427
|
return;
|
|
1416
1428
|
}
|
|
1417
|
-
|
|
1429
|
+
const spinner = format === "json" ? null : createSpinner("Fetching ride status...");
|
|
1418
1430
|
const result = await deps.apiClient.get(
|
|
1419
1431
|
`/ride/${encodeURIComponent(orderId)}/status`,
|
|
1420
1432
|
{ type: "api-key", key: apiKey }
|
|
1421
1433
|
);
|
|
1434
|
+
spinner?.stop();
|
|
1422
1435
|
if (!result.success) {
|
|
1423
1436
|
throw CliError.fromApi(result, { auth: "api-key" });
|
|
1424
1437
|
}
|
|
@@ -1467,8 +1480,7 @@ function registerCancelCommand(parent, deps) {
|
|
|
1467
1480
|
const isYes = Boolean(opts.yes);
|
|
1468
1481
|
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
1469
1482
|
message: "API Key:",
|
|
1470
|
-
type: "password"
|
|
1471
|
-
envVar: "AGENZO_API_KEY"
|
|
1483
|
+
type: "password"
|
|
1472
1484
|
});
|
|
1473
1485
|
const orderId = need4(opts.orderId, "order-id");
|
|
1474
1486
|
if (!isYes) {
|
|
@@ -1484,13 +1496,14 @@ function registerCancelCommand(parent, deps) {
|
|
|
1484
1496
|
yes: isYes,
|
|
1485
1497
|
commandPath: "ride-elife cancel"
|
|
1486
1498
|
});
|
|
1487
|
-
|
|
1499
|
+
const spinner = format === "json" ? null : createSpinner("Cancelling ride...");
|
|
1488
1500
|
const result = await deps.apiClient.post(
|
|
1489
1501
|
`/ride/${encodeURIComponent(orderId)}/cancel`,
|
|
1490
1502
|
{ type: "api-key", key: apiKey },
|
|
1491
1503
|
void 0,
|
|
1492
1504
|
{ "Idempotency-Key": idempotencyKey }
|
|
1493
1505
|
);
|
|
1506
|
+
spinner?.stop();
|
|
1494
1507
|
if (!result.success) {
|
|
1495
1508
|
throw CliError.fromApi(result, { auth: "api-key" });
|
|
1496
1509
|
}
|
|
@@ -1545,8 +1558,7 @@ function registerListOrdersCommand(parent, deps) {
|
|
|
1545
1558
|
const format = resolveFormat(opts.format);
|
|
1546
1559
|
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
1547
1560
|
message: "API Key:",
|
|
1548
|
-
type: "password"
|
|
1549
|
-
envVar: "AGENZO_API_KEY"
|
|
1561
|
+
type: "password"
|
|
1550
1562
|
});
|
|
1551
1563
|
const params = {
|
|
1552
1564
|
page: positiveInt(opts.page ?? DEFAULT_PAGE, "page"),
|
|
@@ -1554,12 +1566,13 @@ function registerListOrdersCommand(parent, deps) {
|
|
|
1554
1566
|
};
|
|
1555
1567
|
if (opts.status !== void 0) params.status = opts.status;
|
|
1556
1568
|
if (opts.orderType !== void 0) params.order_type = opts.orderType;
|
|
1557
|
-
|
|
1569
|
+
const spinner = format === "json" ? null : createSpinner("Fetching orders...");
|
|
1558
1570
|
const result = await deps.apiClient.get(
|
|
1559
1571
|
"/ride/orders",
|
|
1560
1572
|
{ type: "api-key", key: apiKey },
|
|
1561
1573
|
params
|
|
1562
1574
|
);
|
|
1575
|
+
spinner?.stop();
|
|
1563
1576
|
if (!result.success) {
|
|
1564
1577
|
throw CliError.fromApi(result, { auth: "api-key" });
|
|
1565
1578
|
}
|
|
@@ -1614,25 +1627,12 @@ function toListItem(s) {
|
|
|
1614
1627
|
discovery: s.discovery
|
|
1615
1628
|
};
|
|
1616
1629
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
const result = await apiClient.get(
|
|
1620
|
-
"/services",
|
|
1621
|
-
{ type: "api-key", key: apiKey }
|
|
1622
|
-
);
|
|
1623
|
-
if (result.success && Array.isArray(result.data?.services)) {
|
|
1624
|
-
return result.data.services;
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
return SERVICE_REGISTRY.map(toListItem);
|
|
1628
|
-
}
|
|
1629
|
-
function registerServicesListCommand(parent, deps) {
|
|
1630
|
-
const cmd = parent.command("list").description("List available merchant services (live backend feed; falls back to bundled registry)").option("--api-key <key>", "API Key enabling live backend discovery (or set AGENZO_API_KEY)");
|
|
1630
|
+
function registerServicesListCommand(parent) {
|
|
1631
|
+
const cmd = parent.command("list").description("List available merchant services from the registry");
|
|
1631
1632
|
cmd.action(async () => {
|
|
1632
1633
|
const opts = cmd.optsWithGlobals();
|
|
1633
1634
|
const format = resolveFormat(opts.format);
|
|
1634
|
-
const
|
|
1635
|
-
const services = await resolveServiceList(deps?.apiClient, apiKey);
|
|
1635
|
+
const services = SERVICE_REGISTRY.map(toListItem);
|
|
1636
1636
|
const configManager = new ConfigManager();
|
|
1637
1637
|
const commandResult = {
|
|
1638
1638
|
data: { services },
|
|
@@ -1676,25 +1676,12 @@ function formatServiceGet(s) {
|
|
|
1676
1676
|
Verb descriptions:
|
|
1677
1677
|
${verbDetail}`;
|
|
1678
1678
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
const result = await apiClient.get(
|
|
1682
|
-
`/services/${encodeURIComponent(serviceId)}`,
|
|
1683
|
-
{ type: "api-key", key: apiKey }
|
|
1684
|
-
);
|
|
1685
|
-
if (result.success && result.data?.service_id) {
|
|
1686
|
-
return result.data;
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
return findService(serviceId);
|
|
1690
|
-
}
|
|
1691
|
-
function registerServiceGetCommand(parent, deps) {
|
|
1692
|
-
const cmd = parent.command("get <service-id>").description("Retrieve a single merchant service by id (live backend feed; falls back to bundled registry)").option("--api-key <key>", "API Key enabling live backend discovery (or set AGENZO_API_KEY)");
|
|
1679
|
+
function registerServiceGetCommand(parent) {
|
|
1680
|
+
const cmd = parent.command("get <service-id>").description("Retrieve a single merchant service by id");
|
|
1693
1681
|
cmd.action(async (serviceId) => {
|
|
1694
1682
|
const opts = cmd.optsWithGlobals();
|
|
1695
1683
|
const format = resolveFormat(opts.format);
|
|
1696
|
-
const
|
|
1697
|
-
const service = await resolveService(deps?.apiClient, apiKey, serviceId);
|
|
1684
|
+
const service = findService(serviceId);
|
|
1698
1685
|
if (!service) {
|
|
1699
1686
|
throw new CliError(
|
|
1700
1687
|
"SERVICE_NOT_FOUND",
|
|
@@ -1738,8 +1725,8 @@ async function main() {
|
|
|
1738
1725
|
registerCancelCommand(rideCmd, deps);
|
|
1739
1726
|
registerListOrdersCommand(rideCmd, deps);
|
|
1740
1727
|
const servicesCmd = program.command("services").description("Merchant service discovery");
|
|
1741
|
-
registerServicesListCommand(servicesCmd
|
|
1742
|
-
registerServiceGetCommand(servicesCmd
|
|
1728
|
+
registerServicesListCommand(servicesCmd);
|
|
1729
|
+
registerServiceGetCommand(servicesCmd);
|
|
1743
1730
|
await program.parseAsync(process.argv);
|
|
1744
1731
|
}
|
|
1745
1732
|
function resolveActiveFormat() {
|