@agenzo/merchant-cli 0.3.0 → 0.3.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/dist/index.js +765 -132
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -2,31 +2,715 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
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";
|
|
16
5
|
|
|
17
|
-
//
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
6
|
+
// ../../packages/cli-core/dist/index.js
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, chmod as chmod3 } from "fs/promises";
|
|
11
|
+
import { join as join4 } from "path";
|
|
12
|
+
import { homedir as homedir3 } from "os";
|
|
13
|
+
import { input, password, select } from "@inquirer/prompts";
|
|
14
|
+
var CODE_NUM = {
|
|
15
|
+
AUTH_SESSION_EXPIRED: 1001,
|
|
16
|
+
AUTH_MAGIC_LINK_EXPIRED: 1002,
|
|
17
|
+
AUTH_INVITE_CODE_REQUIRED: 1003,
|
|
18
|
+
AUTH_INVITE_CODE_INVALID: 1004,
|
|
19
|
+
KEY_INVALID: 1101,
|
|
20
|
+
KEY_SCOPE_DENIED: 1102,
|
|
21
|
+
RESOURCE_NOT_FOUND: 2001,
|
|
22
|
+
RESOURCE_CONFLICT: 2002,
|
|
23
|
+
RESOURCE_STATE_INVALID: 2003,
|
|
24
|
+
PARAM_INVALID: 2101,
|
|
25
|
+
PARAM_IDEMPOTENCY_KEY_REQUIRED: 2102,
|
|
26
|
+
PARAM_IDEMPOTENCY_KEY_CONFLICT: 2103,
|
|
27
|
+
BILLING_MODE_MISMATCH: 3001,
|
|
28
|
+
ACCOUNT_NOT_FOUND: 3101,
|
|
29
|
+
ACCOUNT_SUSPENDED: 3102,
|
|
30
|
+
ACCOUNT_INSUFFICIENT_BALANCE: 3103,
|
|
31
|
+
PAYMENT_ORDER_NOT_FOUND: 3201,
|
|
32
|
+
PAYMENT_ORDER_NOT_PAID: 3202,
|
|
33
|
+
PAYMENT_ORDER_MISMATCH: 3203,
|
|
34
|
+
PAYMENT_ORDER_ALREADY_CONSUMED: 3204,
|
|
35
|
+
TOKEN_FEATURE_DISABLED: 4001,
|
|
36
|
+
SERVICE_NOT_FOUND: 4101,
|
|
37
|
+
VEHICLE_UNAVAILABLE: 4201,
|
|
38
|
+
QUOTE_EXPIRED: 4202,
|
|
39
|
+
BOOKING_FAILED: 4203,
|
|
40
|
+
CANCELLATION_NOT_ALLOWED: 4204,
|
|
41
|
+
RATE_LIMITED: 5001,
|
|
42
|
+
UPSTREAM_ERROR: 5101,
|
|
43
|
+
INTERNAL_ERROR: 5201,
|
|
44
|
+
NOT_IMPLEMENTED: 5300,
|
|
45
|
+
CLIENT_NOT_SIGNED_IN: 9001,
|
|
46
|
+
CLIENT_LOGIN_TIMEOUT: 9002,
|
|
47
|
+
CLIENT_ORG_NOT_LOCAL: 9003,
|
|
48
|
+
CLIENT_ORG_WRONG_ENV: 9004,
|
|
49
|
+
CLIENT_NO_PAYMENT_METHOD: 9005,
|
|
50
|
+
CLIENT_CARD_NOT_MATCHED: 9006,
|
|
51
|
+
CLIENT_ABORTED: 9007,
|
|
52
|
+
UPGRADE_REQUIRED: 9008
|
|
53
|
+
};
|
|
54
|
+
function codeNum(code) {
|
|
55
|
+
return CODE_NUM[code];
|
|
56
|
+
}
|
|
57
|
+
var CliError = class _CliError extends Error {
|
|
58
|
+
constructor(code, message, statusCode, requestId, upstream) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.code = code;
|
|
61
|
+
this.statusCode = statusCode;
|
|
62
|
+
this.requestId = requestId;
|
|
63
|
+
this.upstream = upstream;
|
|
64
|
+
this.name = "CliError";
|
|
65
|
+
}
|
|
66
|
+
code;
|
|
67
|
+
statusCode;
|
|
68
|
+
requestId;
|
|
69
|
+
upstream;
|
|
70
|
+
/**
|
|
71
|
+
* Map a backend ApiError to a CLI-facing code (§8.5 transitional mapping).
|
|
72
|
+
* Backend numeric codes / HTTP status are translated to §8.4 string codes;
|
|
73
|
+
* the raw backend message is NOT passed through (§8.1 principle 4) — a
|
|
74
|
+
* stable, fixed message per code is used instead.
|
|
75
|
+
*
|
|
76
|
+
* The optional `opts.auth` selects the auth semantics for the 401 mapping:
|
|
77
|
+
* - `'bearer'` (default, admin-cli Bearer Token): 401 → `AUTH_SESSION_EXPIRED`.
|
|
78
|
+
* - `'api-key'` (token-cli `X-Api-Key`): 401 → `KEY_INVALID`.
|
|
79
|
+
* 403 maps to `KEY_SCOPE_DENIED` under both auth modes. Omitting `opts`
|
|
80
|
+
* preserves the original Bearer behavior exactly (§8.5 / requirement 6.4).
|
|
81
|
+
*
|
|
82
|
+
* Domain string-code routing (D3 / requirement 5.4): when the backend
|
|
83
|
+
* supplies a string `error.code` in the v3 envelope that is a known §8 code
|
|
84
|
+
* (present in {@link CODE_NUM}), it is used verbatim as the CLI-facing code
|
|
85
|
+
* so ride/merchant-specific codes (`QUOTE_EXPIRED`, `VEHICLE_UNAVAILABLE`,
|
|
86
|
+
* `BILLING_MODE_MISMATCH`, `PAYMENT_ORDER_*`, …) survive intact. Otherwise —
|
|
87
|
+
* including when no string code is provided, as is the case for admin/token
|
|
88
|
+
* today — mapping falls back to the HTTP-status table below, leaving the
|
|
89
|
+
* existing Bearer/api-key behavior unchanged.
|
|
90
|
+
*/
|
|
91
|
+
static fromApi(error, opts) {
|
|
92
|
+
if (error.code && isKnownErrorCode(error.code)) {
|
|
93
|
+
return new _CliError(
|
|
94
|
+
error.code,
|
|
95
|
+
STABLE_MESSAGE[error.code] ?? "Request failed. Please check your input and retry.",
|
|
96
|
+
error.statusCode,
|
|
97
|
+
error.requestId,
|
|
98
|
+
error.upstream
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const status = error.statusCode;
|
|
102
|
+
let code;
|
|
103
|
+
if (status === 401) code = opts?.auth === "api-key" ? "KEY_INVALID" : "AUTH_SESSION_EXPIRED";
|
|
104
|
+
else if (status === 403) code = "KEY_SCOPE_DENIED";
|
|
105
|
+
else if (status === 404) code = "RESOURCE_NOT_FOUND";
|
|
106
|
+
else if (status === 409) code = "RESOURCE_CONFLICT";
|
|
107
|
+
else if (status === 429) code = "RATE_LIMITED";
|
|
108
|
+
else if (status >= 500) code = "INTERNAL_ERROR";
|
|
109
|
+
else code = "PARAM_INVALID";
|
|
110
|
+
return new _CliError(
|
|
111
|
+
code,
|
|
112
|
+
STABLE_MESSAGE[code] ?? "Request failed. Please check your input and retry.",
|
|
113
|
+
status,
|
|
114
|
+
error.requestId,
|
|
115
|
+
error.upstream
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var NetworkError = class extends CliError {
|
|
120
|
+
constructor(_url, timeout, _cause) {
|
|
121
|
+
super(
|
|
122
|
+
"UPSTREAM_ERROR",
|
|
123
|
+
timeout ? "The request timed out. The service may be temporarily unavailable \u2014 please retry." : "Connection failed. The service may be temporarily unavailable \u2014 please retry."
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var ConfigError = class extends CliError {
|
|
128
|
+
constructor(message, filePath) {
|
|
129
|
+
super("INTERNAL_ERROR", message || "Unknown error");
|
|
130
|
+
this.filePath = filePath;
|
|
131
|
+
}
|
|
132
|
+
filePath;
|
|
133
|
+
};
|
|
134
|
+
var ValidationError = class extends CliError {
|
|
135
|
+
constructor(message) {
|
|
136
|
+
super("PARAM_INVALID", message || "Unknown error");
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var IdempotencyKeyRequiredError = class extends CliError {
|
|
140
|
+
constructor(commandPath) {
|
|
141
|
+
super(
|
|
142
|
+
"PARAM_IDEMPOTENCY_KEY_REQUIRED",
|
|
143
|
+
`\`${commandPath}\` requires --idempotency-key <key>. Supply a unique key so the write can be safely retried.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var UpgradeRequiredError = class extends CliError {
|
|
148
|
+
constructor(currentVersion, minVersion, upgradeCommand) {
|
|
149
|
+
super(
|
|
150
|
+
"UPGRADE_REQUIRED",
|
|
151
|
+
`agenzo-admin-cli ${currentVersion} is out of date \u2014 the server requires ${minVersion} or newer. To upgrade, run: ${upgradeCommand}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var UserCancelError = class extends CliError {
|
|
156
|
+
constructor(message = "Operation cancelled by user") {
|
|
157
|
+
super("CLIENT_ABORTED", message || "Unknown error");
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
function isKnownErrorCode(value) {
|
|
161
|
+
return Object.prototype.hasOwnProperty.call(CODE_NUM, value);
|
|
162
|
+
}
|
|
163
|
+
var STABLE_MESSAGE = {
|
|
164
|
+
AUTH_SESSION_EXPIRED: "Your session has expired. Please run `agenzo-admin-cli auth login` again.",
|
|
165
|
+
AUTH_MAGIC_LINK_EXPIRED: "Verification link is invalid or has expired. Please request a new one.",
|
|
166
|
+
AUTH_INVITE_CODE_REQUIRED: "An invitation code is required to register.",
|
|
167
|
+
AUTH_INVITE_CODE_INVALID: "The invitation code is invalid.",
|
|
168
|
+
KEY_INVALID: "The API key is invalid or has been revoked. Please check your --api-key and retry.",
|
|
169
|
+
KEY_SCOPE_DENIED: "This API key does not have the required scope for this command.",
|
|
170
|
+
RESOURCE_NOT_FOUND: "The resource was not found or does not belong to the current organization.",
|
|
171
|
+
RESOURCE_CONFLICT: "A resource with the same unique value already exists.",
|
|
172
|
+
RESOURCE_STATE_INVALID: "The resource is in a state that does not permit this operation.",
|
|
173
|
+
RATE_LIMITED: "Too many requests. Please back off and retry.",
|
|
174
|
+
UPSTREAM_ERROR: "A third-party service is temporarily unavailable. Please try again later.",
|
|
175
|
+
INTERNAL_ERROR: "Something went wrong on the server. Please retry and note the request_id.",
|
|
176
|
+
PARAM_INVALID: "One or more parameters are invalid. Please check your input and retry.",
|
|
177
|
+
PARAM_IDEMPOTENCY_KEY_CONFLICT: "A request with this idempotency key was already processed with different parameters.",
|
|
178
|
+
TOKEN_FEATURE_DISABLED: "This token feature is currently unavailable.",
|
|
179
|
+
BILLING_MODE_MISMATCH: "The billing mode does not match this operation. Please verify your account billing configuration.",
|
|
180
|
+
ACCOUNT_NOT_FOUND: "The settlement account was not found.",
|
|
181
|
+
ACCOUNT_SUSPENDED: "The settlement account is suspended. Please contact support.",
|
|
182
|
+
ACCOUNT_INSUFFICIENT_BALANCE: "The settlement account has insufficient balance for this operation.",
|
|
183
|
+
PAYMENT_ORDER_NOT_FOUND: "The payment order was not found.",
|
|
184
|
+
PAYMENT_ORDER_NOT_PAID: "The payment order has not been paid. Please complete payment and retry.",
|
|
185
|
+
PAYMENT_ORDER_MISMATCH: "The payment order does not match this order. Please check the --payment-order-id.",
|
|
186
|
+
PAYMENT_ORDER_ALREADY_CONSUMED: "The payment order has already been consumed by another booking.",
|
|
187
|
+
SERVICE_NOT_FOUND: "The requested service was not found.",
|
|
188
|
+
VEHICLE_UNAVAILABLE: "No vehicle is available for the requested trip. Please try a different time or class.",
|
|
189
|
+
QUOTE_EXPIRED: "The quote has expired. Please request a new quote and retry.",
|
|
190
|
+
BOOKING_FAILED: "The booking could not be completed. Please retry.",
|
|
191
|
+
CANCELLATION_NOT_ALLOWED: "This order cannot be cancelled in its current state.",
|
|
192
|
+
NOT_IMPLEMENTED: "This operation is not implemented yet."
|
|
193
|
+
};
|
|
194
|
+
function toErrorEnvelope(error) {
|
|
195
|
+
if (error instanceof CliError) {
|
|
196
|
+
return {
|
|
197
|
+
error: {
|
|
198
|
+
code: error.code,
|
|
199
|
+
code_num: codeNum(error.code),
|
|
200
|
+
message: error.message || "Unknown error",
|
|
201
|
+
...error.requestId ? { request_id: error.requestId } : {},
|
|
202
|
+
...error.upstream ? { upstream: error.upstream } : {}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unexpected error";
|
|
207
|
+
return {
|
|
208
|
+
error: {
|
|
209
|
+
code: "INTERNAL_ERROR",
|
|
210
|
+
code_num: codeNum("INTERNAL_ERROR"),
|
|
211
|
+
message: message || "Unknown error"
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
var UPGRADE_COMMAND = "npm install -g agenzo-admin-cli@latest";
|
|
216
|
+
var FALLBACK_VERSION = "0.1.1";
|
|
217
|
+
var cachedVersion = null;
|
|
218
|
+
function getCurrentVersion() {
|
|
219
|
+
if (cachedVersion !== null) return cachedVersion;
|
|
220
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
221
|
+
const candidates = [
|
|
222
|
+
join(here, "..", "package.json"),
|
|
223
|
+
// dist/ → project root
|
|
224
|
+
join(here, "..", "..", "package.json")
|
|
225
|
+
// src/utils/ → project root
|
|
226
|
+
];
|
|
227
|
+
for (const p of candidates) {
|
|
228
|
+
try {
|
|
229
|
+
const pkg = JSON.parse(readFileSync(p, "utf-8"));
|
|
230
|
+
if (typeof pkg.version === "string") {
|
|
231
|
+
cachedVersion = pkg.version;
|
|
232
|
+
return cachedVersion;
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
cachedVersion = FALLBACK_VERSION;
|
|
238
|
+
console.warn(
|
|
239
|
+
`[agenzo-admin-cli] package.json not found; using fallback version ${FALLBACK_VERSION}`
|
|
240
|
+
);
|
|
241
|
+
return cachedVersion;
|
|
242
|
+
}
|
|
243
|
+
function compareVersions(a, b) {
|
|
244
|
+
const parse = (v) => {
|
|
245
|
+
const clean = v.trim().replace(/^v/, "").split(/[-+]/)[0];
|
|
246
|
+
const parts = clean.split(".");
|
|
247
|
+
return [
|
|
248
|
+
parseInt(parts[0] ?? "0", 10) || 0,
|
|
249
|
+
parseInt(parts[1] ?? "0", 10) || 0,
|
|
250
|
+
parseInt(parts[2] ?? "0", 10) || 0
|
|
251
|
+
];
|
|
252
|
+
};
|
|
253
|
+
const [a1, a2, a3] = parse(a);
|
|
254
|
+
const [b1, b2, b3] = parse(b);
|
|
255
|
+
if (a1 !== b1) return a1 - b1;
|
|
256
|
+
if (a2 !== b2) return a2 - b2;
|
|
257
|
+
return a3 - b3;
|
|
258
|
+
}
|
|
259
|
+
function isBelow(current, minimum) {
|
|
260
|
+
return compareVersions(current, minimum) < 0;
|
|
261
|
+
}
|
|
262
|
+
var ApiClient = class {
|
|
263
|
+
baseUrl;
|
|
264
|
+
timeout;
|
|
265
|
+
constructor(config) {
|
|
266
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
267
|
+
this.timeout = config.timeout ?? 6e4;
|
|
268
|
+
}
|
|
269
|
+
buildHeaders(auth) {
|
|
270
|
+
const headers = {
|
|
271
|
+
"User-Agent": `agenzo-admin-cli/${getCurrentVersion()}`
|
|
272
|
+
};
|
|
273
|
+
if (auth.type === "bearer") {
|
|
274
|
+
headers["Authorization"] = `Bearer ${auth.token}`;
|
|
275
|
+
} else if (auth.type === "api-key") {
|
|
276
|
+
headers["X-Api-Key"] = auth.key;
|
|
277
|
+
}
|
|
278
|
+
return headers;
|
|
279
|
+
}
|
|
280
|
+
async get(path, auth, params) {
|
|
281
|
+
let url = `${this.baseUrl}${path}`;
|
|
282
|
+
if (params && Object.keys(params).length > 0) {
|
|
283
|
+
const searchParams = new URLSearchParams(params);
|
|
284
|
+
url += `?${searchParams.toString()}`;
|
|
285
|
+
}
|
|
286
|
+
return this.request(url, {
|
|
287
|
+
method: "GET",
|
|
288
|
+
headers: this.buildHeaders(auth)
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async post(path, auth, body, extraHeaders) {
|
|
292
|
+
const url = `${this.baseUrl}${path}`;
|
|
293
|
+
const headers = this.buildHeaders(auth);
|
|
294
|
+
headers["Content-Type"] = "application/json";
|
|
295
|
+
if (extraHeaders) {
|
|
296
|
+
Object.assign(headers, extraHeaders);
|
|
297
|
+
}
|
|
298
|
+
return this.request(url, {
|
|
299
|
+
method: "POST",
|
|
300
|
+
headers,
|
|
301
|
+
body: body ? JSON.stringify(body) : void 0
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
async request(url, init) {
|
|
305
|
+
const controller = new AbortController();
|
|
306
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
307
|
+
try {
|
|
308
|
+
const response = await fetch(url, {
|
|
309
|
+
...init,
|
|
310
|
+
signal: controller.signal
|
|
311
|
+
});
|
|
312
|
+
this.enforceMinVersion(response.headers.get("x-cli-min-version"));
|
|
313
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
314
|
+
let responseBody;
|
|
315
|
+
if (contentType.includes("application/json")) {
|
|
316
|
+
responseBody = await response.json();
|
|
317
|
+
} else {
|
|
318
|
+
const text = await response.text();
|
|
319
|
+
if (!response.ok) {
|
|
320
|
+
const statusMsg = this.friendlyStatusMessage(response.status);
|
|
321
|
+
return {
|
|
322
|
+
success: false,
|
|
323
|
+
errorCode: 0,
|
|
324
|
+
errorMessage: statusMsg,
|
|
325
|
+
statusCode: response.status
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
responseBody = JSON.parse(text);
|
|
330
|
+
} catch {
|
|
331
|
+
return {
|
|
332
|
+
success: false,
|
|
333
|
+
errorCode: 0,
|
|
334
|
+
errorMessage: `Unexpected response from server (${response.status})`,
|
|
335
|
+
statusCode: response.status
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const code = responseBody.code;
|
|
340
|
+
const message = responseBody.message;
|
|
341
|
+
const data = responseBody.data;
|
|
342
|
+
if (response.ok && (!code || code === "0000")) {
|
|
343
|
+
const payload = data ?? responseBody;
|
|
344
|
+
return { success: true, data: payload, message };
|
|
345
|
+
}
|
|
346
|
+
const errorCode = code ? parseInt(code, 10) : 0;
|
|
347
|
+
let errorMsg = message ?? response.statusText;
|
|
348
|
+
if (!errorMsg || errorMsg === response.statusText) {
|
|
349
|
+
const detail = responseBody.detail;
|
|
350
|
+
if (Array.isArray(detail)) {
|
|
351
|
+
errorMsg = detail.map((d) => {
|
|
352
|
+
const loc = d.loc?.slice(1).join(".") ?? "";
|
|
353
|
+
return loc ? `${loc}: ${d.msg}` : String(d.msg);
|
|
354
|
+
}).join("; ");
|
|
355
|
+
} else if (typeof detail === "string") {
|
|
356
|
+
errorMsg = detail;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const errorCodeStr = responseBody.error_code;
|
|
360
|
+
const rawData = responseBody.data;
|
|
361
|
+
const upstream = rawData?.upstream && typeof rawData.upstream === "object" ? rawData.upstream : void 0;
|
|
362
|
+
return {
|
|
363
|
+
success: false,
|
|
364
|
+
errorCode: isNaN(errorCode) ? 0 : errorCode,
|
|
365
|
+
errorMessage: errorMsg,
|
|
366
|
+
statusCode: response.status,
|
|
367
|
+
...errorCodeStr ? { code: errorCodeStr } : typeof code === "string" && code ? { code } : {},
|
|
368
|
+
requestId: responseBody.request_id ?? responseBody.requestId,
|
|
369
|
+
...upstream ? { upstream } : {}
|
|
370
|
+
};
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (error instanceof UpgradeRequiredError) {
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
376
|
+
throw new NetworkError(url, this.timeout);
|
|
377
|
+
}
|
|
378
|
+
throw new NetworkError(url, void 0, error instanceof Error ? error : void 0);
|
|
379
|
+
} finally {
|
|
380
|
+
clearTimeout(timeoutId);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Validate that the running CLI meets the server-advertised minimum.
|
|
385
|
+
* Missing or empty header → skip (backward compat with servers that don't
|
|
386
|
+
* advertise yet, and clients hitting legacy proxies).
|
|
387
|
+
*/
|
|
388
|
+
enforceMinVersion(headerValue) {
|
|
389
|
+
const minVersion = headerValue?.trim();
|
|
390
|
+
if (!minVersion) return;
|
|
391
|
+
const current = getCurrentVersion();
|
|
392
|
+
if (isBelow(current, minVersion)) {
|
|
393
|
+
throw new UpgradeRequiredError(current, minVersion, UPGRADE_COMMAND);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
friendlyStatusMessage(status) {
|
|
397
|
+
const messages = {
|
|
398
|
+
400: "Invalid request. Please check your input.",
|
|
399
|
+
401: "Authentication failed. Please check your API key or login again.",
|
|
400
|
+
403: "Access denied. You do not have permission for this operation.",
|
|
401
|
+
404: "Resource not found. Please check the ID.",
|
|
402
|
+
409: "Conflict. This resource may already exist.",
|
|
403
|
+
422: "Invalid input. Please check the request parameters.",
|
|
404
|
+
429: "Too many requests. Please wait and try again.",
|
|
405
|
+
500: "Something went wrong on the server. Please try again later.",
|
|
406
|
+
502: "Service is temporarily unavailable. Please try again in a moment.",
|
|
407
|
+
503: "Service is temporarily unavailable. Please try again in a moment.",
|
|
408
|
+
504: "The request took too long. Please try again."
|
|
409
|
+
};
|
|
410
|
+
return messages[status] ?? `Something went wrong (${status}). Please try again later.`;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
var DEFAULT_API_PATH = "/api/admin/v1";
|
|
414
|
+
var DEFAULT_CONFIG = {
|
|
415
|
+
active_org: null,
|
|
416
|
+
active_developer_id: null,
|
|
417
|
+
api_host: "https://agent.everonet.com",
|
|
418
|
+
api_path: DEFAULT_API_PATH
|
|
419
|
+
};
|
|
420
|
+
var BUILTIN_PROFILES = {
|
|
421
|
+
production: "https://agent.everonet.com",
|
|
422
|
+
testing: "https://agent-test.everonet.com"
|
|
423
|
+
};
|
|
424
|
+
function resolveApiHost(host) {
|
|
425
|
+
if (BUILTIN_PROFILES[host]) {
|
|
426
|
+
return BUILTIN_PROFILES[host];
|
|
427
|
+
}
|
|
428
|
+
let url;
|
|
429
|
+
try {
|
|
430
|
+
url = new URL(host);
|
|
431
|
+
} catch {
|
|
432
|
+
throw new ValidationError(`Unknown profile or invalid URL: ${host}`);
|
|
433
|
+
}
|
|
434
|
+
if (url.protocol === "https:") {
|
|
435
|
+
return host;
|
|
436
|
+
}
|
|
437
|
+
if (url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
|
|
438
|
+
return host;
|
|
439
|
+
}
|
|
440
|
+
throw new ValidationError(
|
|
441
|
+
`Insecure API host is not allowed: ${host}. Use HTTPS, except for localhost or 127.0.0.1.`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
var ConfigManager = class {
|
|
445
|
+
basePath;
|
|
446
|
+
configPath;
|
|
447
|
+
/**
|
|
448
|
+
* Product-line API prefix this CLI targets (e.g. /api/admin/v1,
|
|
449
|
+
* /api/token/v1). Injected per binary so the four CLIs share one api_host
|
|
450
|
+
* but route to different product lines. Overrides any persisted api_path.
|
|
451
|
+
*/
|
|
452
|
+
apiPath;
|
|
453
|
+
constructor(basePath, apiPath) {
|
|
454
|
+
this.basePath = basePath ?? join4(homedir3(), ".agenzo-admin-cli");
|
|
455
|
+
this.configPath = join4(this.basePath, "config.json");
|
|
456
|
+
this.apiPath = apiPath ?? DEFAULT_API_PATH;
|
|
457
|
+
}
|
|
458
|
+
/** The resolved product-line API prefix for this CLI (per-binary constant). */
|
|
459
|
+
getApiPath() {
|
|
460
|
+
return this.apiPath;
|
|
461
|
+
}
|
|
462
|
+
async ensureDirectories() {
|
|
463
|
+
await mkdir3(this.basePath, { recursive: true, mode: 448 });
|
|
464
|
+
await chmod3(this.basePath, 448);
|
|
465
|
+
const credDir = join4(this.basePath, "credentials");
|
|
466
|
+
await mkdir3(credDir, { recursive: true, mode: 448 });
|
|
467
|
+
await chmod3(credDir, 448);
|
|
468
|
+
}
|
|
469
|
+
async load() {
|
|
470
|
+
try {
|
|
471
|
+
const content = await readFile3(this.configPath, "utf-8");
|
|
472
|
+
try {
|
|
473
|
+
const raw = JSON.parse(content);
|
|
474
|
+
if (raw.api_base_url && !raw.api_host) {
|
|
475
|
+
const url = String(raw.api_base_url);
|
|
476
|
+
const pathIndex = url.indexOf("/api/");
|
|
477
|
+
raw.api_host = pathIndex > 0 ? url.slice(0, pathIndex) : url;
|
|
478
|
+
raw.api_path = pathIndex > 0 ? url.slice(pathIndex) : DEFAULT_CONFIG.api_path;
|
|
479
|
+
delete raw.api_base_url;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
active_org: raw.active_org ?? null,
|
|
483
|
+
active_developer_id: raw.active_developer_id ?? null,
|
|
484
|
+
api_host: raw.api_host ?? DEFAULT_CONFIG.api_host,
|
|
485
|
+
api_path: raw.api_path ?? DEFAULT_CONFIG.api_path
|
|
486
|
+
};
|
|
487
|
+
} catch {
|
|
488
|
+
throw new ConfigError(
|
|
489
|
+
`Invalid config file: ${this.configPath}`,
|
|
490
|
+
this.configPath
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
} catch (error) {
|
|
494
|
+
if (error instanceof ConfigError) throw error;
|
|
495
|
+
if (error.code === "ENOENT") {
|
|
496
|
+
return { ...DEFAULT_CONFIG };
|
|
497
|
+
}
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async save(config) {
|
|
502
|
+
await this.ensureDirectories();
|
|
503
|
+
await writeFile3(this.configPath, JSON.stringify(config, null, 2), {
|
|
504
|
+
encoding: "utf-8",
|
|
505
|
+
mode: 384
|
|
506
|
+
});
|
|
507
|
+
await chmod3(this.configPath, 384);
|
|
508
|
+
}
|
|
509
|
+
async getActiveOrg() {
|
|
510
|
+
const config = await this.load();
|
|
511
|
+
return config.active_org;
|
|
512
|
+
}
|
|
513
|
+
async setActiveOrg(orgId) {
|
|
514
|
+
const config = await this.load();
|
|
515
|
+
config.active_org = orgId;
|
|
516
|
+
await this.save(config);
|
|
517
|
+
}
|
|
518
|
+
async getApiBaseUrl() {
|
|
519
|
+
const config = await this.load();
|
|
520
|
+
const host = resolveApiHost(config.api_host).replace(/\/+$/, "");
|
|
521
|
+
const path = this.apiPath.startsWith("/") ? this.apiPath : `/${this.apiPath}`;
|
|
522
|
+
return `${host}${path}`;
|
|
523
|
+
}
|
|
524
|
+
async setApiHost(host) {
|
|
525
|
+
const config = await this.load();
|
|
526
|
+
config.api_host = resolveApiHost(host);
|
|
527
|
+
await this.save(config);
|
|
528
|
+
}
|
|
529
|
+
async getApiHost() {
|
|
530
|
+
const config = await this.load();
|
|
531
|
+
return resolveApiHost(config.api_host);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
function exitCodeFor(error) {
|
|
535
|
+
if (!(error instanceof CliError)) {
|
|
536
|
+
return 1;
|
|
537
|
+
}
|
|
538
|
+
const code = error.code;
|
|
539
|
+
if (code === "UPGRADE_REQUIRED") {
|
|
540
|
+
return 2;
|
|
541
|
+
}
|
|
542
|
+
if (code === "CLIENT_ABORTED") {
|
|
543
|
+
return 5;
|
|
544
|
+
}
|
|
545
|
+
if (code.startsWith("AUTH_") || code.startsWith("KEY_")) {
|
|
546
|
+
return 3;
|
|
547
|
+
}
|
|
548
|
+
if (code === "UPSTREAM_ERROR" || code === "INTERNAL_ERROR" || code === "RATE_LIMITED") {
|
|
549
|
+
return 4;
|
|
550
|
+
}
|
|
551
|
+
return 1;
|
|
552
|
+
}
|
|
553
|
+
var STATUS_ICONS = {
|
|
554
|
+
success: "\u2713",
|
|
555
|
+
error: "\u2717",
|
|
556
|
+
info: "\u2139",
|
|
557
|
+
warning: "\u26A0",
|
|
558
|
+
loading: "\u280B"
|
|
559
|
+
};
|
|
560
|
+
var Formatter = class _Formatter {
|
|
561
|
+
/** Get display width of a string (CJK characters count as 2) */
|
|
562
|
+
static displayWidth(str) {
|
|
563
|
+
let width = 0;
|
|
564
|
+
for (const char of str) {
|
|
565
|
+
const code = char.codePointAt(0);
|
|
566
|
+
if (code >= 19968 && code <= 40959 || // CJK Unified
|
|
567
|
+
code >= 12288 && code <= 12351 || // CJK Punctuation
|
|
568
|
+
code >= 13312 && code <= 19903 || // CJK Extension A
|
|
569
|
+
code >= 65280 && code <= 65519 || // Fullwidth Forms
|
|
570
|
+
code >= 63744 && code <= 64255 || // CJK Compatibility
|
|
571
|
+
code >= 131072 && code <= 173791) {
|
|
572
|
+
width += 2;
|
|
573
|
+
} else {
|
|
574
|
+
width += 1;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return width;
|
|
578
|
+
}
|
|
579
|
+
/** Pad string to target display width (right-pad with spaces) */
|
|
580
|
+
static padEndDisplay(str, targetWidth) {
|
|
581
|
+
const currentWidth = _Formatter.displayWidth(str);
|
|
582
|
+
const padding = Math.max(0, targetWidth - currentWidth);
|
|
583
|
+
return str + " ".repeat(padding);
|
|
584
|
+
}
|
|
585
|
+
/** Pad string to target display width (left-pad with spaces) */
|
|
586
|
+
static padStartDisplay(str, targetWidth) {
|
|
587
|
+
const currentWidth = _Formatter.displayWidth(str);
|
|
588
|
+
const padding = Math.max(0, targetWidth - currentWidth);
|
|
589
|
+
return " ".repeat(padding) + str;
|
|
590
|
+
}
|
|
591
|
+
/** Table output for list commands — columns aligned by max width */
|
|
592
|
+
static table(headers, rows) {
|
|
593
|
+
const colWidths = headers.map((h, i) => {
|
|
594
|
+
const cellWidths = rows.map((r) => _Formatter.displayWidth(r[i] ?? ""));
|
|
595
|
+
return Math.max(_Formatter.displayWidth(h), ...cellWidths);
|
|
596
|
+
});
|
|
597
|
+
const sep = " ";
|
|
598
|
+
const headerLine = headers.map((h, i) => _Formatter.padEndDisplay(h, colWidths[i])).join(sep);
|
|
599
|
+
const divider = colWidths.map((w) => "-".repeat(w)).join(sep);
|
|
600
|
+
const dataLines = rows.map(
|
|
601
|
+
(row) => row.map((cell, i) => _Formatter.padEndDisplay(cell ?? "", colWidths[i])).join(sep)
|
|
602
|
+
);
|
|
603
|
+
return [headerLine, divider, ...dataLines].join("\n");
|
|
604
|
+
}
|
|
605
|
+
/** Key-value output for detail commands — keys left-aligned */
|
|
606
|
+
static keyValue(entries) {
|
|
607
|
+
const maxKeyWidth = Math.max(...entries.map(([k]) => _Formatter.displayWidth(k)));
|
|
608
|
+
return entries.map(([key, value]) => `${_Formatter.padEndDisplay(key, maxKeyWidth)} ${value}`).join("\n");
|
|
609
|
+
}
|
|
610
|
+
/** Status-prefixed message */
|
|
611
|
+
static status(type, message) {
|
|
612
|
+
return `${STATUS_ICONS[type]} ${message}`;
|
|
613
|
+
}
|
|
614
|
+
/** Mask sensitive info, keeping only the prefix */
|
|
615
|
+
static maskKey(key, prefixLength = 8) {
|
|
616
|
+
if (key.length <= prefixLength) {
|
|
617
|
+
return key;
|
|
618
|
+
}
|
|
619
|
+
return key.slice(0, prefixLength) + "*".repeat(key.length - prefixLength);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Format ISO 8601 timestamp to local timezone display.
|
|
623
|
+
* Automatically uses the user's system timezone.
|
|
624
|
+
* e.g. "2026-05-07T03:24:53+00:00" → "2026-05-07 11:24:53" (in CST)
|
|
625
|
+
* "2026-05-07T03:24:53+00:00" → "2026-05-06 20:24:53" (in PDT)
|
|
626
|
+
* Returns the original string if parsing fails or input is empty.
|
|
627
|
+
*/
|
|
628
|
+
static formatTime(iso) {
|
|
629
|
+
if (!iso) return "";
|
|
630
|
+
const date = new Date(iso);
|
|
631
|
+
if (isNaN(date.getTime())) return iso;
|
|
632
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
633
|
+
const y = date.getFullYear();
|
|
634
|
+
const m = pad(date.getMonth() + 1);
|
|
635
|
+
const d = pad(date.getDate());
|
|
636
|
+
const h = pad(date.getHours());
|
|
637
|
+
const min = pad(date.getMinutes());
|
|
638
|
+
const s = pad(date.getSeconds());
|
|
639
|
+
const offsetMin = -date.getTimezoneOffset();
|
|
640
|
+
const sign = offsetMin >= 0 ? "+" : "-";
|
|
641
|
+
const absOffset = Math.abs(offsetMin);
|
|
642
|
+
const tzH = pad(Math.floor(absOffset / 60));
|
|
643
|
+
const tzM = pad(absOffset % 60);
|
|
644
|
+
return `${y}-${m}-${d} ${h}:${min}:${s} (UTC${sign}${tzH}:${tzM})`;
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
var DEFAULT_FORMAT = "table";
|
|
648
|
+
function isOutputFormat(value) {
|
|
649
|
+
return value === "json" || value === "table";
|
|
650
|
+
}
|
|
651
|
+
function resolveFormat(flag, env = process.env.AGENZO_FORMAT) {
|
|
652
|
+
if (flag !== void 0) {
|
|
653
|
+
return isOutputFormat(flag) ? flag : DEFAULT_FORMAT;
|
|
654
|
+
}
|
|
655
|
+
if (env !== void 0 && isOutputFormat(env)) {
|
|
656
|
+
return env;
|
|
657
|
+
}
|
|
658
|
+
return DEFAULT_FORMAT;
|
|
659
|
+
}
|
|
660
|
+
function render(result, opts) {
|
|
661
|
+
if (opts.format === "json") {
|
|
662
|
+
process.stdout.write(`${JSON.stringify(result.data, null, 2)}
|
|
663
|
+
`);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
process.stdout.write(`${result.text()}
|
|
667
|
+
`);
|
|
668
|
+
}
|
|
669
|
+
function notify(format, type, message) {
|
|
670
|
+
if (format === "json") {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
console.error(Formatter.status(type, message));
|
|
674
|
+
}
|
|
675
|
+
async function renderWithContext(result, opts, configManager) {
|
|
676
|
+
if (opts.format !== "json") {
|
|
677
|
+
render(result, opts);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const config = await configManager.load();
|
|
681
|
+
const host = config.api_host.replace(/\/+$/, "");
|
|
682
|
+
const profile = Object.entries(BUILTIN_PROFILES).find(([, v]) => v === host)?.[0] ?? "custom";
|
|
683
|
+
const dataObj = result.data && typeof result.data === "object" ? result.data : { value: result.data };
|
|
684
|
+
const payload = {
|
|
685
|
+
profile,
|
|
686
|
+
endpoint: host,
|
|
687
|
+
...dataObj
|
|
688
|
+
};
|
|
689
|
+
render({ ...result, data: payload }, opts);
|
|
690
|
+
}
|
|
691
|
+
var PromptEngine = class {
|
|
692
|
+
/** Return flagValue directly if provided, otherwise prompt interactively */
|
|
693
|
+
static async resolveInput(flagValue, config) {
|
|
694
|
+
if (flagValue !== void 0) {
|
|
695
|
+
return flagValue;
|
|
696
|
+
}
|
|
697
|
+
if (config.type === "password") {
|
|
698
|
+
return password({ message: config.message, mask: "*" });
|
|
699
|
+
}
|
|
700
|
+
if (config.type === "select" && config.choices) {
|
|
701
|
+
return select({
|
|
702
|
+
message: config.message,
|
|
703
|
+
choices: config.choices
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
return input({
|
|
707
|
+
message: config.message,
|
|
708
|
+
validate: config.validate
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
};
|
|
27
712
|
|
|
28
713
|
// src/ride-elife/watch.ts
|
|
29
|
-
import { CliError } from "@agenzo/cli-core";
|
|
30
714
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
31
715
|
"At destination",
|
|
32
716
|
"Cancelled",
|
|
@@ -371,14 +1055,14 @@ var listOrdersSchema = {
|
|
|
371
1055
|
// src/ride-elife/quote.ts
|
|
372
1056
|
function need(value, flag) {
|
|
373
1057
|
if (value === void 0) {
|
|
374
|
-
throw new
|
|
1058
|
+
throw new CliError("PARAM_INVALID", `Missing required --${flag}.`);
|
|
375
1059
|
}
|
|
376
1060
|
return value;
|
|
377
1061
|
}
|
|
378
1062
|
function num(value, flag) {
|
|
379
1063
|
const n = Number(need(value, flag));
|
|
380
1064
|
if (!Number.isFinite(n)) {
|
|
381
|
-
throw new
|
|
1065
|
+
throw new CliError("PARAM_INVALID", `--${flag} must be a number.`);
|
|
382
1066
|
}
|
|
383
1067
|
return n;
|
|
384
1068
|
}
|
|
@@ -454,7 +1138,7 @@ function registerQuoteCommand(parent, deps) {
|
|
|
454
1138
|
body
|
|
455
1139
|
);
|
|
456
1140
|
if (!result.success) {
|
|
457
|
-
throw
|
|
1141
|
+
throw CliError.fromApi(result, { auth: "api-key" });
|
|
458
1142
|
}
|
|
459
1143
|
const data = result.data;
|
|
460
1144
|
const configManager = new ConfigManager();
|
|
@@ -468,28 +1152,14 @@ function registerQuoteCommand(parent, deps) {
|
|
|
468
1152
|
|
|
469
1153
|
// src/ride-elife/book.ts
|
|
470
1154
|
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";
|
|
480
1155
|
|
|
481
1156
|
// src/idempotency.ts
|
|
482
|
-
import {
|
|
483
|
-
CliError as CliError3,
|
|
484
|
-
IdempotencyKeyRequiredError,
|
|
485
|
-
PromptEngine as PromptEngine2
|
|
486
|
-
} from "@agenzo/cli-core";
|
|
487
1157
|
var IDEMPOTENCY_KEY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
488
1158
|
var IDEMPOTENCY_KEY_RULE = "Use 1-128 characters from [A-Za-z0-9_-].";
|
|
489
1159
|
function normalizeIdempotencyKey(key) {
|
|
490
1160
|
const trimmed = key.trim();
|
|
491
1161
|
if (!IDEMPOTENCY_KEY_PATTERN.test(trimmed)) {
|
|
492
|
-
throw new
|
|
1162
|
+
throw new CliError(
|
|
493
1163
|
"PARAM_INVALID",
|
|
494
1164
|
`Invalid idempotency key: "${key}". ${IDEMPOTENCY_KEY_RULE}`
|
|
495
1165
|
);
|
|
@@ -503,7 +1173,7 @@ async function resolveIdempotencyKey(flagValue, opts) {
|
|
|
503
1173
|
if (opts.yes) {
|
|
504
1174
|
throw new IdempotencyKeyRequiredError(opts.commandPath);
|
|
505
1175
|
}
|
|
506
|
-
const entered = await
|
|
1176
|
+
const entered = await PromptEngine.resolveInput(void 0, {
|
|
507
1177
|
message: "Idempotency key (unique per write, for safe retry):",
|
|
508
1178
|
validate: (v) => IDEMPOTENCY_KEY_PATTERN.test(v.trim()) || IDEMPOTENCY_KEY_RULE
|
|
509
1179
|
});
|
|
@@ -513,21 +1183,21 @@ async function resolveIdempotencyKey(flagValue, opts) {
|
|
|
513
1183
|
// src/ride-elife/book.ts
|
|
514
1184
|
function need2(value, flag) {
|
|
515
1185
|
if (value === void 0) {
|
|
516
|
-
throw new
|
|
1186
|
+
throw new CliError("PARAM_INVALID", `Missing required --${flag}.`);
|
|
517
1187
|
}
|
|
518
1188
|
return value;
|
|
519
1189
|
}
|
|
520
1190
|
function num2(value, flag) {
|
|
521
1191
|
const n = Number(need2(value, flag));
|
|
522
1192
|
if (!Number.isFinite(n)) {
|
|
523
|
-
throw new
|
|
1193
|
+
throw new CliError("PARAM_INVALID", `--${flag} must be a number.`);
|
|
524
1194
|
}
|
|
525
1195
|
return n;
|
|
526
1196
|
}
|
|
527
1197
|
function seatCount(value, flag) {
|
|
528
1198
|
const n = num2(value, flag);
|
|
529
1199
|
if (!Number.isInteger(n) || n < 0 || n > 5) {
|
|
530
|
-
throw new
|
|
1200
|
+
throw new CliError("PARAM_INVALID", `--${flag} must be an integer between 0 and 5.`);
|
|
531
1201
|
}
|
|
532
1202
|
return n;
|
|
533
1203
|
}
|
|
@@ -546,7 +1216,7 @@ function formatBook(data) {
|
|
|
546
1216
|
lines.push(["Payment status", String(data.payment_status ?? "-")]);
|
|
547
1217
|
if (data.billing_entry_id) lines.push(["Billing entry", String(data.billing_entry_id)]);
|
|
548
1218
|
if (data.payment_order_id) lines.push(["Payment order", String(data.payment_order_id)]);
|
|
549
|
-
return
|
|
1219
|
+
return Formatter.keyValue(lines);
|
|
550
1220
|
}
|
|
551
1221
|
function registerBookCommand(parent, deps) {
|
|
552
1222
|
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(
|
|
@@ -556,9 +1226,9 @@ function registerBookCommand(parent, deps) {
|
|
|
556
1226
|
attachSchemaHelp(cmd, bookSchema);
|
|
557
1227
|
cmd.action(async () => {
|
|
558
1228
|
const opts = cmd.optsWithGlobals();
|
|
559
|
-
const format =
|
|
1229
|
+
const format = resolveFormat(opts.format);
|
|
560
1230
|
const isYes = Boolean(opts.yes);
|
|
561
|
-
const apiKey = await
|
|
1231
|
+
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
562
1232
|
message: "API Key:",
|
|
563
1233
|
type: "password"
|
|
564
1234
|
});
|
|
@@ -626,14 +1296,14 @@ function registerBookCommand(parent, deps) {
|
|
|
626
1296
|
default: true
|
|
627
1297
|
});
|
|
628
1298
|
if (!confirmed) {
|
|
629
|
-
throw new
|
|
1299
|
+
throw new CliError("CLIENT_ABORTED", "Booking cancelled by user.");
|
|
630
1300
|
}
|
|
631
1301
|
}
|
|
632
1302
|
const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
|
|
633
1303
|
yes: isYes,
|
|
634
1304
|
commandPath: "ride-elife book"
|
|
635
1305
|
});
|
|
636
|
-
|
|
1306
|
+
notify(format, "loading", "Booking ride...");
|
|
637
1307
|
const result = await deps.apiClient.post(
|
|
638
1308
|
"/ride/book",
|
|
639
1309
|
{ type: "api-key", key: apiKey },
|
|
@@ -641,31 +1311,22 @@ function registerBookCommand(parent, deps) {
|
|
|
641
1311
|
{ "Idempotency-Key": idempotencyKey }
|
|
642
1312
|
);
|
|
643
1313
|
if (!result.success) {
|
|
644
|
-
throw
|
|
1314
|
+
throw CliError.fromApi(result, { auth: "api-key" });
|
|
645
1315
|
}
|
|
646
1316
|
const data = result.data;
|
|
647
|
-
const configManager = new
|
|
1317
|
+
const configManager = new ConfigManager();
|
|
648
1318
|
const commandResult = {
|
|
649
1319
|
data,
|
|
650
1320
|
text: () => formatBook(data)
|
|
651
1321
|
};
|
|
652
|
-
await
|
|
1322
|
+
await renderWithContext(commandResult, { format }, configManager);
|
|
653
1323
|
});
|
|
654
1324
|
}
|
|
655
1325
|
|
|
656
1326
|
// 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
1327
|
function need3(value, flag) {
|
|
667
1328
|
if (value === void 0) {
|
|
668
|
-
throw new
|
|
1329
|
+
throw new CliError("PARAM_INVALID", `Missing required --${flag}.`);
|
|
669
1330
|
}
|
|
670
1331
|
return value;
|
|
671
1332
|
}
|
|
@@ -705,7 +1366,7 @@ function formatGetOrder(data) {
|
|
|
705
1366
|
lines.push(["Vehicle", `${desc}${plate}`]);
|
|
706
1367
|
}
|
|
707
1368
|
if (data.created_at) lines.push(["Created at", String(data.created_at)]);
|
|
708
|
-
return
|
|
1369
|
+
return Formatter.keyValue(lines);
|
|
709
1370
|
}
|
|
710
1371
|
function registerRideGetCommand(parent, deps) {
|
|
711
1372
|
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(
|
|
@@ -718,8 +1379,8 @@ function registerRideGetCommand(parent, deps) {
|
|
|
718
1379
|
attachSchemaHelp(cmd, rideGetSchema);
|
|
719
1380
|
cmd.action(async () => {
|
|
720
1381
|
const opts = cmd.optsWithGlobals();
|
|
721
|
-
const format =
|
|
722
|
-
const apiKey = await
|
|
1382
|
+
const format = resolveFormat(opts.format);
|
|
1383
|
+
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
723
1384
|
message: "API Key:",
|
|
724
1385
|
type: "password"
|
|
725
1386
|
});
|
|
@@ -737,38 +1398,29 @@ function registerRideGetCommand(parent, deps) {
|
|
|
737
1398
|
});
|
|
738
1399
|
return;
|
|
739
1400
|
}
|
|
740
|
-
|
|
1401
|
+
notify(format, "loading", "Fetching ride status...");
|
|
741
1402
|
const result = await deps.apiClient.get(
|
|
742
1403
|
`/ride/${encodeURIComponent(orderId)}/status`,
|
|
743
1404
|
{ type: "api-key", key: apiKey }
|
|
744
1405
|
);
|
|
745
1406
|
if (!result.success) {
|
|
746
|
-
throw
|
|
1407
|
+
throw CliError.fromApi(result, { auth: "api-key" });
|
|
747
1408
|
}
|
|
748
1409
|
const data = result.data;
|
|
749
|
-
const configManager = new
|
|
1410
|
+
const configManager = new ConfigManager();
|
|
750
1411
|
const commandResult = {
|
|
751
1412
|
data,
|
|
752
1413
|
text: () => formatGetOrder(data)
|
|
753
1414
|
};
|
|
754
|
-
await
|
|
1415
|
+
await renderWithContext(commandResult, { format }, configManager);
|
|
755
1416
|
});
|
|
756
1417
|
}
|
|
757
1418
|
|
|
758
1419
|
// src/ride-elife/cancel.ts
|
|
759
1420
|
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
1421
|
function need4(value, flag) {
|
|
770
1422
|
if (value === void 0) {
|
|
771
|
-
throw new
|
|
1423
|
+
throw new CliError("PARAM_INVALID", `Missing required --${flag}.`);
|
|
772
1424
|
}
|
|
773
1425
|
return value;
|
|
774
1426
|
}
|
|
@@ -785,7 +1437,7 @@ function formatCancel(data) {
|
|
|
785
1437
|
if (data.refund_amount !== void 0 && data.refund_amount !== null) {
|
|
786
1438
|
lines.push(["Refund amount", String(data.refund_amount)]);
|
|
787
1439
|
}
|
|
788
|
-
return
|
|
1440
|
+
return Formatter.keyValue(lines);
|
|
789
1441
|
}
|
|
790
1442
|
function registerCancelCommand(parent, deps) {
|
|
791
1443
|
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(
|
|
@@ -795,9 +1447,9 @@ function registerCancelCommand(parent, deps) {
|
|
|
795
1447
|
attachSchemaHelp(cmd, cancelSchema);
|
|
796
1448
|
cmd.action(async () => {
|
|
797
1449
|
const opts = cmd.optsWithGlobals();
|
|
798
|
-
const format =
|
|
1450
|
+
const format = resolveFormat(opts.format);
|
|
799
1451
|
const isYes = Boolean(opts.yes);
|
|
800
|
-
const apiKey = await
|
|
1452
|
+
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
801
1453
|
message: "API Key:",
|
|
802
1454
|
type: "password"
|
|
803
1455
|
});
|
|
@@ -808,14 +1460,14 @@ function registerCancelCommand(parent, deps) {
|
|
|
808
1460
|
default: false
|
|
809
1461
|
});
|
|
810
1462
|
if (!confirmed) {
|
|
811
|
-
throw new
|
|
1463
|
+
throw new CliError("CLIENT_ABORTED", "Cancellation aborted by user.");
|
|
812
1464
|
}
|
|
813
1465
|
}
|
|
814
1466
|
const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
|
|
815
1467
|
yes: isYes,
|
|
816
1468
|
commandPath: "ride-elife cancel"
|
|
817
1469
|
});
|
|
818
|
-
|
|
1470
|
+
notify(format, "loading", "Cancelling ride...");
|
|
819
1471
|
const result = await deps.apiClient.post(
|
|
820
1472
|
`/ride/${encodeURIComponent(orderId)}/cancel`,
|
|
821
1473
|
{ type: "api-key", key: apiKey },
|
|
@@ -823,41 +1475,32 @@ function registerCancelCommand(parent, deps) {
|
|
|
823
1475
|
{ "Idempotency-Key": idempotencyKey }
|
|
824
1476
|
);
|
|
825
1477
|
if (!result.success) {
|
|
826
|
-
throw
|
|
1478
|
+
throw CliError.fromApi(result, { auth: "api-key" });
|
|
827
1479
|
}
|
|
828
1480
|
const data = result.data;
|
|
829
|
-
const configManager = new
|
|
1481
|
+
const configManager = new ConfigManager();
|
|
830
1482
|
const commandResult = {
|
|
831
1483
|
data,
|
|
832
1484
|
text: () => formatCancel(data)
|
|
833
1485
|
};
|
|
834
|
-
await
|
|
1486
|
+
await renderWithContext(commandResult, { format }, configManager);
|
|
835
1487
|
});
|
|
836
1488
|
}
|
|
837
1489
|
|
|
838
1490
|
// 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
1491
|
var DEFAULT_PAGE = "1";
|
|
849
1492
|
var DEFAULT_PAGE_SIZE = "20";
|
|
850
1493
|
function positiveInt(value, flag) {
|
|
851
1494
|
const n = Number(value);
|
|
852
1495
|
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
|
|
853
|
-
throw new
|
|
1496
|
+
throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
|
|
854
1497
|
}
|
|
855
1498
|
return String(n);
|
|
856
1499
|
}
|
|
857
1500
|
function formatOrders(data) {
|
|
858
1501
|
const orders = data.orders ?? [];
|
|
859
1502
|
if (orders.length === 0) {
|
|
860
|
-
return
|
|
1503
|
+
return Formatter.status("info", "No ride orders found");
|
|
861
1504
|
}
|
|
862
1505
|
const headers = ["Order ID", "Ride ID", "Status", "Vehicle Class", "Price", "Currency", "Payment"];
|
|
863
1506
|
const rows = orders.map((o) => [
|
|
@@ -874,16 +1517,16 @@ function formatOrders(data) {
|
|
|
874
1517
|
["Page", String(data.page ?? "-")],
|
|
875
1518
|
["Page size", String(data.page_size ?? "-")]
|
|
876
1519
|
];
|
|
877
|
-
return `${
|
|
878
|
-
${
|
|
1520
|
+
return `${Formatter.table(headers, rows)}
|
|
1521
|
+
${Formatter.keyValue(summary)}`;
|
|
879
1522
|
}
|
|
880
1523
|
function registerListOrdersCommand(parent, deps) {
|
|
881
1524
|
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
1525
|
attachSchemaHelp(cmd, listOrdersSchema);
|
|
883
1526
|
cmd.action(async () => {
|
|
884
1527
|
const opts = cmd.optsWithGlobals();
|
|
885
|
-
const format =
|
|
886
|
-
const apiKey = await
|
|
1528
|
+
const format = resolveFormat(opts.format);
|
|
1529
|
+
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
887
1530
|
message: "API Key:",
|
|
888
1531
|
type: "password"
|
|
889
1532
|
});
|
|
@@ -893,28 +1536,25 @@ function registerListOrdersCommand(parent, deps) {
|
|
|
893
1536
|
};
|
|
894
1537
|
if (opts.status !== void 0) params.status = opts.status;
|
|
895
1538
|
if (opts.orderType !== void 0) params.order_type = opts.orderType;
|
|
896
|
-
|
|
1539
|
+
notify(format, "loading", "Fetching orders...");
|
|
897
1540
|
const result = await deps.apiClient.get(
|
|
898
1541
|
"/ride/orders",
|
|
899
1542
|
{ type: "api-key", key: apiKey },
|
|
900
1543
|
params
|
|
901
1544
|
);
|
|
902
1545
|
if (!result.success) {
|
|
903
|
-
throw
|
|
1546
|
+
throw CliError.fromApi(result, { auth: "api-key" });
|
|
904
1547
|
}
|
|
905
1548
|
const data = result.data;
|
|
906
|
-
const configManager = new
|
|
1549
|
+
const configManager = new ConfigManager();
|
|
907
1550
|
const commandResult = {
|
|
908
1551
|
data,
|
|
909
1552
|
text: () => formatOrders(data)
|
|
910
1553
|
};
|
|
911
|
-
await
|
|
1554
|
+
await renderWithContext(commandResult, { format }, configManager);
|
|
912
1555
|
});
|
|
913
1556
|
}
|
|
914
1557
|
|
|
915
|
-
// src/services/list.ts
|
|
916
|
-
import { ConfigManager as ConfigManager6, Formatter as Formatter6, resolveFormat as resolveFormat6, renderWithContext as renderWithContext6 } from "@agenzo/cli-core";
|
|
917
|
-
|
|
918
1558
|
// src/services/registry.ts
|
|
919
1559
|
var SERVICE_REGISTRY = [
|
|
920
1560
|
{
|
|
@@ -960,14 +1600,14 @@ function registerServicesListCommand(parent) {
|
|
|
960
1600
|
const cmd = parent.command("list").description("List available merchant services from the registry");
|
|
961
1601
|
cmd.action(async () => {
|
|
962
1602
|
const opts = cmd.optsWithGlobals();
|
|
963
|
-
const format =
|
|
1603
|
+
const format = resolveFormat(opts.format);
|
|
964
1604
|
const services = SERVICE_REGISTRY.map(toListItem);
|
|
965
|
-
const configManager = new
|
|
1605
|
+
const configManager = new ConfigManager();
|
|
966
1606
|
const commandResult = {
|
|
967
1607
|
data: { services },
|
|
968
1608
|
text: () => {
|
|
969
1609
|
if (services.length === 0) {
|
|
970
|
-
return
|
|
1610
|
+
return Formatter.status("info", "No services found");
|
|
971
1611
|
}
|
|
972
1612
|
const headers = ["Service ID", "Name", "Category", "Provider", "Version", "Verbs"];
|
|
973
1613
|
const rows = services.map((s) => [
|
|
@@ -978,21 +1618,14 @@ function registerServicesListCommand(parent) {
|
|
|
978
1618
|
s.version,
|
|
979
1619
|
s.verbs.join(", ")
|
|
980
1620
|
]);
|
|
981
|
-
return
|
|
1621
|
+
return Formatter.table(headers, rows);
|
|
982
1622
|
}
|
|
983
1623
|
};
|
|
984
|
-
await
|
|
1624
|
+
await renderWithContext(commandResult, { format }, configManager);
|
|
985
1625
|
});
|
|
986
1626
|
}
|
|
987
1627
|
|
|
988
1628
|
// src/services/get.ts
|
|
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
1629
|
function formatServiceGet(s) {
|
|
997
1630
|
const lines = [
|
|
998
1631
|
["Service ID", s.service_id],
|
|
@@ -1008,7 +1641,7 @@ function formatServiceGet(s) {
|
|
|
1008
1641
|
["Help", s.discovery.help_command]
|
|
1009
1642
|
];
|
|
1010
1643
|
const verbDetail = s.verbs.map((v) => ` ${v}: ${s.verb_descriptions[v] ?? ""}`).join("\n");
|
|
1011
|
-
return `${
|
|
1644
|
+
return `${Formatter.keyValue(lines)}
|
|
1012
1645
|
Verb descriptions:
|
|
1013
1646
|
${verbDetail}`;
|
|
1014
1647
|
}
|
|
@@ -1016,30 +1649,30 @@ function registerServiceGetCommand(parent) {
|
|
|
1016
1649
|
const cmd = parent.command("get <service-id>").description("Retrieve a single merchant service by id");
|
|
1017
1650
|
cmd.action(async (serviceId) => {
|
|
1018
1651
|
const opts = cmd.optsWithGlobals();
|
|
1019
|
-
const format =
|
|
1652
|
+
const format = resolveFormat(opts.format);
|
|
1020
1653
|
const service = findService(serviceId);
|
|
1021
1654
|
if (!service) {
|
|
1022
|
-
throw new
|
|
1655
|
+
throw new CliError(
|
|
1023
1656
|
"SERVICE_NOT_FOUND",
|
|
1024
1657
|
`Service '${serviceId}' was not found in the registry. Run "services list" to see available services.`
|
|
1025
1658
|
);
|
|
1026
1659
|
}
|
|
1027
|
-
const configManager = new
|
|
1660
|
+
const configManager = new ConfigManager();
|
|
1028
1661
|
const commandResult = {
|
|
1029
1662
|
data: service,
|
|
1030
1663
|
text: () => formatServiceGet(service)
|
|
1031
1664
|
};
|
|
1032
|
-
await
|
|
1665
|
+
await renderWithContext(commandResult, { format }, configManager);
|
|
1033
1666
|
});
|
|
1034
1667
|
}
|
|
1035
1668
|
|
|
1036
1669
|
// src/index.ts
|
|
1037
1670
|
var programRef;
|
|
1038
1671
|
async function main() {
|
|
1039
|
-
const configManager = new
|
|
1672
|
+
const configManager = new ConfigManager();
|
|
1040
1673
|
await configManager.ensureDirectories();
|
|
1041
1674
|
const apiBaseUrl = await configManager.getApiBaseUrl();
|
|
1042
|
-
const apiClient = new
|
|
1675
|
+
const apiClient = new ApiClient({ baseUrl: apiBaseUrl });
|
|
1043
1676
|
const deps = { apiClient };
|
|
1044
1677
|
const program = new Command();
|
|
1045
1678
|
programRef = program;
|
|
@@ -1052,7 +1685,7 @@ async function main() {
|
|
|
1052
1685
|
).option("--api-key <key>", "API Key for authentication (X-Api-Key)");
|
|
1053
1686
|
program.hook("preAction", (thisCommand) => {
|
|
1054
1687
|
const flag = thisCommand.opts().format;
|
|
1055
|
-
process.env.AGENZO_FORMAT =
|
|
1688
|
+
process.env.AGENZO_FORMAT = resolveFormat(flag);
|
|
1056
1689
|
});
|
|
1057
1690
|
const rideCmd = program.command("ride-elife").description("Ride ordering (eLife)");
|
|
1058
1691
|
registerQuoteCommand(rideCmd, deps);
|
|
@@ -1067,7 +1700,7 @@ async function main() {
|
|
|
1067
1700
|
}
|
|
1068
1701
|
function resolveActiveFormat() {
|
|
1069
1702
|
const flag = programRef?.opts().format;
|
|
1070
|
-
return
|
|
1703
|
+
return resolveFormat(flag);
|
|
1071
1704
|
}
|
|
1072
1705
|
function reportError(error) {
|
|
1073
1706
|
const envelope = toErrorEnvelope(error);
|
|
@@ -1076,9 +1709,9 @@ function reportError(error) {
|
|
|
1076
1709
|
console.error(JSON.stringify(envelope));
|
|
1077
1710
|
} else {
|
|
1078
1711
|
console.error(
|
|
1079
|
-
|
|
1712
|
+
Formatter.status("error", `[${envelope.error.code_num}] ${envelope.error.message}`)
|
|
1080
1713
|
);
|
|
1081
|
-
if (!(error instanceof
|
|
1714
|
+
if (!(error instanceof CliError) && process.argv.includes("--verbose")) {
|
|
1082
1715
|
console.error(error);
|
|
1083
1716
|
}
|
|
1084
1717
|
}
|