@agenzo/merchant-cli 0.2.0 → 0.3.0

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/dist/index.js CHANGED
@@ -1,826 +1,919 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command8 } from "commander";
4
+ import { Command } from "commander";
5
+ import {
6
+ ApiClient as ApiClient6,
7
+ ConfigManager as ConfigManager8,
8
+ Formatter as Formatter8,
9
+ CliError as CliError9,
10
+ UserCancelError,
11
+ toErrorEnvelope,
12
+ resolveFormat as resolveFormat8,
13
+ exitCodeFor,
14
+ getCurrentVersion
15
+ } from "@agenzo/cli-core";
5
16
 
6
- // src/core/errors.ts
7
- var ErrorCodes = {
8
- // -- Ride/elife domain (raised by the backend, recognized here) --
9
- QUOTE_EXPIRED: "QUOTE_EXPIRED",
10
- VEHICLE_UNAVAILABLE: "VEHICLE_UNAVAILABLE",
11
- BOOKING_FAILED: "BOOKING_FAILED",
12
- CANCELLATION_NOT_ALLOWED: "CANCELLATION_NOT_ALLOWED",
13
- // -- Billing / settlement-account (monthly_settlement) --
14
- BILLING_MODE_MISMATCH: "BILLING_MODE_MISMATCH",
15
- ACCOUNT_NOT_FOUND: "ACCOUNT_NOT_FOUND",
16
- ACCOUNT_SUSPENDED: "ACCOUNT_SUSPENDED",
17
- ACCOUNT_INSUFFICIENT_BALANCE: "ACCOUNT_INSUFFICIENT_BALANCE",
18
- // -- Payment order (pay_per_call; reserved until payment-cli ships) --
19
- PAYMENT_ORDER_NOT_FOUND: "PAYMENT_ORDER_NOT_FOUND",
20
- PAYMENT_ORDER_NOT_PAID: "PAYMENT_ORDER_NOT_PAID",
21
- PAYMENT_ORDER_MISMATCH: "PAYMENT_ORDER_MISMATCH",
22
- PAYMENT_ORDER_ALREADY_CONSUMED: "PAYMENT_ORDER_ALREADY_CONSUMED",
23
- // -- Parameter / idempotency --
24
- PARAM_IDEMPOTENCY_KEY_REQUIRED: "PARAM_IDEMPOTENCY_KEY_REQUIRED",
25
- PARAM_IDEMPOTENCY_KEY_CONFLICT: "PARAM_IDEMPOTENCY_KEY_CONFLICT",
26
- // -- Service discovery --
27
- SERVICE_NOT_FOUND: "SERVICE_NOT_FOUND",
28
- // -- CLI-local / transport --
29
- NETWORK_ERROR: "NETWORK_ERROR",
30
- TIMEOUT: "TIMEOUT",
31
- CLI_OUTDATED: "CLI_OUTDATED",
32
- CONFIG_ERROR: "CONFIG_ERROR",
33
- NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
34
- INTERNAL_ERROR: "INTERNAL_ERROR"
35
- };
36
- var CliError = class extends Error {
37
- constructor(code, message, recovery, exitCode = 1) {
38
- super(message);
39
- this.code = code;
40
- this.recovery = recovery;
41
- this.exitCode = exitCode;
42
- this.name = "CliError";
43
- }
44
- code;
45
- recovery;
46
- exitCode;
47
- };
17
+ // src/ride-elife/quote.ts
18
+ import {
19
+ ConfigManager,
20
+ PromptEngine,
21
+ Formatter,
22
+ resolveFormat,
23
+ notify,
24
+ CliError as CliError2,
25
+ renderWithContext
26
+ } from "@agenzo/cli-core";
48
27
 
49
- // src/core/config-manager.ts
50
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
51
- import { homedir } from "os";
52
- import { join } from "path";
53
- var DEFAULT_HOST = "https://agent.everonet.com";
54
- var CONFIG_DIR = join(homedir(), ".agenzo-merchant-cli");
55
- var CONFIG_PATH = join(CONFIG_DIR, "config.json");
56
- function read() {
57
- let raw;
58
- try {
59
- raw = readFileSync(CONFIG_PATH, "utf-8");
60
- } catch (error) {
61
- if (error.code === "ENOENT") {
62
- return { host: DEFAULT_HOST };
63
- }
64
- throw error;
65
- }
66
- try {
67
- const parsed = JSON.parse(raw);
68
- return { host: parsed.host ?? DEFAULT_HOST };
69
- } catch {
70
- throw new CliError(
71
- ErrorCodes.CONFIG_ERROR,
72
- `Invalid config file: ${CONFIG_PATH}`,
73
- "Delete the file to reset to defaults."
74
- );
75
- }
76
- }
77
- function write(config) {
78
- mkdirSync(CONFIG_DIR, { recursive: true });
79
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
28
+ // src/ride-elife/watch.ts
29
+ import { CliError } from "@agenzo/cli-core";
30
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
31
+ "At destination",
32
+ "Cancelled",
33
+ "Rejected",
34
+ "Customer no show",
35
+ "Driver no show"
36
+ ]);
37
+ var DEFAULT_WATCH_INTERVAL_SECONDS = 5;
38
+ var DEFAULT_WATCH_TIMEOUT_SECONDS = 600;
39
+ function isTerminalStatus(status) {
40
+ return status !== void 0 && status !== null && TERMINAL_STATUSES.has(status);
80
41
  }
81
- function getHost() {
82
- return read().host;
42
+ function statusOf(data) {
43
+ const status = data.status;
44
+ return typeof status === "string" ? status : void 0;
83
45
  }
84
- function setHost(host) {
85
- write({ host: host.replace(/\/+$/, "") });
46
+ async function runWatch(deps, opts) {
47
+ const deadline = deps.now() + opts.timeoutMs;
48
+ for (; ; ) {
49
+ const data = await deps.fetchStatus();
50
+ deps.writeLine(data);
51
+ const status = statusOf(data);
52
+ if (isTerminalStatus(status)) {
53
+ return;
54
+ }
55
+ if (deps.now() + opts.intervalMs >= deadline) {
56
+ const timeoutLine = {
57
+ watch_status: "timeout",
58
+ message: `Polling stopped after ${opts.timeoutMs / 1e3}s without reaching a terminal status.`,
59
+ last_status: status ?? null
60
+ };
61
+ deps.writeLine(timeoutLine);
62
+ return;
63
+ }
64
+ await deps.sleep(opts.intervalMs);
65
+ }
86
66
  }
87
- function resetHost() {
88
- write({ host: DEFAULT_HOST });
67
+ function resolveSeconds(value, fallback) {
68
+ if (value === void 0) return fallback;
69
+ const n = Number(value);
70
+ return Number.isFinite(n) && n > 0 ? n : fallback;
89
71
  }
90
-
91
- // src/core/formatter.ts
92
- var ICONS = {
93
- success: "\u2713",
94
- error: "\u2717",
95
- info: "\u2139",
96
- warning: "\u26A0"
97
- };
98
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
99
- function createSpinner(message) {
100
- const isTty = Boolean(process.stderr.isTTY);
101
- let current = message;
102
- if (!isTty) {
103
- return {
104
- update(msg) {
105
- current = msg;
106
- },
107
- stop() {
108
- },
109
- succeed(msg) {
110
- if (msg) process.stderr.write(`${Formatter.status("success", msg)}
72
+ var realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
73
+ var ndjsonWriteLine = (record) => {
74
+ process.stdout.write(`${JSON.stringify(record)}
111
75
  `);
76
+ };
77
+ async function watchRideStatus(apiClient, apiKey, orderId, opts) {
78
+ const path = `/ride/${encodeURIComponent(orderId)}/status`;
79
+ await runWatch(
80
+ {
81
+ fetchStatus: async () => {
82
+ const result = await apiClient.get(path, {
83
+ type: "api-key",
84
+ key: apiKey
85
+ });
86
+ if (!result.success) {
87
+ throw CliError.fromApi(result, { auth: "api-key" });
88
+ }
89
+ return result.data;
112
90
  },
113
- fail(msg) {
114
- process.stderr.write(`${Formatter.status("error", msg ?? current)}
115
- `);
116
- }
117
- };
118
- }
119
- let frame = 0;
120
- const render = () => {
121
- process.stderr.write(`\r\x1B[K${SPINNER_FRAMES[frame]} ${current}`);
122
- frame = (frame + 1) % SPINNER_FRAMES.length;
123
- };
124
- render();
125
- const timer = setInterval(render, 80);
126
- const clear = () => {
127
- clearInterval(timer);
128
- process.stderr.write("\r\x1B[K");
129
- };
130
- return {
131
- update(msg) {
132
- current = msg;
133
- },
134
- stop() {
135
- clear();
136
- },
137
- succeed(msg) {
138
- clear();
139
- if (msg) process.stderr.write(`${Formatter.status("success", msg)}
140
- `);
91
+ writeLine: ndjsonWriteLine,
92
+ sleep: realSleep,
93
+ now: () => Date.now()
141
94
  },
142
- fail(msg) {
143
- clear();
144
- process.stderr.write(`${Formatter.status("error", msg ?? current)}
145
- `);
95
+ {
96
+ intervalMs: opts.intervalSeconds * 1e3,
97
+ timeoutMs: opts.timeoutSeconds * 1e3
146
98
  }
147
- };
148
- }
149
- function stringify(value) {
150
- if (value === null || value === void 0) return "";
151
- if (typeof value === "object") return JSON.stringify(value);
152
- return String(value);
99
+ );
153
100
  }
154
- var Formatter = class _Formatter {
155
- /** Status-prefixed line, e.g. "✓ done". */
156
- static status(type, message) {
157
- return `${ICONS[type]} ${message}`;
158
- }
159
- /** Aligned columnar table for list output. */
160
- static table(headers, rows) {
161
- const widths = headers.map(
162
- (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
163
- );
164
- const line = (cells) => cells.map((c, i) => (c ?? "").padEnd(widths[i])).join(" ");
165
- return [
166
- line(headers),
167
- widths.map((w) => "-".repeat(w)).join(" "),
168
- ...rows.map(line)
169
- ].join("\n");
170
- }
171
- /** Key-value detail output with left-aligned keys. */
172
- static keyValue(entries) {
173
- const width = Math.max(0, ...entries.map(([k]) => k.length));
174
- return entries.map(([k, v]) => `${k.padEnd(width)} ${v}`).join("\n");
175
- }
176
- /** Best-effort human-readable rendering of arbitrary data. */
177
- static auto(data) {
178
- if (Array.isArray(data)) {
179
- return _Formatter.arrayTable(data);
180
- }
181
- if (data && typeof data === "object") {
182
- const obj = data;
183
- const scalarEntries = [];
184
- const tableSections = [];
185
- for (const [k, v] of Object.entries(obj)) {
186
- if (Array.isArray(v) && v.some((i) => i && typeof i === "object")) {
187
- tableSections.push(`${k}:
188
- ${_Formatter.arrayTable(v)}`);
189
- } else {
190
- scalarEntries.push([k, stringify(v)]);
191
- }
192
- }
193
- const parts = [];
194
- if (scalarEntries.length) parts.push(_Formatter.keyValue(scalarEntries));
195
- parts.push(...tableSections);
196
- return parts.join("\n\n");
197
- }
198
- return stringify(data);
199
- }
200
- /** Render an array of objects as an aligned table; falls back gracefully. */
201
- static arrayTable(items) {
202
- if (items.length === 0) return "(empty)";
203
- const rows = items.filter(
204
- (i) => Boolean(i) && typeof i === "object"
205
- );
206
- if (rows.length === 0) {
207
- return items.map((i) => stringify(i)).join(", ");
208
- }
209
- const headers = Object.keys(rows[0]);
210
- return _Formatter.table(
211
- headers,
212
- rows.map((r) => headers.map((h) => stringify(r[h])))
213
- );
214
- }
215
- };
216
101
 
217
- // src/core/output.ts
218
- function emit(data, format = "json") {
219
- if (format === "table") {
220
- console.log(Formatter.auto(data));
221
- } else {
222
- console.log(JSON.stringify(data, null, 2));
102
+ // src/verb-schema.ts
103
+ var CLI_NAME = "agenzo-merchant-cli";
104
+ var NOUN = "ride-elife";
105
+ function wantsJsonSchema(argv = process.argv) {
106
+ for (let i = 0; i < argv.length; i++) {
107
+ const a = argv[i];
108
+ if (a === "--format=json") return true;
109
+ if (a === "--format" && argv[i + 1] === "json") return true;
223
110
  }
111
+ return false;
224
112
  }
225
- function emitNdjsonLine(data) {
226
- console.log(JSON.stringify(data));
113
+ function emitSchema(schema) {
114
+ console.log(JSON.stringify(schema, null, 2));
227
115
  }
228
- function emitSchema(schema, format = "json") {
229
- if (format === "table") {
230
- console.log(Formatter.keyValue(Object.entries(schema.params)));
231
- } else {
232
- console.log(JSON.stringify(schema, null, 2));
233
- }
116
+ function attachSchemaHelp(cmd, schema) {
117
+ const baseHelp = cmd.helpInformation.bind(cmd);
118
+ cmd.helpInformation = (context) => {
119
+ if (!wantsJsonSchema()) return baseHelp(context);
120
+ emitSchema(schema);
121
+ return "";
122
+ };
123
+ return cmd;
234
124
  }
235
-
236
- // src/core/version.ts
237
- import { readFileSync as readFileSync2 } from "fs";
238
- import { dirname, join as join2 } from "path";
239
- import { fileURLToPath } from "url";
240
- var UPGRADE_COMMAND = "npm install -g agenzo-merchant-cli@latest";
241
- var FALLBACK_VERSION = "0.1.0";
242
- var cached = null;
243
- function getCurrentVersion() {
244
- if (cached !== null) return cached;
245
- const here = dirname(fileURLToPath(import.meta.url));
246
- const candidates = [
247
- join2(here, "..", "package.json"),
248
- join2(here, "..", "..", "package.json")
249
- ];
250
- for (const p of candidates) {
251
- try {
252
- const pkg = JSON.parse(readFileSync2(p, "utf-8"));
253
- if (typeof pkg.version === "string") {
254
- cached = pkg.version;
255
- return cached;
125
+ var quoteSchema = {
126
+ cli: CLI_NAME,
127
+ noun: NOUN,
128
+ verb: "quote",
129
+ description: "Request fare quotes for a ride between two points",
130
+ flags: {
131
+ "pickup-lat": { type: "float", required: true, description: "Pickup latitude", constraints: "-90 to 90" },
132
+ "pickup-lng": { type: "float", required: true, description: "Pickup longitude", constraints: "-180 to 180" },
133
+ "pickup-name": { type: "string", required: true, description: "Pickup location name" },
134
+ "dropoff-lat": { type: "float", required: true, description: "Dropoff latitude", constraints: "-90 to 90" },
135
+ "dropoff-lng": { type: "float", required: true, description: "Dropoff longitude", constraints: "-180 to 180" },
136
+ "dropoff-name": { type: "string", required: true, description: "Dropoff location name" },
137
+ "pickup-time": {
138
+ type: "int|string",
139
+ required: true,
140
+ description: "Epoch seconds or the literal 'now'"
141
+ },
142
+ "passenger-name": { type: "string", required: false, description: "Lead passenger full name" },
143
+ "passenger-phone": { type: "string", required: false, description: "Passenger phone in E.164 format (e.g. +14155551234)" },
144
+ "passenger-count": { type: "int", required: false, description: "Number of passengers" },
145
+ "luggage-count": { type: "int", required: false, description: "Number of luggage items" },
146
+ "passenger-email": { type: "string", required: false, description: "Passenger email address" },
147
+ "children-count": { type: "int", required: false, description: "Number of children" }
148
+ },
149
+ response: {
150
+ vehicle_classes: {
151
+ type: "array",
152
+ description: "Available vehicle options with pricing",
153
+ items: {
154
+ vehicle_class: { type: "string", description: "Sedan / SUV / MPV-5 / MPV-7 / Van / Luxury / Train" },
155
+ "price.amount": { type: "float", description: "Price in currency units (e.g. 42.50), NOT cents" },
156
+ "price.currency": { type: "string", description: "ISO 4217 (e.g. USD)" },
157
+ "price.quote_id": { type: "string", description: "Use this in `ride-elife book --quote-id`" },
158
+ passenger_capacity: { type: "int", description: "Max passengers" },
159
+ luggage_capacity: { type: "int", description: "Max luggage items" }
256
160
  }
257
- } catch {
258
- }
259
- }
260
- cached = FALLBACK_VERSION;
261
- return cached;
262
- }
263
- function compareVersion(a, b) {
264
- const parse = (v) => v.trim().replace(/^v/, "").split(/[-+]/)[0].split(".").map((n) => parseInt(n, 10) || 0);
265
- const pa = parse(a);
266
- const pb = parse(b);
267
- for (let i = 0; i < 3; i++) {
268
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
269
- if (diff !== 0) return diff;
270
- }
271
- return 0;
272
- }
273
- function enforceMinVersion(header) {
274
- const min = header?.trim();
275
- if (!min) return;
276
- const current = getCurrentVersion();
277
- if (compareVersion(current, min) < 0) {
278
- throw new CliError(
279
- ErrorCodes.CLI_OUTDATED,
280
- `CLI ${current} is below the required minimum ${min}.`,
281
- `Upgrade: ${UPGRADE_COMMAND}`,
282
- 2
283
- );
161
+ },
162
+ meet_and_greet: { type: "object|null", description: "Meet & greet availability and price" },
163
+ is_airport_transfer: { type: "bool", description: "Whether this is an airport transfer" },
164
+ airport_direction: { type: "string|null", description: "pickup / dropoff / null" }
165
+ },
166
+ example: {
167
+ command: 'agenzo-merchant-cli ride-elife quote --pickup-lat 37.7937 --pickup-lng -122.3956 --pickup-name "1 Market St" --dropoff-lat 37.6213 --dropoff-lng -122.3790 --dropoff-name "SFO Airport" --pickup-time now',
168
+ output_summary: "Returns vehicle_classes[] with a quote_id per option. Use quote_id and vehicle_class for booking."
169
+ },
170
+ error_recovery: {
171
+ VEHICLE_UNAVAILABLE: "No vehicles for this route/time. Try a different pickup-time or location. Do NOT blindly retry.",
172
+ PARAM_INVALID: "Fix the offending flag (coordinates must be numbers; required pickup/dropoff/pickup-time present), then retry."
284
173
  }
285
- }
286
-
287
- // src/ride/quote.ts
288
- import { Command } from "commander";
289
-
290
- // src/core/api-client.ts
291
- var RIDE_PREFIX = "/api/v3/agent-pay";
292
- var ApiClient = class {
293
- host;
294
- apiKey;
295
- timeout;
296
- constructor(options) {
297
- this.apiKey = options.apiKey;
298
- this.host = (options.host ?? getHost()).replace(/\/+$/, "");
299
- this.timeout = options.timeout ?? 3e4;
300
- }
301
- get(path, options = {}) {
302
- return this.request("GET", path, options);
303
- }
304
- post(path, options = {}) {
305
- return this.request("POST", path, options);
306
- }
307
- async request(method, path, options) {
308
- let url = `${this.host}${RIDE_PREFIX}${path}`;
309
- if (options.query) {
310
- const params = new URLSearchParams();
311
- for (const [key, value] of Object.entries(options.query)) {
312
- if (value !== void 0) params.set(key, String(value));
313
- }
314
- const qs = params.toString();
315
- if (qs) url += `?${qs}`;
316
- }
317
- const headers = {
318
- "X-Api-Key": this.apiKey,
319
- "User-Agent": `agenzo-merchant-cli/${getCurrentVersion()}`
320
- };
321
- if (options.body) headers["Content-Type"] = "application/json";
322
- if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
323
- const controller = new AbortController();
324
- const timer = setTimeout(() => controller.abort(), this.timeout);
325
- let response;
326
- try {
327
- response = await fetch(url, {
328
- method,
329
- headers,
330
- body: options.body ? JSON.stringify(options.body) : void 0,
331
- signal: controller.signal
332
- });
333
- } catch (error) {
334
- if (error instanceof Error && error.name === "AbortError") {
335
- throw new CliError(
336
- ErrorCodes.TIMEOUT,
337
- `Request timed out after ${this.timeout}ms: ${url}`,
338
- "Retry, or check your network connection."
339
- );
340
- }
341
- throw new CliError(
342
- ErrorCodes.NETWORK_ERROR,
343
- `Network request failed: ${url}`,
344
- "Check your network connection and the configured API host."
345
- );
346
- } finally {
347
- clearTimeout(timer);
348
- }
349
- enforceMinVersion(response.headers.get("x-cli-min-version"));
350
- const text = await response.text();
351
- let envelope;
352
- try {
353
- envelope = JSON.parse(text);
354
- } catch {
355
- throw new CliError(
356
- ErrorCodes.NETWORK_ERROR,
357
- `Unexpected non-JSON response (HTTP ${response.status}) from ${url}`
358
- );
359
- }
360
- if (envelope.code !== void 0 && envelope.code !== "0000") {
361
- const code = envelope.code;
362
- const message = envelope.message ?? `Request failed (HTTP ${response.status})`;
363
- throw new CliError(code, message);
174
+ };
175
+ var bookSchema = {
176
+ cli: CLI_NAME,
177
+ noun: NOUN,
178
+ verb: "book",
179
+ description: "Book a ride using a quote_id returned by a previous quote call",
180
+ flags: {
181
+ "quote-id": { type: "string", required: true, description: "Quote id from quote response (vehicle_classes[].price.quote_id)" },
182
+ "vehicle-class": { type: "string", required: true, description: "Vehicle class: Sedan / SUV / MPV-5 / MPV-7 / Van / Luxury / Train" },
183
+ "price-amount": { type: "float", required: true, description: "Fare in decimal currency units (NOT cents), from the quote" },
184
+ "price-currency": { type: "string", required: false, default: "USD", description: "ISO 4217 currency code" },
185
+ "payment-order-id": {
186
+ type: "string",
187
+ required: "conditional",
188
+ description: "Required when billing_mode=pay_per_call (a PAID payment order). Forbidden when billing_mode=monthly_settlement"
189
+ },
190
+ "passenger-name": { type: "string", required: true, description: "Passenger full name" },
191
+ "passenger-phone": { type: "string", required: true, description: "Passenger phone in E.164 format (e.g. +14155551234)" },
192
+ "passenger-email": { type: "string", required: true, description: "Passenger email (required by eLife for booking)" },
193
+ "luggage-count": { type: "int", required: false, description: "Number of luggage items" },
194
+ "special-requests": { type: "string", required: false, description: "Free-text special requests or notes" },
195
+ "pickup-lat": { type: "float", required: false, description: "Pickup latitude (must match the quote)", constraints: "-90 to 90" },
196
+ "pickup-lng": { type: "float", required: false, description: "Pickup longitude (must match the quote)", constraints: "-180 to 180" },
197
+ "pickup-name": { type: "string", required: false, description: "Pickup location name (must match the quote)" },
198
+ "dropoff-lat": { type: "float", required: false, description: "Dropoff latitude (must match the quote)", constraints: "-90 to 90" },
199
+ "dropoff-lng": { type: "float", required: false, description: "Dropoff longitude (must match the quote)", constraints: "-180 to 180" },
200
+ "dropoff-name": { type: "string", required: false, description: "Dropoff location name (must match the quote)" },
201
+ "pickup-time": { type: "int|string", required: false, description: "Epoch seconds or 'now' (must match the quote)" },
202
+ "meet-and-greet": { type: "bool", required: false, default: false, description: "Enable meet & greet service" },
203
+ "meet-and-greet-price": { type: "float", required: false, description: "Meet & greet surcharge from the quote (meet_and_greet.price.amount)" },
204
+ "welcome-sign": { type: "string", required: false, description: "Welcome sign text (meet & greet)" },
205
+ "child-seat-count": { type: "int", required: false, description: "Number of child seats needed", constraints: "0 to 5" },
206
+ "infant-seat-count": { type: "int", required: false, description: "Number of infant seats needed", constraints: "0 to 5" },
207
+ "toddler-seat-count": { type: "int", required: false, description: "Number of toddler seats needed", constraints: "0 to 5" },
208
+ "arrival-flight-no": { type: "string", required: false, description: "Arrival flight number (airport pickup)" },
209
+ "arrival-airline": { type: "string", required: false, description: "Arrival airline name" },
210
+ "departure-flight-no": { type: "string", required: false, description: "Departure flight number (airport dropoff)" },
211
+ "departure-airline": { type: "string", required: false, description: "Departure airline name" },
212
+ "idempotency-key": {
213
+ type: "string",
214
+ required: true,
215
+ description: "Unique key (1-128 chars [A-Za-z0-9_-]) forwarded verbatim as the Idempotency-Key header; never auto-generated"
364
216
  }
365
- return envelope.data;
217
+ },
218
+ response: {
219
+ ride_id: { type: "string|int", description: "Ride identifier \u2014 use for `ride-elife get` / `ride-elife cancel`" },
220
+ order_id: { type: "string|int", description: "Internal order id for reconciliation" },
221
+ status: { type: "string", description: "Ride status (case-sensitive server casing)" },
222
+ is_scheduled: { type: "bool", description: "true = scheduled/airport ride; false = realtime" },
223
+ order_type: { type: "string", description: "'realtime' or 'airport'" },
224
+ price: { type: "object", description: "{ amount, currency, quote_id }" },
225
+ payment_status: { type: "string", description: "PAID (pay_per_call) or ON_ACCOUNT (monthly_settlement)" },
226
+ billing_entry_id: { type: "string|absent", description: "Only in monthly_settlement mode" },
227
+ payment_order_id: { type: "string|absent", description: "Only in pay_per_call mode (echoes the request)" }
228
+ },
229
+ example: {
230
+ command: 'agenzo-merchant-cli ride-elife book --quote-id qte_01HZXD --vehicle-class Sedan --price-amount 42.50 --passenger-name "Alice" --passenger-phone +14155551234 --idempotency-key book-123',
231
+ output_summary: "Returns ride_id. Use ride_id in `ride-elife get` to poll status."
232
+ },
233
+ error_recovery: {
234
+ QUOTE_EXPIRED: "Re-invoke 'quote' for a fresh quote_id, then retry 'book' with the SAME --idempotency-key.",
235
+ BILLING_MODE_MISMATCH: "Check Developer.billing_mode. pay_per_call requires --payment-order-id; monthly_settlement forbids it.",
236
+ PAYMENT_ORDER_NOT_PAID: "Wait for the payment order status=PAID, then retry with the SAME --idempotency-key.",
237
+ PAYMENT_ORDER_ALREADY_CONSUMED: "Create a new payment order, then retry with the new payment-order-id and a NEW --idempotency-key.",
238
+ ACCOUNT_INSUFFICIENT_BALANCE: "Top up the settlement account (offline) or pick a cheaper vehicle_class, then retry.",
239
+ BOOKING_FAILED: "elife rejected after settlement. The message contains a ref: for manual reconciliation. Do NOT auto-retry.",
240
+ PARAM_IDEMPOTENCY_KEY_REQUIRED: "Supply --idempotency-key (1-128 chars [A-Za-z0-9_-]); the CLI never generates one under --yes."
366
241
  }
367
242
  };
368
-
369
- // src/core/prompt-engine.ts
370
- import { confirm, input, password, select } from "@inquirer/prompts";
371
- var PromptEngine = class {
372
- static async resolveInput(flagValue, options) {
373
- if (flagValue !== void 0) return flagValue;
374
- if (options.type === "password") {
375
- return password({ message: options.message, mask: "*" });
376
- }
377
- if (options.type === "select" && options.choices) {
378
- return select({ message: options.message, choices: options.choices });
243
+ var rideGetSchema = {
244
+ cli: CLI_NAME,
245
+ noun: NOUN,
246
+ verb: "get",
247
+ description: "Retrieve a ride order status by id; with --watch, poll until a terminal status",
248
+ flags: {
249
+ "order-id": { type: "string", required: true, description: "The numeric ride_id (e.g. 4112961) \u2014 use the `ride_id` field from book/list-orders responses, NOT the rio_... order_id" },
250
+ watch: { type: "bool", required: false, default: false, description: "Poll until a terminal status, emitting one NDJSON line per update" },
251
+ "watch-interval": { type: "int", required: false, default: DEFAULT_WATCH_INTERVAL_SECONDS, description: "Seconds between polls when --watch is set" },
252
+ "watch-timeout": { type: "int", required: false, default: 600, description: "Max seconds to poll before giving up" }
253
+ },
254
+ response: {
255
+ ride_id: { type: "string|int", description: "Ride identifier" },
256
+ status: { type: "string", description: "Pending / Accepted / On my way / Waiting / On board / At destination / Rejected / Cancelled / Customer no show / Driver no show" },
257
+ source: { type: "string", description: "'local_cache' when served from the local fallback; 'mock' in sandbox" },
258
+ is_scheduled: { type: "bool", description: "true = scheduled/airport ride" },
259
+ from_location: { type: "object", description: "Pickup { lat, lng, name, address } (v3 snake_case, NOT elife from/to)" },
260
+ to_location: { type: "object", description: "Dropoff { lat, lng, name, address }" },
261
+ pickup_time: { type: "int|string", description: "Pickup time" },
262
+ vehicle_class: { type: "string|null", description: "Vehicle class" },
263
+ price: { type: "object", description: "{ amount, currency, quote_id }" },
264
+ final_amount: { type: "float|null", description: "Final settled fare (realtime, after final-fare settlement)" },
265
+ final_settlement_status: { type: "string", description: "pending / settled / no_adjustment / settlement_pending / not_applicable" },
266
+ driver: { type: "object|null", description: "{ name, phone_number } \u2014 available after a driver is assigned" },
267
+ vehicle: { type: "object|null", description: "{ make, model, color, license_plate, image_url } \u2014 available after a driver is assigned" },
268
+ created_at: { type: "string", description: "Order creation time" }
269
+ },
270
+ example: {
271
+ command: "agenzo-merchant-cli ride-elife get --order-id EL-20260527-1023456",
272
+ output_summary: "Returns current status and driver/vehicle info. Terminal statuses: At destination, Cancelled, Rejected, Customer no show, Driver no show."
273
+ },
274
+ polling: {
275
+ recommended_interval_seconds: DEFAULT_WATCH_INTERVAL_SECONDS,
276
+ terminal_statuses: [...TERMINAL_STATUSES],
277
+ in_progress_statuses: ["Pending", "Accepted", "On my way", "Waiting", "On board"],
278
+ field_availability: {
279
+ driver: "Populated from status 'Accepted' onwards (after a driver is dispatched). Null in 'Pending'.",
280
+ vehicle: "Populated from status 'Accepted' onwards. Null in 'Pending'."
379
281
  }
380
- return input({ message: options.message, validate: options.validate });
381
- }
382
- /** Confirmation prompt; auto-approves when `--yes` is set. */
383
- static async confirm(message, yes) {
384
- if (yes) return true;
385
- return confirm({ message });
282
+ },
283
+ error_recovery: {
284
+ VEHICLE_UNAVAILABLE: "Order not found. Verify --order-id is the ride_id from book. Do NOT retry.",
285
+ BOOKING_FAILED: "Transient elife error. Retry once after 5s."
386
286
  }
387
287
  };
388
-
389
- // src/utils/cli-help.ts
390
- function explicitFormat() {
391
- const argv = process.argv;
392
- for (let i = 0; i < argv.length; i++) {
393
- const a = argv[i];
394
- if (a === "--format=json" || a === "--format" && argv[i + 1] === "json") return "json";
395
- if (a === "--format=table" || a === "--format" && argv[i + 1] === "table") return "table";
288
+ var cancelSchema = {
289
+ cli: CLI_NAME,
290
+ noun: NOUN,
291
+ verb: "cancel",
292
+ description: "Cancel a ride order by id (a cancellation fee may apply depending on ride status)",
293
+ flags: {
294
+ "order-id": { type: "string", required: true, description: "The numeric ride_id (e.g. 4112961) to cancel \u2014 use the `ride_id` field from book/list-orders responses, NOT the rio_... order_id" },
295
+ "idempotency-key": {
296
+ type: "string",
297
+ required: true,
298
+ description: "Unique key (1-128 chars [A-Za-z0-9_-]) forwarded verbatim as the Idempotency-Key header; never auto-generated"
299
+ }
300
+ },
301
+ response: {
302
+ ride_id: { type: "string|int", description: "Cancelled ride id" },
303
+ ride_stat: { type: "string", description: "Status after cancellation (typically 'Cancelled')" },
304
+ cancellation: {
305
+ type: "object|null",
306
+ description: "Cancellation details",
307
+ properties: {
308
+ cancellation_fee: { type: "float", description: "Fee charged for cancellation" },
309
+ reversal_amount: { type: "float", description: "Amount reversed/refunded" },
310
+ currency: { type: "string", description: "ISO 4217 currency code" }
311
+ }
312
+ },
313
+ refund_amount: { type: "float", description: "Amount credited back to the settlement balance (paid \u2212 cancellation_fee)" }
314
+ },
315
+ example: {
316
+ command: "agenzo-merchant-cli ride-elife cancel --order-id EL-20260527-1023456 --idempotency-key cancel-456",
317
+ output_summary: "Returns cancellation fee and refund amount."
318
+ },
319
+ error_recovery: {
320
+ CANCELLATION_NOT_ALLOWED: "The ride is in a non-cancellable state. Do NOT retry; query status with `ride-elife get`.",
321
+ PARAM_IDEMPOTENCY_KEY_REQUIRED: "Supply --idempotency-key (1-128 chars [A-Za-z0-9_-]); the CLI never generates one under --yes."
396
322
  }
397
- return void 0;
398
- }
399
- function attachSchemaHelp(cmd, schema) {
400
- const baseHelp = cmd.helpInformation.bind(cmd);
401
- cmd.helpInformation = (ctx) => {
402
- const format = explicitFormat();
403
- if (!format) return baseHelp(ctx);
404
- emitSchema(schema, format);
405
- return "";
406
- };
407
- return cmd;
408
- }
409
-
410
- // src/ride/quote.ts
411
- var quoteSchema = {
412
- description: "Request fare quotes for a ride between two points.",
413
- params: {
414
- "pickup-lat": "number \u2014 pickup latitude",
415
- "pickup-lng": "number \u2014 pickup longitude",
416
- "pickup-name": "string \u2014 pickup location name",
417
- "dropoff-lat": "number \u2014 dropoff latitude",
418
- "dropoff-lng": "number \u2014 dropoff longitude",
419
- "dropoff-name": "string \u2014 dropoff location name",
420
- "pickup-time": 'number|string \u2014 epoch seconds, or "now"',
421
- "passenger-name": "string \u2014 optional, lead passenger full name",
422
- "passenger-phone": "string \u2014 optional, passenger phone in E.164 format",
423
- "passenger-count": "number \u2014 optional passenger count",
424
- "luggage-count": "number \u2014 optional luggage count",
425
- "passenger-email": "string \u2014 optional passenger email",
426
- "children-count": "number \u2014 optional children count"
323
+ };
324
+ var listOrdersSchema = {
325
+ cli: CLI_NAME,
326
+ noun: NOUN,
327
+ verb: "list-orders",
328
+ description: "List previously placed ride orders with optional filters and pagination",
329
+ flags: {
330
+ status: { type: "string", required: false, description: "Filter by ride status (case-sensitive server casing)" },
331
+ "order-type": { type: "string", required: false, description: "Filter by order type (e.g. realtime / airport)" },
332
+ page: { type: "int", required: false, default: 1, description: "Page number", constraints: ">= 1" },
333
+ "page-size": { type: "int", required: false, default: 20, description: "Items per page", constraints: ">= 1" }
427
334
  },
428
335
  response: {
429
- vehicle_classes: "VehicleClass[] \u2014 each: vehicle_class, price{amount,currency,quote_id}, passenger_capacity, luggage_capacity (amount is a decimal in the currency standard unit, e.g. 42.50 = $42.50)",
430
- meet_and_greet: "object|null \u2014 { available: boolean, price: { amount, currency } } when applicable",
431
- is_airport_transfer: "boolean",
432
- airport_direction: "string|null \u2014 pickup/dropoff direction for airport transfers"
336
+ orders: {
337
+ type: "array",
338
+ description: "List of ride orders (slim list-item shape)",
339
+ items: {
340
+ order_id: { type: "string", description: "Internal order id" },
341
+ ride_id: { type: "string", description: "Ride id (same as ride_id from book)" },
342
+ status: { type: "string", description: "Current ride status" },
343
+ vehicle_class: { type: "string", description: "Vehicle class" },
344
+ is_scheduled: { type: "bool", description: "true = scheduled/airport, false = realtime" },
345
+ scheduled_at: { type: "string", description: "ISO 8601 datetime; empty for realtime orders" },
346
+ price_amount: { type: "float|null", description: "Price in decimal currency units (NOT cents)" },
347
+ final_amount: { type: "float|null", description: "Final settled amount" },
348
+ price_currency: { type: "string", description: "ISO 4217 currency code" },
349
+ payment_status: { type: "string", description: "Payment status" },
350
+ final_settlement_status: { type: "string", description: "pending / settled / no_adjustment / settlement_pending / not_applicable" },
351
+ cancellation_fee: { type: "float|null", description: "Cancellation fee for cancelled orders; null otherwise" },
352
+ provider: { type: "string", description: "Service provider (elife)" },
353
+ created_at: { type: "string|null", description: "Order creation time (ISO 8601)" },
354
+ updated_at: { type: "string|null", description: "Last update time (ISO 8601)" }
355
+ }
356
+ },
357
+ total: { type: "int", description: "Total matching orders" },
358
+ page: { type: "int", description: "Current page" },
359
+ page_size: { type: "int", description: "Items per page" }
360
+ },
361
+ example: {
362
+ command: "agenzo-merchant-cli ride-elife list-orders --status Pending --page 1 --page-size 10",
363
+ output_summary: "Returns a paginated list of ride orders with status and amount info."
364
+ },
365
+ error_recovery: {
366
+ INTERNAL_ERROR: "Transient backend error. Retry once after a short delay.",
367
+ PARAM_INVALID: "Ensure --page / --page-size are positive integers, then retry."
433
368
  }
434
369
  };
370
+
371
+ // src/ride-elife/quote.ts
435
372
  function need(value, flag) {
436
- if (value === void 0) throw new CliError("PARAM_REQUIRED", `Missing required --${flag}.`);
373
+ if (value === void 0) {
374
+ throw new CliError2("PARAM_INVALID", `Missing required --${flag}.`);
375
+ }
437
376
  return value;
438
377
  }
439
378
  function num(value, flag) {
440
379
  const n = Number(need(value, flag));
441
- if (!Number.isFinite(n)) throw new CliError("PARAM_INVALID", `--${flag} must be a number.`);
380
+ if (!Number.isFinite(n)) {
381
+ throw new CliError2("PARAM_INVALID", `--${flag} must be a number.`);
382
+ }
442
383
  return n;
443
384
  }
444
- function buildQuoteCommand() {
445
- const cmd = new Command("quote").description(quoteSchema.description).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").action(async (_opts, command) => {
446
- const o = command.optsWithGlobals();
447
- const format = o.format === "table" ? "table" : "json";
448
- const apiKey = await PromptEngine.resolveInput(o.apiKey, {
449
- message: "API key:",
385
+ function formatQuote(data) {
386
+ const classes = data.vehicle_classes ?? [];
387
+ if (classes.length === 0) {
388
+ return Formatter.status("info", "No vehicle classes available for this route");
389
+ }
390
+ const headers = ["Vehicle Class", "Price", "Currency", "Quote ID", "Pax", "Luggage"];
391
+ const rows = classes.map((vc) => [
392
+ String(vc.vehicle_class ?? "-"),
393
+ String(vc.price?.amount ?? "-"),
394
+ String(vc.price?.currency ?? "-"),
395
+ String(vc.price?.quote_id ?? "-"),
396
+ String(vc.passenger_capacity ?? "-"),
397
+ String(vc.luggage_capacity ?? "-")
398
+ ]);
399
+ const lines = [
400
+ ["Airport transfer", String(data.is_airport_transfer ?? false)]
401
+ ];
402
+ if (data.airport_direction) {
403
+ lines.push(["Airport direction", String(data.airport_direction)]);
404
+ }
405
+ if (data.meet_and_greet) {
406
+ const mg = data.meet_and_greet;
407
+ const mgPrice = mg.price ? ` (${mg.price.amount} ${mg.price.currency})` : "";
408
+ lines.push(["Meet & greet", `${mg.available ? "available" : "unavailable"}${mgPrice}`]);
409
+ }
410
+ return `${Formatter.table(headers, rows)}
411
+ ${Formatter.keyValue(lines)}`;
412
+ }
413
+ function registerQuoteCommand(parent, deps) {
414
+ 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");
415
+ attachSchemaHelp(cmd, quoteSchema);
416
+ cmd.action(async () => {
417
+ const opts = cmd.optsWithGlobals();
418
+ const format = resolveFormat(opts.format);
419
+ const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
420
+ message: "API Key:",
450
421
  type: "password"
451
422
  });
452
423
  const body = {
453
424
  pickup: {
454
- lat: num(o.pickupLat, "pickup-lat"),
455
- lng: num(o.pickupLng, "pickup-lng"),
456
- name: need(o.pickupName, "pickup-name")
425
+ lat: num(opts.pickupLat, "pickup-lat"),
426
+ lng: num(opts.pickupLng, "pickup-lng"),
427
+ name: need(opts.pickupName, "pickup-name")
457
428
  },
458
429
  dropoff: {
459
- lat: num(o.dropoffLat, "dropoff-lat"),
460
- lng: num(o.dropoffLng, "dropoff-lng"),
461
- name: need(o.dropoffName, "dropoff-name")
430
+ lat: num(opts.dropoffLat, "dropoff-lat"),
431
+ lng: num(opts.dropoffLng, "dropoff-lng"),
432
+ name: need(opts.dropoffName, "dropoff-name")
462
433
  },
463
- pickup_time: o.pickupTime === "now" ? "now" : num(o.pickupTime, "pickup-time")
434
+ pickup_time: opts.pickupTime === "now" ? "now" : num(opts.pickupTime, "pickup-time")
464
435
  };
465
- if (o.passengerName) body.passenger_name = o.passengerName;
466
- if (o.passengerPhone) body.passenger_phone = o.passengerPhone;
467
- if (o.passengerCount !== void 0) body.passenger_count = num(o.passengerCount, "passenger-count");
468
- if (o.luggageCount !== void 0) body.luggage_count = num(o.luggageCount, "luggage-count");
469
- if (o.childrenCount !== void 0) body.children_count = num(o.childrenCount, "children-count");
470
- if (o.passengerEmail !== void 0) body.passenger_email = o.passengerEmail;
471
- const client = new ApiClient({ apiKey });
472
- const spinner = createSpinner("Fetching quotes...");
473
- try {
474
- const data = await client.post("/ride/quote", { body });
475
- spinner.stop();
476
- emit(data, format);
477
- } catch (err) {
478
- spinner.fail("Quote request failed");
479
- throw err;
436
+ if (opts.passengerName) body.passenger_name = opts.passengerName;
437
+ if (opts.passengerPhone) body.passenger_phone = opts.passengerPhone;
438
+ if (opts.passengerCount !== void 0) {
439
+ body.passenger_count = num(opts.passengerCount, "passenger-count");
440
+ }
441
+ if (opts.luggageCount !== void 0) {
442
+ body.luggage_count = num(opts.luggageCount, "luggage-count");
443
+ }
444
+ if (opts.childrenCount !== void 0) {
445
+ body.children_count = num(opts.childrenCount, "children-count");
480
446
  }
447
+ if (opts.passengerEmail !== void 0) {
448
+ body.passenger_email = opts.passengerEmail;
449
+ }
450
+ notify(format, "loading", "Fetching quotes...");
451
+ const result = await deps.apiClient.post(
452
+ "/ride/quote",
453
+ { type: "api-key", key: apiKey },
454
+ body
455
+ );
456
+ if (!result.success) {
457
+ throw CliError2.fromApi(result, { auth: "api-key" });
458
+ }
459
+ const data = result.data;
460
+ const configManager = new ConfigManager();
461
+ const commandResult = {
462
+ data,
463
+ text: () => formatQuote(data)
464
+ };
465
+ await renderWithContext(commandResult, { format }, configManager);
481
466
  });
482
- return attachSchemaHelp(cmd, quoteSchema);
483
467
  }
484
468
 
485
- // src/ride/book.ts
486
- import { Command as Command2 } from "commander";
469
+ // src/ride-elife/book.ts
470
+ import { confirm } from "@inquirer/prompts";
471
+ import {
472
+ ConfigManager as ConfigManager2,
473
+ PromptEngine as PromptEngine3,
474
+ Formatter as Formatter2,
475
+ resolveFormat as resolveFormat2,
476
+ notify as notify2,
477
+ CliError as CliError4,
478
+ renderWithContext as renderWithContext2
479
+ } from "@agenzo/cli-core";
487
480
 
488
- // src/utils/idempotency.ts
489
- import { input as input2 } from "@inquirer/prompts";
490
- var KEY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
491
- var RULE = "Use 1-128 characters from [A-Za-z0-9_-].";
481
+ // src/idempotency.ts
482
+ import {
483
+ CliError as CliError3,
484
+ IdempotencyKeyRequiredError,
485
+ PromptEngine as PromptEngine2
486
+ } from "@agenzo/cli-core";
487
+ var IDEMPOTENCY_KEY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
488
+ var IDEMPOTENCY_KEY_RULE = "Use 1-128 characters from [A-Za-z0-9_-].";
492
489
  function normalizeIdempotencyKey(key) {
493
490
  const trimmed = key.trim();
494
- if (!KEY_PATTERN.test(trimmed)) {
495
- throw new CliError(
496
- ErrorCodes.PARAM_IDEMPOTENCY_KEY_REQUIRED,
497
- `Invalid idempotency key: "${key}".`,
498
- RULE
491
+ if (!IDEMPOTENCY_KEY_PATTERN.test(trimmed)) {
492
+ throw new CliError3(
493
+ "PARAM_INVALID",
494
+ `Invalid idempotency key: "${key}". ${IDEMPOTENCY_KEY_RULE}`
499
495
  );
500
496
  }
501
497
  return trimmed;
502
498
  }
503
- async function resolveIdempotencyKey(flagValue, options) {
504
- if (flagValue !== void 0) return normalizeIdempotencyKey(flagValue);
505
- if (options.yes) {
506
- throw new CliError(
507
- ErrorCodes.PARAM_IDEMPOTENCY_KEY_REQUIRED,
508
- "An idempotency key is required for write operations.",
509
- "Pass --idempotency-key <key>; the CLI never auto-generates one."
510
- );
499
+ async function resolveIdempotencyKey(flagValue, opts) {
500
+ if (flagValue !== void 0) {
501
+ return normalizeIdempotencyKey(flagValue);
502
+ }
503
+ if (opts.yes) {
504
+ throw new IdempotencyKeyRequiredError(opts.commandPath);
511
505
  }
512
- const entered = await input2({
513
- message: "Idempotency key:",
514
- validate: (v) => KEY_PATTERN.test(v.trim()) || RULE
506
+ const entered = await PromptEngine2.resolveInput(void 0, {
507
+ message: "Idempotency key (unique per write, for safe retry):",
508
+ validate: (v) => IDEMPOTENCY_KEY_PATTERN.test(v.trim()) || IDEMPOTENCY_KEY_RULE
515
509
  });
516
510
  return normalizeIdempotencyKey(entered);
517
511
  }
518
512
 
519
- // src/ride/book.ts
520
- var bookSchema = {
521
- description: "Book a ride against a previously returned quote.",
522
- params: {
523
- "quote-id": "string \u2014 required, quote id from `ride-elife quote`",
524
- "vehicle-class": "string \u2014 required, chosen vehicle class",
525
- "price-amount": "number \u2014 required, fare in decimal currency units (not cents)",
526
- "price-currency": "string \u2014 currency code (default USD)",
527
- "payment-order-id": "string \u2014 conditional. Required for pay_per_call billing (a PAID order from payment-cli); omit for monthly_settlement (deducted from the settlement account)",
528
- "passenger-name": "string \u2014 required, passenger full name",
529
- "passenger-phone": "string \u2014 required, passenger phone",
530
- "passenger-email": "string \u2014 optional passenger email",
531
- "luggage-count": "number \u2014 optional luggage count",
532
- "special-requests": "string \u2014 optional free-text requests",
533
- "pickup-lat": "number \u2014 required, pickup latitude",
534
- "pickup-lng": "number \u2014 required, pickup longitude",
535
- "pickup-name": "string \u2014 required, pickup location name",
536
- "dropoff-lat": "number \u2014 required, dropoff latitude",
537
- "dropoff-lng": "number \u2014 required, dropoff longitude",
538
- "dropoff-name": "string \u2014 required, dropoff location name",
539
- "pickup-time": 'number|string \u2014 required, epoch seconds or "now" (must match quote)',
540
- "meet-and-greet": "boolean \u2014 optional meet & greet service",
541
- "meet-and-greet-price": "number \u2014 optional meet & greet surcharge",
542
- "welcome-sign": "string \u2014 optional welcome sign text",
543
- "arrival-flight-no": "string \u2014 optional arrival flight number",
544
- "arrival-airline": "string \u2014 optional arrival airline",
545
- "departure-flight-no": "string \u2014 optional departure flight number",
546
- "departure-airline": "string \u2014 optional departure airline",
547
- "idempotency-key": "string \u2014 required, sent as the Idempotency-Key header (never in body)"
548
- },
549
- response: {
550
- ride_id: "string \u2014 booked ride id, used by `ride-elife get` / `ride-elife cancel`",
551
- order_id: "string \u2014 internal ride order id (rio_...)",
552
- status: "string \u2014 initial ride status (e.g. INIT / Pending)",
553
- is_scheduled: "boolean \u2014 true = scheduled/airport ride, false = realtime",
554
- order_type: "string \u2014 realtime / airport (derived from pickup-time)",
555
- price: "{ amount, currency, quote_id } \u2014 confirmed fare",
556
- payment_status: "string \u2014 ON_ACCOUNT (monthly_settlement) or PAID (pay_per_call)",
557
- billing_entry_id: "string \u2014 settlement ledger entry id (monthly_settlement only)"
513
+ // src/ride-elife/book.ts
514
+ function need2(value, flag) {
515
+ if (value === void 0) {
516
+ throw new CliError4("PARAM_INVALID", `Missing required --${flag}.`);
558
517
  }
559
- };
560
- function buildBookCommand() {
561
- return new Command2("book").description(bookSchema.description).helpOption(false).option("--help", "Show help (add --format json for the machine-readable schema)").option("--format <format>", "Output format: json|table").option("--api-key <key>", "API key for runtime requests").option("--yes", "Skip confirmation prompts").option("--quote-id <id>", "Quote id from `ride-elife quote`").option("--vehicle-class <class>", "Chosen vehicle class").option("--price-amount <amount>", "Fare in decimal currency units (not cents)").option("--price-currency <currency>", "Currency code (default USD)").option("--payment-order-id <id>", "Paid payment order id (pay_per_call mode only)").option("--passenger-name <name>", "Passenger full name").option("--passenger-phone <phone>", "Passenger phone").option("--passenger-email <email>", "Passenger email").option("--luggage-count <n>", "Luggage count").option("--special-requests <text>", "Free-text special requests").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("--meet-and-greet", "Enable meet & greet service").option("--meet-and-greet-price <amount>", "Meet & greet surcharge").option("--welcome-sign <text>", "Welcome sign text").option("--arrival-flight-no <no>", "Arrival flight number").option("--arrival-airline <airline>", "Arrival airline").option("--departure-flight-no <no>", "Departure flight number").option("--departure-airline <airline>", "Departure airline").option("--idempotency-key <key>", "Caller-supplied idempotency key").action(async (_options, command) => {
562
- const merged = command.optsWithGlobals();
563
- const format = merged.format === "table" ? "table" : "json";
564
- if (merged.help) {
565
- const wantsSchema = process.argv.some((a) => a === "--format" || a.startsWith("--format="));
566
- if (wantsSchema) emitSchema(bookSchema, format);
567
- else command.outputHelp();
568
- return;
569
- }
570
- const yes = merged.yes === true;
571
- const apiKey = await PromptEngine.resolveInput(merged.apiKey, {
572
- message: "API key:",
518
+ return value;
519
+ }
520
+ function num2(value, flag) {
521
+ const n = Number(need2(value, flag));
522
+ if (!Number.isFinite(n)) {
523
+ throw new CliError4("PARAM_INVALID", `--${flag} must be a number.`);
524
+ }
525
+ return n;
526
+ }
527
+ function seatCount(value, flag) {
528
+ const n = num2(value, flag);
529
+ if (!Number.isInteger(n) || n < 0 || n > 5) {
530
+ throw new CliError4("PARAM_INVALID", `--${flag} must be an integer between 0 and 5.`);
531
+ }
532
+ return n;
533
+ }
534
+ function formatBook(data) {
535
+ const lines = [
536
+ ["Ride ID", String(data.ride_id ?? "-")],
537
+ ["Order ID", String(data.order_id ?? "-")],
538
+ ["Status", String(data.status ?? "-")],
539
+ ["Scheduled", String(data.is_scheduled ?? false)],
540
+ ["Order type", String(data.order_type ?? "-")]
541
+ ];
542
+ if (data.price) {
543
+ lines.push(["Price", `${data.price.amount} ${data.price.currency}`]);
544
+ if (data.price.quote_id) lines.push(["Quote ID", String(data.price.quote_id)]);
545
+ }
546
+ lines.push(["Payment status", String(data.payment_status ?? "-")]);
547
+ if (data.billing_entry_id) lines.push(["Billing entry", String(data.billing_entry_id)]);
548
+ if (data.payment_order_id) lines.push(["Payment order", String(data.payment_order_id)]);
549
+ return Formatter2.keyValue(lines);
550
+ }
551
+ function registerBookCommand(parent, deps) {
552
+ const cmd = parent.command("book").description("Book a ride using a quote_id returned by quote").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--quote-id <id>", "Quote id from `ride-elife quote`").option("--vehicle-class <class>", "Chosen vehicle class").option("--price-amount <amount>", "Fare in decimal currency units (not cents)").option("--price-currency <currency>", "Currency code (default USD)").option("--payment-order-id <id>", "Paid payment order id (pay_per_call mode only)").option("--passenger-name <name>", "Passenger full name").option("--passenger-phone <phone>", "Passenger phone").option("--passenger-email <email>", "Passenger email").option("--luggage-count <n>", "Luggage count").option("--special-requests <text>", "Free-text special requests").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("--meet-and-greet", "Enable meet & greet service").option("--meet-and-greet-price <amount>", "Meet & greet surcharge").option("--welcome-sign <text>", "Welcome sign text").option("--child-seat-count <n>", "Number of child seats needed (0-5)").option("--infant-seat-count <n>", "Number of infant seats needed (0-5)").option("--toddler-seat-count <n>", "Number of toddler seats needed (0-5)").option("--arrival-flight-no <no>", "Arrival flight number").option("--arrival-airline <airline>", "Arrival airline").option("--departure-flight-no <no>", "Departure flight number").option("--departure-airline <airline>", "Departure airline").option(
553
+ "--idempotency-key <key>",
554
+ "Idempotency key forwarded verbatim as the Idempotency-Key header"
555
+ );
556
+ attachSchemaHelp(cmd, bookSchema);
557
+ cmd.action(async () => {
558
+ const opts = cmd.optsWithGlobals();
559
+ const format = resolveFormat2(opts.format);
560
+ const isYes = Boolean(opts.yes);
561
+ const apiKey = await PromptEngine3.resolveInput(opts.apiKey, {
562
+ message: "API Key:",
573
563
  type: "password"
574
564
  });
575
- const quoteId = await PromptEngine.resolveInput(merged.quoteId, { message: "Quote id:" });
576
- const vehicleClass = await PromptEngine.resolveInput(merged.vehicleClass, {
577
- message: "Vehicle class:"
578
- });
579
- const priceAmount = await PromptEngine.resolveInput(merged.priceAmount, {
580
- message: "Price amount (decimal):"
581
- });
582
- const passengerName = await PromptEngine.resolveInput(merged.passengerName, {
583
- message: "Passenger name:"
584
- });
585
- const passengerPhone = await PromptEngine.resolveInput(merged.passengerPhone, {
586
- message: "Passenger phone:"
587
- });
565
+ const quoteId = need2(opts.quoteId, "quote-id");
588
566
  const body = {
589
567
  quote_id: quoteId,
590
- vehicle_class: vehicleClass,
591
- price_amount: Number(priceAmount),
592
- price_currency: merged.priceCurrency ?? "USD",
593
- passenger_name: passengerName,
594
- passenger_phone: passengerPhone
568
+ vehicle_class: need2(opts.vehicleClass, "vehicle-class"),
569
+ price_amount: num2(opts.priceAmount, "price-amount"),
570
+ price_currency: opts.priceCurrency ?? "USD",
571
+ passenger_name: need2(opts.passengerName, "passenger-name"),
572
+ passenger_phone: need2(opts.passengerPhone, "passenger-phone")
595
573
  };
596
- if (merged.paymentOrderId) body.payment_order_id = merged.paymentOrderId;
597
- if (merged.passengerEmail) body.passenger_email = merged.passengerEmail;
598
- if (merged.luggageCount !== void 0) body.luggage_count = Number(merged.luggageCount);
599
- if (merged.specialRequests) body.special_requests = merged.specialRequests;
600
- if (merged.pickupLat || merged.pickupLng || merged.pickupName) {
601
- body.pickup = { lat: Number(merged.pickupLat), lng: Number(merged.pickupLng), name: merged.pickupName };
574
+ if (opts.paymentOrderId) body.payment_order_id = opts.paymentOrderId;
575
+ if (opts.passengerEmail) body.passenger_email = opts.passengerEmail;
576
+ if (opts.luggageCount !== void 0) {
577
+ body.luggage_count = num2(opts.luggageCount, "luggage-count");
602
578
  }
603
- if (merged.dropoffLat || merged.dropoffLng || merged.dropoffName) {
604
- body.dropoff = { lat: Number(merged.dropoffLat), lng: Number(merged.dropoffLng), name: merged.dropoffName };
579
+ if (opts.specialRequests) body.special_requests = opts.specialRequests;
580
+ if (opts.pickupLat || opts.pickupLng || opts.pickupName) {
581
+ body.pickup = {
582
+ lat: num2(opts.pickupLat, "pickup-lat"),
583
+ lng: num2(opts.pickupLng, "pickup-lng"),
584
+ name: need2(opts.pickupName, "pickup-name")
585
+ };
605
586
  }
606
- if (merged.pickupTime) {
607
- body.pickup_time = merged.pickupTime === "now" ? "now" : Number(merged.pickupTime);
587
+ if (opts.dropoffLat || opts.dropoffLng || opts.dropoffName) {
588
+ body.dropoff = {
589
+ lat: num2(opts.dropoffLat, "dropoff-lat"),
590
+ lng: num2(opts.dropoffLng, "dropoff-lng"),
591
+ name: need2(opts.dropoffName, "dropoff-name")
592
+ };
608
593
  }
609
- if (merged.meetAndGreet) body.meet_and_greet = true;
610
- if (merged.meetAndGreetPrice !== void 0) body.meet_and_greet_price = Number(merged.meetAndGreetPrice);
611
- if (merged.welcomeSign) body.welcome_sign = merged.welcomeSign;
612
- if (merged.arrivalFlightNo || merged.arrivalAirline) {
613
- body.arrival_flight = { flight_no: merged.arrivalFlightNo, airline: merged.arrivalAirline };
594
+ if (opts.pickupTime) {
595
+ body.pickup_time = opts.pickupTime === "now" ? "now" : num2(opts.pickupTime, "pickup-time");
614
596
  }
615
- if (merged.departureFlightNo || merged.departureAirline) {
616
- body.departure_flight = { flight_no: merged.departureFlightNo, airline: merged.departureAirline };
597
+ if (opts.meetAndGreet) body.meet_and_greet = true;
598
+ if (opts.meetAndGreetPrice !== void 0) {
599
+ body.meet_and_greet_price = num2(opts.meetAndGreetPrice, "meet-and-greet-price");
617
600
  }
618
- if (!await PromptEngine.confirm(`Book ride with quote ${quoteId}?`, yes)) {
619
- console.log(Formatter.status("info", "Booking aborted."));
620
- return;
601
+ if (opts.welcomeSign) body.welcome_sign = opts.welcomeSign;
602
+ if (opts.childSeatCount !== void 0) {
603
+ body.child_seat_count = seatCount(opts.childSeatCount, "child-seat-count");
604
+ }
605
+ if (opts.infantSeatCount !== void 0) {
606
+ body.infant_seat_count = seatCount(opts.infantSeatCount, "infant-seat-count");
607
+ }
608
+ if (opts.toddlerSeatCount !== void 0) {
609
+ body.toddler_seat_count = seatCount(opts.toddlerSeatCount, "toddler-seat-count");
621
610
  }
622
- const idempotencyKey = await resolveIdempotencyKey(merged.idempotencyKey, { yes });
623
- const client = new ApiClient({ apiKey });
624
- const spinner = createSpinner("Booking ride...");
625
- try {
626
- const data = await client.post("/ride/book", { body, idempotencyKey });
627
- spinner.stop();
628
- emit(data, format);
629
- } catch (err) {
630
- spinner.fail("Booking failed");
631
- throw err;
611
+ if (opts.arrivalFlightNo || opts.arrivalAirline) {
612
+ body.arrival_flight = {
613
+ flight_no: opts.arrivalFlightNo,
614
+ airline: opts.arrivalAirline
615
+ };
632
616
  }
617
+ if (opts.departureFlightNo || opts.departureAirline) {
618
+ body.departure_flight = {
619
+ flight_no: opts.departureFlightNo,
620
+ airline: opts.departureAirline
621
+ };
622
+ }
623
+ if (!isYes) {
624
+ const confirmed = await confirm({
625
+ message: `Book ride with quote ${quoteId}?`,
626
+ default: true
627
+ });
628
+ if (!confirmed) {
629
+ throw new CliError4("CLIENT_ABORTED", "Booking cancelled by user.");
630
+ }
631
+ }
632
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
633
+ yes: isYes,
634
+ commandPath: "ride-elife book"
635
+ });
636
+ notify2(format, "loading", "Booking ride...");
637
+ const result = await deps.apiClient.post(
638
+ "/ride/book",
639
+ { type: "api-key", key: apiKey },
640
+ body,
641
+ { "Idempotency-Key": idempotencyKey }
642
+ );
643
+ if (!result.success) {
644
+ throw CliError4.fromApi(result, { auth: "api-key" });
645
+ }
646
+ const data = result.data;
647
+ const configManager = new ConfigManager2();
648
+ const commandResult = {
649
+ data,
650
+ text: () => formatBook(data)
651
+ };
652
+ await renderWithContext2(commandResult, { format }, configManager);
633
653
  });
634
654
  }
635
655
 
636
- // src/ride/get.ts
637
- import { Command as Command3 } from "commander";
638
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
639
- "At destination",
640
- "Cancelled",
641
- "Rejected",
642
- "Customer no show",
643
- "Driver no show"
644
- ]);
645
- var DEFAULT_WATCH_INTERVAL_SECONDS = 5;
646
- var DEFAULT_WATCH_TIMEOUT_SECONDS = 600;
647
- var rideGetSchema = {
648
- description: "Retrieve a single ride order status by id (repeatable for polling).",
649
- params: {
650
- "order-id": "string \u2014 ride order id (the ride_id returned by `ride-elife book`)",
651
- watch: "boolean \u2014 poll until a terminal status, emitting one NDJSON line per update (default off)",
652
- "watch-interval": `number \u2014 seconds between polls when --watch is set (default ${DEFAULT_WATCH_INTERVAL_SECONDS})`,
653
- "watch-timeout": `number \u2014 max seconds to poll before giving up (default ${DEFAULT_WATCH_TIMEOUT_SECONDS})`
654
- },
655
- response: {
656
- status: "OrderStatus \u2014 case-sensitive; terminal states: At destination | Cancelled | Rejected | Customer no show | Driver no show",
657
- from_location: "GeoPoint \u2014 pickup",
658
- to_location: "GeoPoint \u2014 dropoff",
659
- pickup_time: "number|string",
660
- vehicle_class: "string",
661
- price: "Price{amount,currency} \u2014 amount is a decimal in the currency standard unit",
662
- final_amount: "number \u2014 final settled fare (realtime, after final-fare settlement); local-cache path only",
663
- final_settlement_status: "string \u2014 pending | settled | no_adjustment | settlement_pending | not_applicable (local-cache path)",
664
- passenger: "Passenger",
665
- driver: "Driver \u2014 present after assignment",
666
- vehicle: "Vehicle \u2014 present after assignment"
656
+ // src/ride-elife/get.ts
657
+ import {
658
+ ConfigManager as ConfigManager3,
659
+ PromptEngine as PromptEngine4,
660
+ Formatter as Formatter3,
661
+ resolveFormat as resolveFormat3,
662
+ notify as notify3,
663
+ CliError as CliError5,
664
+ renderWithContext as renderWithContext3
665
+ } from "@agenzo/cli-core";
666
+ function need3(value, flag) {
667
+ if (value === void 0) {
668
+ throw new CliError5("PARAM_INVALID", `Missing required --${flag}.`);
667
669
  }
668
- };
669
- function num2(value, fallback) {
670
- if (value === void 0) return fallback;
671
- const n = Number(value);
672
- return Number.isFinite(n) && n > 0 ? n : fallback;
670
+ return value;
673
671
  }
674
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
675
- function statusOf(data) {
676
- return data.status;
677
- }
678
- function buildRideGetCommand() {
679
- const cmd = new Command3("get").description(rideGetSchema.description).option("--order-id <id>", "Ride order id (the ride_id returned by `ride-elife book`)").option("--watch", "Poll until a terminal status, emitting one NDJSON line per update").option("--watch-interval <seconds>", "Seconds between polls when --watch is set").option("--watch-timeout <seconds>", "Max seconds to poll before giving up").action(async (_opts, command) => {
680
- const o = command.optsWithGlobals();
681
- const format = o.format === "table" ? "table" : "json";
682
- const apiKey = await PromptEngine.resolveInput(o.apiKey, {
683
- message: "API key:",
672
+ function formatGetOrder(data) {
673
+ const lines = [
674
+ ["Ride ID", String(data.ride_id ?? "-")],
675
+ ["Status", String(data.status ?? "-")]
676
+ ];
677
+ if (data.source) lines.push(["Source", String(data.source)]);
678
+ if (data.is_scheduled !== void 0) lines.push(["Scheduled", String(data.is_scheduled)]);
679
+ if (data.from_location) {
680
+ lines.push(["From", String(data.from_location.name ?? "-")]);
681
+ }
682
+ if (data.to_location) {
683
+ lines.push(["To", String(data.to_location.name ?? "-")]);
684
+ }
685
+ if (data.pickup_time !== void 0) lines.push(["Pickup time", String(data.pickup_time)]);
686
+ if (data.vehicle_class) lines.push(["Vehicle class", String(data.vehicle_class)]);
687
+ if (data.price) {
688
+ lines.push(["Price", `${data.price.amount} ${data.price.currency}`]);
689
+ }
690
+ if (data.final_amount !== void 0 && data.final_amount !== null) {
691
+ lines.push(["Final amount", String(data.final_amount)]);
692
+ }
693
+ if (data.final_settlement_status) {
694
+ lines.push(["Settlement", String(data.final_settlement_status)]);
695
+ }
696
+ if (data.driver) {
697
+ const driver = data.driver;
698
+ const phone = driver.phone_number ? ` (${driver.phone_number})` : "";
699
+ lines.push(["Driver", `${driver.name ?? "-"}${phone}`]);
700
+ }
701
+ if (data.vehicle) {
702
+ const v = data.vehicle;
703
+ const desc = [v.color, v.make, v.model].filter(Boolean).join(" ") || "-";
704
+ const plate = v.license_plate ? ` [${v.license_plate}]` : "";
705
+ lines.push(["Vehicle", `${desc}${plate}`]);
706
+ }
707
+ if (data.created_at) lines.push(["Created at", String(data.created_at)]);
708
+ return Formatter3.keyValue(lines);
709
+ }
710
+ function registerRideGetCommand(parent, deps) {
711
+ const cmd = parent.command("get").description("Retrieve a ride order status by id (repeatable for polling)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-id <id>", "Ride order id (the ride_id returned by `ride-elife book`)").option("--watch", "Poll until a terminal status, emitting one NDJSON line per update").option(
712
+ "--watch-interval <seconds>",
713
+ `Seconds between polls when --watch is set (default ${DEFAULT_WATCH_INTERVAL_SECONDS})`
714
+ ).option(
715
+ "--watch-timeout <seconds>",
716
+ `Max seconds to poll before giving up (default ${DEFAULT_WATCH_TIMEOUT_SECONDS})`
717
+ );
718
+ attachSchemaHelp(cmd, rideGetSchema);
719
+ cmd.action(async () => {
720
+ const opts = cmd.optsWithGlobals();
721
+ const format = resolveFormat3(opts.format);
722
+ const apiKey = await PromptEngine4.resolveInput(opts.apiKey, {
723
+ message: "API Key:",
684
724
  type: "password"
685
725
  });
686
- const orderId = await PromptEngine.resolveInput(o.orderId, { message: "Order id:" });
687
- const client = new ApiClient({ apiKey });
688
- const path = `/ride/${encodeURIComponent(orderId)}/status`;
689
- if (o.watch) {
690
- await watchUntilTerminal(client, path, {
691
- intervalMs: num2(o.watchInterval, DEFAULT_WATCH_INTERVAL_SECONDS) * 1e3,
692
- timeoutMs: num2(o.watchTimeout, DEFAULT_WATCH_TIMEOUT_SECONDS) * 1e3
726
+ const orderId = need3(opts.orderId, "order-id");
727
+ if (opts.watch) {
728
+ await watchRideStatus(deps.apiClient, apiKey, orderId, {
729
+ intervalSeconds: resolveSeconds(
730
+ opts.watchInterval,
731
+ DEFAULT_WATCH_INTERVAL_SECONDS
732
+ ),
733
+ timeoutSeconds: resolveSeconds(
734
+ opts.watchTimeout,
735
+ DEFAULT_WATCH_TIMEOUT_SECONDS
736
+ )
693
737
  });
694
738
  return;
695
739
  }
696
- const spinner = createSpinner("Fetching ride status...");
697
- try {
698
- const data = await client.get(path);
699
- spinner.stop();
700
- emit(data, format);
701
- } catch (err) {
702
- spinner.fail("Failed to fetch ride status");
703
- throw err;
740
+ notify3(format, "loading", "Fetching ride status...");
741
+ const result = await deps.apiClient.get(
742
+ `/ride/${encodeURIComponent(orderId)}/status`,
743
+ { type: "api-key", key: apiKey }
744
+ );
745
+ if (!result.success) {
746
+ throw CliError5.fromApi(result, { auth: "api-key" });
704
747
  }
748
+ const data = result.data;
749
+ const configManager = new ConfigManager3();
750
+ const commandResult = {
751
+ data,
752
+ text: () => formatGetOrder(data)
753
+ };
754
+ await renderWithContext3(commandResult, { format }, configManager);
705
755
  });
706
- return attachSchemaHelp(cmd, rideGetSchema);
707
756
  }
708
- async function watchUntilTerminal(client, path, opts) {
709
- const deadline = Date.now() + opts.timeoutMs;
710
- for (; ; ) {
711
- const data = await client.get(path);
712
- emitNdjsonLine(data);
713
- const status = statusOf(data);
714
- if (status !== void 0 && TERMINAL_STATUSES.has(status)) return;
715
- if (Date.now() + opts.intervalMs >= deadline) {
716
- emitNdjsonLine({
717
- watch_status: "timeout",
718
- message: `Polling stopped after ${opts.timeoutMs / 1e3}s without reaching a terminal status.`,
719
- last_status: status ?? null
720
- });
721
- return;
722
- }
723
- await sleep(opts.intervalMs);
757
+
758
+ // src/ride-elife/cancel.ts
759
+ import { confirm as confirm2 } from "@inquirer/prompts";
760
+ import {
761
+ ConfigManager as ConfigManager4,
762
+ PromptEngine as PromptEngine5,
763
+ Formatter as Formatter4,
764
+ resolveFormat as resolveFormat4,
765
+ notify as notify4,
766
+ CliError as CliError6,
767
+ renderWithContext as renderWithContext4
768
+ } from "@agenzo/cli-core";
769
+ function need4(value, flag) {
770
+ if (value === void 0) {
771
+ throw new CliError6("PARAM_INVALID", `Missing required --${flag}.`);
724
772
  }
773
+ return value;
725
774
  }
726
-
727
- // src/ride/cancel.ts
728
- import { Command as Command4 } from "commander";
729
- var cancelSchema = {
730
- description: "Cancel a ride order; may incur a cancellation fee.",
731
- params: {
732
- "order-id": "string \u2014 required, order id (a.k.a. ride id) to cancel",
733
- "idempotency-key": "string \u2014 required, sent as the Idempotency-Key header (never in body)"
734
- },
735
- response: {
736
- ride_id: "string \u2014 cancelled ride id",
737
- ride_stat: "string \u2014 resulting ride status",
738
- cancellation: "{ cancellation_fee, reversal_amount, currency }",
739
- refund_amount: "number \u2014 amount credited back to the settlement balance (paid \u2212 cancellation_fee)"
775
+ function formatCancel(data) {
776
+ const lines = [
777
+ ["Ride ID", String(data.ride_id ?? "-")],
778
+ ["Status", String(data.ride_stat ?? "-")]
779
+ ];
780
+ if (data.cancellation) {
781
+ const c = data.cancellation;
782
+ lines.push(["Cancellation fee", `${c.cancellation_fee} ${c.currency}`]);
783
+ lines.push(["Reversal amount", `${c.reversal_amount} ${c.currency}`]);
740
784
  }
741
- };
742
- function buildCancelCommand() {
743
- return new Command4("cancel").description(cancelSchema.description).helpOption(false).option("--help", "Show help (add --format json for the machine-readable schema)").option("--format <format>", "Output format: json|table").option("--api-key <key>", "API key for runtime requests").option("--yes", "Skip confirmation prompts").option("--order-id <id>", "Order id (a.k.a. ride id) to cancel").option("--idempotency-key <key>", "Caller-supplied idempotency key").action(async (_options, command) => {
744
- const merged = command.optsWithGlobals();
745
- const format = merged.format === "table" ? "table" : "json";
746
- if (merged.help) {
747
- const wantsSchema = process.argv.some((a) => a === "--format" || a.startsWith("--format="));
748
- if (wantsSchema) emitSchema(cancelSchema, format);
749
- else command.outputHelp();
750
- return;
751
- }
752
- const yes = merged.yes === true;
753
- const apiKey = await PromptEngine.resolveInput(merged.apiKey, {
754
- message: "API key:",
785
+ if (data.refund_amount !== void 0 && data.refund_amount !== null) {
786
+ lines.push(["Refund amount", String(data.refund_amount)]);
787
+ }
788
+ return Formatter4.keyValue(lines);
789
+ }
790
+ function registerCancelCommand(parent, deps) {
791
+ const cmd = parent.command("cancel").description("Cancel a ride order by id (may incur a cancellation fee)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-id <id>", "Ride order id to cancel (the ride_id returned by book)").option(
792
+ "--idempotency-key <key>",
793
+ "Idempotency key forwarded verbatim as the Idempotency-Key header"
794
+ );
795
+ attachSchemaHelp(cmd, cancelSchema);
796
+ cmd.action(async () => {
797
+ const opts = cmd.optsWithGlobals();
798
+ const format = resolveFormat4(opts.format);
799
+ const isYes = Boolean(opts.yes);
800
+ const apiKey = await PromptEngine5.resolveInput(opts.apiKey, {
801
+ message: "API Key:",
755
802
  type: "password"
756
803
  });
757
- const orderId = await PromptEngine.resolveInput(merged.orderId, { message: "Order id:" });
758
- if (!await PromptEngine.confirm(`Cancel order ${orderId}? This may incur a fee.`, yes)) {
759
- console.log(Formatter.status("info", "Cancellation aborted."));
760
- return;
804
+ const orderId = need4(opts.orderId, "order-id");
805
+ if (!isYes) {
806
+ const confirmed = await confirm2({
807
+ message: `Cancel ride ${orderId}? This may incur a fee.`,
808
+ default: false
809
+ });
810
+ if (!confirmed) {
811
+ throw new CliError6("CLIENT_ABORTED", "Cancellation aborted by user.");
812
+ }
761
813
  }
762
- const idempotencyKey = await resolveIdempotencyKey(merged.idempotencyKey, { yes });
763
- const client = new ApiClient({ apiKey });
764
- const spinner = createSpinner("Cancelling ride...");
765
- try {
766
- const data = await client.post(`/ride/${encodeURIComponent(orderId)}/cancel`, { idempotencyKey });
767
- spinner.stop();
768
- emit(data, format);
769
- } catch (err) {
770
- spinner.fail("Cancellation failed");
771
- throw err;
814
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
815
+ yes: isYes,
816
+ commandPath: "ride-elife cancel"
817
+ });
818
+ notify4(format, "loading", "Cancelling ride...");
819
+ const result = await deps.apiClient.post(
820
+ `/ride/${encodeURIComponent(orderId)}/cancel`,
821
+ { type: "api-key", key: apiKey },
822
+ void 0,
823
+ { "Idempotency-Key": idempotencyKey }
824
+ );
825
+ if (!result.success) {
826
+ throw CliError6.fromApi(result, { auth: "api-key" });
772
827
  }
828
+ const data = result.data;
829
+ const configManager = new ConfigManager4();
830
+ const commandResult = {
831
+ data,
832
+ text: () => formatCancel(data)
833
+ };
834
+ await renderWithContext4(commandResult, { format }, configManager);
773
835
  });
774
836
  }
775
837
 
776
- // src/ride/list-orders.ts
777
- import { Command as Command5 } from "commander";
778
- var listOrdersSchema = {
779
- description: "List ride orders with pagination and optional filters.",
780
- params: {
781
- page: "number \u2014 page number (default 1)",
782
- "page-size": "number \u2014 page size (default 20)",
783
- status: "OrderStatus \u2014 optional status filter",
784
- "order-type": "string \u2014 optional order type filter"
785
- },
786
- response: {
787
- orders: "RideOrderListItem[] \u2014 each: order_id, ride_id, status, vehicle_class, is_scheduled, scheduled_at, price_amount, final_amount, price_currency, payment_status, final_settlement_status (pending|settled|no_adjustment|settlement_pending|not_applicable), cancellation_fee, provider, created_at, updated_at",
788
- total: "number",
789
- page: "number",
790
- page_size: "number"
838
+ // src/ride-elife/list-orders.ts
839
+ import {
840
+ ConfigManager as ConfigManager5,
841
+ PromptEngine as PromptEngine6,
842
+ Formatter as Formatter5,
843
+ resolveFormat as resolveFormat5,
844
+ notify as notify5,
845
+ CliError as CliError7,
846
+ renderWithContext as renderWithContext5
847
+ } from "@agenzo/cli-core";
848
+ var DEFAULT_PAGE = "1";
849
+ var DEFAULT_PAGE_SIZE = "20";
850
+ function positiveInt(value, flag) {
851
+ const n = Number(value);
852
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
853
+ throw new CliError7("PARAM_INVALID", `--${flag} must be a positive integer.`);
791
854
  }
792
- };
793
- function buildListOrdersCommand() {
794
- const cmd = new Command5("list-orders").description(listOrdersSchema.description).option("--page <page>", "Page number", "1").option("--page-size <size>", "Page size", "20").option("--status <status>", "Filter by order status").option("--order-type <type>", "Filter by order type").action(async (_opts, command) => {
795
- const o = command.optsWithGlobals();
796
- const format = o.format === "table" ? "table" : "json";
797
- const apiKey = await PromptEngine.resolveInput(o.apiKey, {
798
- message: "API key:",
855
+ return String(n);
856
+ }
857
+ function formatOrders(data) {
858
+ const orders = data.orders ?? [];
859
+ if (orders.length === 0) {
860
+ return Formatter5.status("info", "No ride orders found");
861
+ }
862
+ const headers = ["Order ID", "Ride ID", "Status", "Vehicle Class", "Price", "Currency", "Payment"];
863
+ const rows = orders.map((o) => [
864
+ String(o.order_id ?? "-"),
865
+ String(o.ride_id ?? "-"),
866
+ String(o.status ?? "-"),
867
+ String(o.vehicle_class ?? "-"),
868
+ String(o.price_amount ?? "-"),
869
+ String(o.price_currency ?? "-"),
870
+ String(o.payment_status ?? "-")
871
+ ]);
872
+ const summary = [
873
+ ["Total", String(data.total ?? "-")],
874
+ ["Page", String(data.page ?? "-")],
875
+ ["Page size", String(data.page_size ?? "-")]
876
+ ];
877
+ return `${Formatter5.table(headers, rows)}
878
+ ${Formatter5.keyValue(summary)}`;
879
+ }
880
+ function registerListOrdersCommand(parent, deps) {
881
+ const cmd = parent.command("list-orders").description("List previously placed ride orders").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--page <page>", "Page number", DEFAULT_PAGE).option("--page-size <size>", "Page size", DEFAULT_PAGE_SIZE).option("--status <status>", "Filter by order status").option("--order-type <type>", "Filter by order type");
882
+ attachSchemaHelp(cmd, listOrdersSchema);
883
+ cmd.action(async () => {
884
+ const opts = cmd.optsWithGlobals();
885
+ const format = resolveFormat5(opts.format);
886
+ const apiKey = await PromptEngine6.resolveInput(opts.apiKey, {
887
+ message: "API Key:",
799
888
  type: "password"
800
889
  });
801
- const client = new ApiClient({ apiKey });
802
- const spinner = createSpinner("Fetching orders...");
803
- try {
804
- const data = await client.get("/ride/orders", {
805
- query: {
806
- page: Number(o.page),
807
- page_size: Number(o.pageSize),
808
- status: o.status,
809
- order_type: o.orderType
810
- }
811
- });
812
- spinner.stop();
813
- emit(data, format);
814
- } catch (err) {
815
- spinner.fail("Failed to fetch orders");
816
- throw err;
890
+ const params = {
891
+ page: positiveInt(opts.page ?? DEFAULT_PAGE, "page"),
892
+ page_size: positiveInt(opts.pageSize ?? DEFAULT_PAGE_SIZE, "page-size")
893
+ };
894
+ if (opts.status !== void 0) params.status = opts.status;
895
+ if (opts.orderType !== void 0) params.order_type = opts.orderType;
896
+ notify5(format, "loading", "Fetching orders...");
897
+ const result = await deps.apiClient.get(
898
+ "/ride/orders",
899
+ { type: "api-key", key: apiKey },
900
+ params
901
+ );
902
+ if (!result.success) {
903
+ throw CliError7.fromApi(result, { auth: "api-key" });
817
904
  }
905
+ const data = result.data;
906
+ const configManager = new ConfigManager5();
907
+ const commandResult = {
908
+ data,
909
+ text: () => formatOrders(data)
910
+ };
911
+ await renderWithContext5(commandResult, { format }, configManager);
818
912
  });
819
- return attachSchemaHelp(cmd, listOrdersSchema);
820
913
  }
821
914
 
822
915
  // src/services/list.ts
823
- import { Command as Command6 } from "commander";
916
+ import { ConfigManager as ConfigManager6, Formatter as Formatter6, resolveFormat as resolveFormat6, renderWithContext as renderWithContext6 } from "@agenzo/cli-core";
824
917
 
825
918
  // src/services/registry.ts
826
919
  var SERVICE_REGISTRY = [
@@ -828,6 +921,7 @@ var SERVICE_REGISTRY = [
828
921
  service_id: "ride-elife",
829
922
  name: "Ride hailing (eLife)",
830
923
  description: "On-demand ride ordering: quote a fare, book it, poll status, and cancel.",
924
+ category: "ride",
831
925
  version: "1.0.0",
832
926
  provider: "elife",
833
927
  cli_noun: "ride-elife",
@@ -835,8 +929,8 @@ var SERVICE_REGISTRY = [
835
929
  verb_descriptions: {
836
930
  quote: "Request fare quotes for a ride between two points.",
837
931
  book: "Book a ride using a quote_id returned by quote.",
838
- get: "Retrieve a ride order by id (poll for status changes).",
839
- cancel: "Cancel a ride order by id.",
932
+ get: "Retrieve a ride order by id (poll for status changes with --watch).",
933
+ cancel: "Cancel a ride order by id (may incur a fee).",
840
934
  "list-orders": "List previously placed ride orders."
841
935
  },
842
936
  workflow: ["quote", "book", "get (poll for status)", "cancel (optional)"],
@@ -849,117 +943,149 @@ function findService(serviceId) {
849
943
  }
850
944
 
851
945
  // src/services/list.ts
852
- var servicesListSchema = {
853
- description: "List available merchant services from the registry.",
854
- params: {},
855
- response: {
856
- services: "Service[] \u2014 each with service_id, name, provider, cli_noun, version, verbs"
857
- }
858
- };
859
- function resolveFormat(cmd) {
860
- return cmd.optsWithGlobals().format === "table" ? "table" : "json";
861
- }
862
- function buildServicesListCommand() {
863
- return new Command6("list").description(servicesListSchema.description).helpOption(false).option("-h, --help", "Show help; with --format json prints the schema").action((opts, cmd) => {
864
- const format = resolveFormat(cmd);
865
- if (opts.help) {
866
- emitSchema(servicesListSchema, format);
867
- return;
868
- }
869
- const services = SERVICE_REGISTRY.map((s) => ({
870
- service_id: s.service_id,
871
- name: s.name,
872
- provider: s.provider,
873
- cli_noun: s.cli_noun,
874
- version: s.version,
875
- verbs: s.verbs.join(", ")
876
- }));
877
- emit(services, format);
946
+ function toListItem(s) {
947
+ return {
948
+ service_id: s.service_id,
949
+ name: s.name,
950
+ category: s.category,
951
+ provider: s.provider,
952
+ cli_noun: s.cli_noun,
953
+ version: s.version,
954
+ verbs: s.verbs,
955
+ since: s.since,
956
+ discovery: s.discovery
957
+ };
958
+ }
959
+ function registerServicesListCommand(parent) {
960
+ const cmd = parent.command("list").description("List available merchant services from the registry");
961
+ cmd.action(async () => {
962
+ const opts = cmd.optsWithGlobals();
963
+ const format = resolveFormat6(opts.format);
964
+ const services = SERVICE_REGISTRY.map(toListItem);
965
+ const configManager = new ConfigManager6();
966
+ const commandResult = {
967
+ data: { services },
968
+ text: () => {
969
+ if (services.length === 0) {
970
+ return Formatter6.status("info", "No services found");
971
+ }
972
+ const headers = ["Service ID", "Name", "Category", "Provider", "Version", "Verbs"];
973
+ const rows = services.map((s) => [
974
+ s.service_id,
975
+ s.name,
976
+ s.category,
977
+ s.provider,
978
+ s.version,
979
+ s.verbs.join(", ")
980
+ ]);
981
+ return Formatter6.table(headers, rows);
982
+ }
983
+ };
984
+ await renderWithContext6(commandResult, { format }, configManager);
878
985
  });
879
986
  }
880
987
 
881
988
  // src/services/get.ts
882
- import { Command as Command7 } from "commander";
883
- var serviceGetSchema = {
884
- description: "Retrieve a single merchant service by id, with verb and workflow detail.",
885
- params: {
886
- "service-id": "string \u2014 service id to retrieve"
887
- },
888
- response: {
889
- service_id: "string",
890
- name: "string",
891
- description: "string",
892
- version: "string",
893
- provider: "string",
894
- cli_noun: "string",
895
- verbs: "string[]",
896
- verb_descriptions: "Record<verb, description>",
897
- workflow: "string[] \u2014 ordered steps quote\u2192book\u2192get(poll)\u2192[cancel]",
898
- since: "string \u2014 ISO date",
899
- discovery: "{ help_command: string }"
900
- }
901
- };
902
- function resolveFormat2(cmd) {
903
- return cmd.optsWithGlobals().format === "table" ? "table" : "json";
904
- }
905
- function buildServiceGetCommand() {
906
- return new Command7("get").description(serviceGetSchema.description).argument("[service-id]", "Service id to retrieve").helpOption(false).option("-h, --help", "Show help; with --format json prints the schema").action((serviceId, opts, cmd) => {
907
- const format = resolveFormat2(cmd);
908
- if (opts.help) {
909
- emitSchema(serviceGetSchema, format);
910
- return;
911
- }
912
- const service = serviceId ? findService(serviceId) : void 0;
989
+ import {
990
+ ConfigManager as ConfigManager7,
991
+ Formatter as Formatter7,
992
+ CliError as CliError8,
993
+ resolveFormat as resolveFormat7,
994
+ renderWithContext as renderWithContext7
995
+ } from "@agenzo/cli-core";
996
+ function formatServiceGet(s) {
997
+ const lines = [
998
+ ["Service ID", s.service_id],
999
+ ["Name", s.name],
1000
+ ["Description", s.description],
1001
+ ["Category", s.category],
1002
+ ["Version", s.version],
1003
+ ["Provider", s.provider],
1004
+ ["CLI Noun", s.cli_noun],
1005
+ ["Verbs", s.verbs.join(", ")],
1006
+ ["Workflow", s.workflow.join(" \u2192 ")],
1007
+ ["Since", s.since],
1008
+ ["Help", s.discovery.help_command]
1009
+ ];
1010
+ const verbDetail = s.verbs.map((v) => ` ${v}: ${s.verb_descriptions[v] ?? ""}`).join("\n");
1011
+ return `${Formatter7.keyValue(lines)}
1012
+ Verb descriptions:
1013
+ ${verbDetail}`;
1014
+ }
1015
+ function registerServiceGetCommand(parent) {
1016
+ const cmd = parent.command("get <service-id>").description("Retrieve a single merchant service by id");
1017
+ cmd.action(async (serviceId) => {
1018
+ const opts = cmd.optsWithGlobals();
1019
+ const format = resolveFormat7(opts.format);
1020
+ const service = findService(serviceId);
913
1021
  if (!service) {
914
- throw new CliError(
915
- ErrorCodes.SERVICE_NOT_FOUND,
916
- `Service '${serviceId ?? ""}' not found in the registry (SERVICE_NOT_FOUND).`,
917
- 'Run "services list" to see available services.'
1022
+ throw new CliError8(
1023
+ "SERVICE_NOT_FOUND",
1024
+ `Service '${serviceId}' was not found in the registry. Run "services list" to see available services.`
918
1025
  );
919
1026
  }
920
- emit(service, format);
1027
+ const configManager = new ConfigManager7();
1028
+ const commandResult = {
1029
+ data: service,
1030
+ text: () => formatServiceGet(service)
1031
+ };
1032
+ await renderWithContext7(commandResult, { format }, configManager);
921
1033
  });
922
1034
  }
923
1035
 
924
1036
  // src/index.ts
925
- function resolveFormat3(cmd) {
926
- return cmd.optsWithGlobals().format === "table" ? "table" : "json";
927
- }
1037
+ var programRef;
928
1038
  async function main() {
929
- const program = new Command8();
930
- program.name("agenzo-merchant-cli").description("CLI for ordering rides and querying merchant services").version(getCurrentVersion()).option("--format <format>", "Output format: json|table", "json").option("--yes", "Skip confirmation prompts (for automation/AI Agents)", false).option("--api-key <key>", "API key for runtime requests");
931
- const config = program.command("config").description("Local API host configuration");
932
- config.command("set-host <url>").description("Set the API host").action((url) => {
933
- setHost(url);
934
- console.log(Formatter.status("success", `API host set to ${getHost()}`));
935
- });
936
- config.command("reset-host").description("Reset the API host to the default").action(() => {
937
- resetHost();
938
- console.log(Formatter.status("success", `API host reset to ${getHost()}`));
939
- });
940
- config.command("show").description("Show the current configuration").action((_options, cmd) => {
941
- emit({ host: getHost() }, resolveFormat3(cmd));
1039
+ const configManager = new ConfigManager8();
1040
+ await configManager.ensureDirectories();
1041
+ const apiBaseUrl = await configManager.getApiBaseUrl();
1042
+ const apiClient = new ApiClient6({ baseUrl: apiBaseUrl });
1043
+ const deps = { apiClient };
1044
+ const program = new Command();
1045
+ programRef = program;
1046
+ program.name("agenzo-merchant-cli").version(getCurrentVersion()).description(
1047
+ "Agenzo merchant fulfillment plane: service discovery (services) and ride ordering (ride-elife)"
1048
+ ).option("--verbose", "Show verbose logs").option("--yes", "Skip confirmation prompts (for automation/AI Agents)").option(
1049
+ "--format <format>",
1050
+ "Output format: json | table (default: json; or set AGENZO_FORMAT)",
1051
+ "json"
1052
+ ).option("--api-key <key>", "API Key for authentication (X-Api-Key)");
1053
+ program.hook("preAction", (thisCommand) => {
1054
+ const flag = thisCommand.opts().format;
1055
+ process.env.AGENZO_FORMAT = resolveFormat8(flag);
942
1056
  });
943
- const rideElife = program.command("ride-elife").description("Ride ordering (eLife): quote, book, get, cancel, list-orders");
944
- rideElife.addCommand(buildQuoteCommand());
945
- rideElife.addCommand(buildBookCommand());
946
- rideElife.addCommand(buildRideGetCommand());
947
- rideElife.addCommand(buildCancelCommand());
948
- rideElife.addCommand(buildListOrdersCommand());
949
- const services = program.command("services").description("Service registry: list, get");
950
- services.addCommand(buildServicesListCommand());
951
- services.addCommand(buildServiceGetCommand());
1057
+ const rideCmd = program.command("ride-elife").description("Ride ordering (eLife)");
1058
+ registerQuoteCommand(rideCmd, deps);
1059
+ registerBookCommand(rideCmd, deps);
1060
+ registerRideGetCommand(rideCmd, deps);
1061
+ registerCancelCommand(rideCmd, deps);
1062
+ registerListOrdersCommand(rideCmd, deps);
1063
+ const servicesCmd = program.command("services").description("Merchant service discovery");
1064
+ registerServicesListCommand(servicesCmd);
1065
+ registerServiceGetCommand(servicesCmd);
952
1066
  await program.parseAsync(process.argv);
953
1067
  }
954
- main().catch((error) => {
955
- if (error instanceof CliError) {
956
- console.error(Formatter.status("error", error.message));
957
- if (error.recovery) console.error(Formatter.status("info", error.recovery));
958
- process.exit(error.exitCode);
1068
+ function resolveActiveFormat() {
1069
+ const flag = programRef?.opts().format;
1070
+ return resolveFormat8(flag);
1071
+ }
1072
+ function reportError(error) {
1073
+ const envelope = toErrorEnvelope(error);
1074
+ const format = resolveActiveFormat();
1075
+ if (format === "json") {
1076
+ console.error(JSON.stringify(envelope));
1077
+ } else {
1078
+ console.error(
1079
+ Formatter8.status("error", `[${envelope.error.code_num}] ${envelope.error.message}`)
1080
+ );
1081
+ if (!(error instanceof CliError9) && process.argv.includes("--verbose")) {
1082
+ console.error(error);
1083
+ }
959
1084
  }
960
- console.error(
961
- Formatter.status("error", error instanceof Error ? error.message : "An unexpected error occurred")
962
- );
963
- process.exit(1);
1085
+ process.exit(exitCodeFor(error));
1086
+ }
1087
+ process.on("SIGINT", () => {
1088
+ reportError(new UserCancelError());
964
1089
  });
1090
+ main().catch(reportError);
965
1091
  //# sourceMappingURL=index.js.map