@agenzo/merchant-cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -0
- package/SKILL.md +132 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +897 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command8 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/core/errors.ts
|
|
7
|
+
var ErrorCodes = {
|
|
8
|
+
QUOTE_EXPIRED: "QUOTE_EXPIRED",
|
|
9
|
+
VEHICLE_UNAVAILABLE: "VEHICLE_UNAVAILABLE",
|
|
10
|
+
BOOKING_FAILED: "BOOKING_FAILED",
|
|
11
|
+
CANCELLATION_NOT_ALLOWED: "CANCELLATION_NOT_ALLOWED",
|
|
12
|
+
SERVICE_NOT_FOUND: "SERVICE_NOT_FOUND",
|
|
13
|
+
PARAM_IDEMPOTENCY_KEY_REQUIRED: "PARAM_IDEMPOTENCY_KEY_REQUIRED",
|
|
14
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
15
|
+
TIMEOUT: "TIMEOUT",
|
|
16
|
+
CLI_OUTDATED: "CLI_OUTDATED",
|
|
17
|
+
CONFIG_ERROR: "CONFIG_ERROR",
|
|
18
|
+
NOT_IMPLEMENTED: "NOT_IMPLEMENTED"
|
|
19
|
+
};
|
|
20
|
+
var CliError = class extends Error {
|
|
21
|
+
constructor(code, message, recovery, exitCode = 1) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.recovery = recovery;
|
|
25
|
+
this.exitCode = exitCode;
|
|
26
|
+
this.name = "CliError";
|
|
27
|
+
}
|
|
28
|
+
code;
|
|
29
|
+
recovery;
|
|
30
|
+
exitCode;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/core/config-manager.ts
|
|
34
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
35
|
+
import { homedir } from "os";
|
|
36
|
+
import { join } from "path";
|
|
37
|
+
var DEFAULT_HOST = "https://agent.everonet.com";
|
|
38
|
+
var CONFIG_DIR = join(homedir(), ".agenzo-merchant-cli");
|
|
39
|
+
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
40
|
+
function read() {
|
|
41
|
+
let raw;
|
|
42
|
+
try {
|
|
43
|
+
raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error.code === "ENOENT") {
|
|
46
|
+
return { host: DEFAULT_HOST };
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(raw);
|
|
52
|
+
return { host: parsed.host ?? DEFAULT_HOST };
|
|
53
|
+
} catch {
|
|
54
|
+
throw new CliError(
|
|
55
|
+
ErrorCodes.CONFIG_ERROR,
|
|
56
|
+
`Invalid config file: ${CONFIG_PATH}`,
|
|
57
|
+
"Delete the file to reset to defaults."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function write(config) {
|
|
62
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
63
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
64
|
+
}
|
|
65
|
+
function getHost() {
|
|
66
|
+
return read().host;
|
|
67
|
+
}
|
|
68
|
+
function setHost(host) {
|
|
69
|
+
write({ host: host.replace(/\/+$/, "") });
|
|
70
|
+
}
|
|
71
|
+
function resetHost() {
|
|
72
|
+
write({ host: DEFAULT_HOST });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/core/formatter.ts
|
|
76
|
+
var ICONS = {
|
|
77
|
+
success: "\u2713",
|
|
78
|
+
error: "\u2717",
|
|
79
|
+
info: "\u2139",
|
|
80
|
+
warning: "\u26A0"
|
|
81
|
+
};
|
|
82
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
83
|
+
function createSpinner(message) {
|
|
84
|
+
const isTty = Boolean(process.stderr.isTTY);
|
|
85
|
+
let current = message;
|
|
86
|
+
if (!isTty) {
|
|
87
|
+
return {
|
|
88
|
+
update(msg) {
|
|
89
|
+
current = msg;
|
|
90
|
+
},
|
|
91
|
+
stop() {
|
|
92
|
+
},
|
|
93
|
+
succeed(msg) {
|
|
94
|
+
if (msg) process.stderr.write(`${Formatter.status("success", msg)}
|
|
95
|
+
`);
|
|
96
|
+
},
|
|
97
|
+
fail(msg) {
|
|
98
|
+
process.stderr.write(`${Formatter.status("error", msg ?? current)}
|
|
99
|
+
`);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
let frame = 0;
|
|
104
|
+
const render = () => {
|
|
105
|
+
process.stderr.write(`\r\x1B[K${SPINNER_FRAMES[frame]} ${current}`);
|
|
106
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
107
|
+
};
|
|
108
|
+
render();
|
|
109
|
+
const timer = setInterval(render, 80);
|
|
110
|
+
const clear = () => {
|
|
111
|
+
clearInterval(timer);
|
|
112
|
+
process.stderr.write("\r\x1B[K");
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
update(msg) {
|
|
116
|
+
current = msg;
|
|
117
|
+
},
|
|
118
|
+
stop() {
|
|
119
|
+
clear();
|
|
120
|
+
},
|
|
121
|
+
succeed(msg) {
|
|
122
|
+
clear();
|
|
123
|
+
if (msg) process.stderr.write(`${Formatter.status("success", msg)}
|
|
124
|
+
`);
|
|
125
|
+
},
|
|
126
|
+
fail(msg) {
|
|
127
|
+
clear();
|
|
128
|
+
process.stderr.write(`${Formatter.status("error", msg ?? current)}
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function stringify(value) {
|
|
134
|
+
if (value === null || value === void 0) return "";
|
|
135
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
136
|
+
return String(value);
|
|
137
|
+
}
|
|
138
|
+
var Formatter = class _Formatter {
|
|
139
|
+
/** Status-prefixed line, e.g. "✓ done". */
|
|
140
|
+
static status(type, message) {
|
|
141
|
+
return `${ICONS[type]} ${message}`;
|
|
142
|
+
}
|
|
143
|
+
/** Aligned columnar table for list output. */
|
|
144
|
+
static table(headers, rows) {
|
|
145
|
+
const widths = headers.map(
|
|
146
|
+
(h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
|
|
147
|
+
);
|
|
148
|
+
const line = (cells) => cells.map((c, i) => (c ?? "").padEnd(widths[i])).join(" ");
|
|
149
|
+
return [
|
|
150
|
+
line(headers),
|
|
151
|
+
widths.map((w) => "-".repeat(w)).join(" "),
|
|
152
|
+
...rows.map(line)
|
|
153
|
+
].join("\n");
|
|
154
|
+
}
|
|
155
|
+
/** Key-value detail output with left-aligned keys. */
|
|
156
|
+
static keyValue(entries) {
|
|
157
|
+
const width = Math.max(0, ...entries.map(([k]) => k.length));
|
|
158
|
+
return entries.map(([k, v]) => `${k.padEnd(width)} ${v}`).join("\n");
|
|
159
|
+
}
|
|
160
|
+
/** Best-effort human-readable rendering of arbitrary data. */
|
|
161
|
+
static auto(data) {
|
|
162
|
+
if (Array.isArray(data)) {
|
|
163
|
+
return _Formatter.arrayTable(data);
|
|
164
|
+
}
|
|
165
|
+
if (data && typeof data === "object") {
|
|
166
|
+
const obj = data;
|
|
167
|
+
const scalarEntries = [];
|
|
168
|
+
const tableSections = [];
|
|
169
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
170
|
+
if (Array.isArray(v) && v.some((i) => i && typeof i === "object")) {
|
|
171
|
+
tableSections.push(`${k}:
|
|
172
|
+
${_Formatter.arrayTable(v)}`);
|
|
173
|
+
} else {
|
|
174
|
+
scalarEntries.push([k, stringify(v)]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const parts = [];
|
|
178
|
+
if (scalarEntries.length) parts.push(_Formatter.keyValue(scalarEntries));
|
|
179
|
+
parts.push(...tableSections);
|
|
180
|
+
return parts.join("\n\n");
|
|
181
|
+
}
|
|
182
|
+
return stringify(data);
|
|
183
|
+
}
|
|
184
|
+
/** Render an array of objects as an aligned table; falls back gracefully. */
|
|
185
|
+
static arrayTable(items) {
|
|
186
|
+
if (items.length === 0) return "(empty)";
|
|
187
|
+
const rows = items.filter(
|
|
188
|
+
(i) => Boolean(i) && typeof i === "object"
|
|
189
|
+
);
|
|
190
|
+
if (rows.length === 0) {
|
|
191
|
+
return items.map((i) => stringify(i)).join(", ");
|
|
192
|
+
}
|
|
193
|
+
const headers = Object.keys(rows[0]);
|
|
194
|
+
return _Formatter.table(
|
|
195
|
+
headers,
|
|
196
|
+
rows.map((r) => headers.map((h) => stringify(r[h])))
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/core/output.ts
|
|
202
|
+
function emit(data, format = "json") {
|
|
203
|
+
if (format === "table") {
|
|
204
|
+
console.log(Formatter.auto(data));
|
|
205
|
+
} else {
|
|
206
|
+
console.log(JSON.stringify(data, null, 2));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function emitSchema(schema, format = "json") {
|
|
210
|
+
if (format === "table") {
|
|
211
|
+
console.log(Formatter.keyValue(Object.entries(schema.params)));
|
|
212
|
+
} else {
|
|
213
|
+
console.log(JSON.stringify(schema, null, 2));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/core/version.ts
|
|
218
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
219
|
+
import { dirname, join as join2 } from "path";
|
|
220
|
+
import { fileURLToPath } from "url";
|
|
221
|
+
var UPGRADE_COMMAND = "npm install -g agenzo-merchant-cli@latest";
|
|
222
|
+
var FALLBACK_VERSION = "0.1.0";
|
|
223
|
+
var cached = null;
|
|
224
|
+
function getCurrentVersion() {
|
|
225
|
+
if (cached !== null) return cached;
|
|
226
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
227
|
+
const candidates = [
|
|
228
|
+
join2(here, "..", "package.json"),
|
|
229
|
+
join2(here, "..", "..", "package.json")
|
|
230
|
+
];
|
|
231
|
+
for (const p of candidates) {
|
|
232
|
+
try {
|
|
233
|
+
const pkg = JSON.parse(readFileSync2(p, "utf-8"));
|
|
234
|
+
if (typeof pkg.version === "string") {
|
|
235
|
+
cached = pkg.version;
|
|
236
|
+
return cached;
|
|
237
|
+
}
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
cached = FALLBACK_VERSION;
|
|
242
|
+
return cached;
|
|
243
|
+
}
|
|
244
|
+
function compareVersion(a, b) {
|
|
245
|
+
const parse = (v) => v.trim().replace(/^v/, "").split(/[-+]/)[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
246
|
+
const pa = parse(a);
|
|
247
|
+
const pb = parse(b);
|
|
248
|
+
for (let i = 0; i < 3; i++) {
|
|
249
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
250
|
+
if (diff !== 0) return diff;
|
|
251
|
+
}
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
function enforceMinVersion(header) {
|
|
255
|
+
const min = header?.trim();
|
|
256
|
+
if (!min) return;
|
|
257
|
+
const current = getCurrentVersion();
|
|
258
|
+
if (compareVersion(current, min) < 0) {
|
|
259
|
+
throw new CliError(
|
|
260
|
+
ErrorCodes.CLI_OUTDATED,
|
|
261
|
+
`CLI ${current} is below the required minimum ${min}.`,
|
|
262
|
+
`Upgrade: ${UPGRADE_COMMAND}`,
|
|
263
|
+
2
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/ride/quote.ts
|
|
269
|
+
import { Command } from "commander";
|
|
270
|
+
|
|
271
|
+
// src/core/api-client.ts
|
|
272
|
+
var RIDE_PREFIX = "/api/v3/agent-pay";
|
|
273
|
+
var ApiClient = class {
|
|
274
|
+
host;
|
|
275
|
+
apiKey;
|
|
276
|
+
timeout;
|
|
277
|
+
constructor(options) {
|
|
278
|
+
this.apiKey = options.apiKey;
|
|
279
|
+
this.host = (options.host ?? getHost()).replace(/\/+$/, "");
|
|
280
|
+
this.timeout = options.timeout ?? 3e4;
|
|
281
|
+
}
|
|
282
|
+
get(path, options = {}) {
|
|
283
|
+
return this.request("GET", path, options);
|
|
284
|
+
}
|
|
285
|
+
post(path, options = {}) {
|
|
286
|
+
return this.request("POST", path, options);
|
|
287
|
+
}
|
|
288
|
+
async request(method, path, options) {
|
|
289
|
+
let url = `${this.host}${RIDE_PREFIX}${path}`;
|
|
290
|
+
if (options.query) {
|
|
291
|
+
const params = new URLSearchParams();
|
|
292
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
293
|
+
if (value !== void 0) params.set(key, String(value));
|
|
294
|
+
}
|
|
295
|
+
const qs = params.toString();
|
|
296
|
+
if (qs) url += `?${qs}`;
|
|
297
|
+
}
|
|
298
|
+
const headers = {
|
|
299
|
+
"X-Api-Key": this.apiKey,
|
|
300
|
+
"User-Agent": `agenzo-merchant-cli/${getCurrentVersion()}`
|
|
301
|
+
};
|
|
302
|
+
if (options.body) headers["Content-Type"] = "application/json";
|
|
303
|
+
if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
304
|
+
const controller = new AbortController();
|
|
305
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
306
|
+
let response;
|
|
307
|
+
try {
|
|
308
|
+
response = await fetch(url, {
|
|
309
|
+
method,
|
|
310
|
+
headers,
|
|
311
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
312
|
+
signal: controller.signal
|
|
313
|
+
});
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
316
|
+
throw new CliError(
|
|
317
|
+
ErrorCodes.TIMEOUT,
|
|
318
|
+
`Request timed out after ${this.timeout}ms: ${url}`,
|
|
319
|
+
"Retry, or check your network connection."
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
throw new CliError(
|
|
323
|
+
ErrorCodes.NETWORK_ERROR,
|
|
324
|
+
`Network request failed: ${url}`,
|
|
325
|
+
"Check your network connection and the configured API host."
|
|
326
|
+
);
|
|
327
|
+
} finally {
|
|
328
|
+
clearTimeout(timer);
|
|
329
|
+
}
|
|
330
|
+
enforceMinVersion(response.headers.get("x-cli-min-version"));
|
|
331
|
+
const text = await response.text();
|
|
332
|
+
let envelope;
|
|
333
|
+
try {
|
|
334
|
+
envelope = JSON.parse(text);
|
|
335
|
+
} catch {
|
|
336
|
+
throw new CliError(
|
|
337
|
+
ErrorCodes.NETWORK_ERROR,
|
|
338
|
+
`Unexpected non-JSON response (HTTP ${response.status}) from ${url}`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
if (envelope.code !== void 0 && envelope.code !== "0000") {
|
|
342
|
+
const code = envelope.code;
|
|
343
|
+
const message = envelope.message ?? `Request failed (HTTP ${response.status})`;
|
|
344
|
+
throw new CliError(code, message);
|
|
345
|
+
}
|
|
346
|
+
return envelope.data;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// src/core/prompt-engine.ts
|
|
351
|
+
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
352
|
+
var PromptEngine = class {
|
|
353
|
+
static async resolveInput(flagValue, options) {
|
|
354
|
+
if (flagValue !== void 0) return flagValue;
|
|
355
|
+
if (options.type === "password") {
|
|
356
|
+
return password({ message: options.message, mask: "*" });
|
|
357
|
+
}
|
|
358
|
+
if (options.type === "select" && options.choices) {
|
|
359
|
+
return select({ message: options.message, choices: options.choices });
|
|
360
|
+
}
|
|
361
|
+
return input({ message: options.message, validate: options.validate });
|
|
362
|
+
}
|
|
363
|
+
/** Confirmation prompt; auto-approves when `--yes` is set. */
|
|
364
|
+
static async confirm(message, yes) {
|
|
365
|
+
if (yes) return true;
|
|
366
|
+
return confirm({ message });
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/utils/cli-help.ts
|
|
371
|
+
function explicitFormat() {
|
|
372
|
+
const argv = process.argv;
|
|
373
|
+
for (let i = 0; i < argv.length; i++) {
|
|
374
|
+
const a = argv[i];
|
|
375
|
+
if (a === "--format=json" || a === "--format" && argv[i + 1] === "json") return "json";
|
|
376
|
+
if (a === "--format=table" || a === "--format" && argv[i + 1] === "table") return "table";
|
|
377
|
+
}
|
|
378
|
+
return void 0;
|
|
379
|
+
}
|
|
380
|
+
function attachSchemaHelp(cmd, schema) {
|
|
381
|
+
const baseHelp = cmd.helpInformation.bind(cmd);
|
|
382
|
+
cmd.helpInformation = (ctx) => {
|
|
383
|
+
const format = explicitFormat();
|
|
384
|
+
if (!format) return baseHelp(ctx);
|
|
385
|
+
emitSchema(schema, format);
|
|
386
|
+
return "";
|
|
387
|
+
};
|
|
388
|
+
return cmd;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/ride/quote.ts
|
|
392
|
+
var quoteSchema = {
|
|
393
|
+
description: "Request fare quotes for a ride between two points.",
|
|
394
|
+
params: {
|
|
395
|
+
"pickup-lat": "number \u2014 pickup latitude",
|
|
396
|
+
"pickup-lng": "number \u2014 pickup longitude",
|
|
397
|
+
"pickup-name": "string \u2014 pickup location name",
|
|
398
|
+
"dropoff-lat": "number \u2014 dropoff latitude",
|
|
399
|
+
"dropoff-lng": "number \u2014 dropoff longitude",
|
|
400
|
+
"dropoff-name": "string \u2014 dropoff location name",
|
|
401
|
+
"pickup-time": 'number|string \u2014 epoch seconds, or "now"',
|
|
402
|
+
"passenger-name": "string \u2014 lead passenger full name",
|
|
403
|
+
"passenger-phone": "string \u2014 passenger phone in E.164 format",
|
|
404
|
+
"passenger-count": "number \u2014 optional passenger count",
|
|
405
|
+
"luggage-count": "number \u2014 optional luggage count",
|
|
406
|
+
"passenger-email": "string \u2014 optional passenger email",
|
|
407
|
+
"children-count": "number \u2014 optional children count"
|
|
408
|
+
},
|
|
409
|
+
response: {
|
|
410
|
+
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)",
|
|
411
|
+
meet_and_greet: "object|null \u2014 { available: boolean, price: { amount, currency } } when applicable",
|
|
412
|
+
is_airport_transfer: "boolean",
|
|
413
|
+
airport_direction: "string|null \u2014 pickup/dropoff direction for airport transfers"
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
function need(value, flag) {
|
|
417
|
+
if (value === void 0) throw new CliError("PARAM_REQUIRED", `Missing required --${flag}.`);
|
|
418
|
+
return value;
|
|
419
|
+
}
|
|
420
|
+
function num(value, flag) {
|
|
421
|
+
const n = Number(need(value, flag));
|
|
422
|
+
if (!Number.isFinite(n)) throw new CliError("PARAM_INVALID", `--${flag} must be a number.`);
|
|
423
|
+
return n;
|
|
424
|
+
}
|
|
425
|
+
function buildQuoteCommand() {
|
|
426
|
+
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) => {
|
|
427
|
+
const o = command.optsWithGlobals();
|
|
428
|
+
const format = o.format === "table" ? "table" : "json";
|
|
429
|
+
const apiKey = await PromptEngine.resolveInput(o.apiKey, {
|
|
430
|
+
message: "API key:",
|
|
431
|
+
type: "password"
|
|
432
|
+
});
|
|
433
|
+
const body = {
|
|
434
|
+
pickup: {
|
|
435
|
+
lat: num(o.pickupLat, "pickup-lat"),
|
|
436
|
+
lng: num(o.pickupLng, "pickup-lng"),
|
|
437
|
+
name: need(o.pickupName, "pickup-name")
|
|
438
|
+
},
|
|
439
|
+
dropoff: {
|
|
440
|
+
lat: num(o.dropoffLat, "dropoff-lat"),
|
|
441
|
+
lng: num(o.dropoffLng, "dropoff-lng"),
|
|
442
|
+
name: need(o.dropoffName, "dropoff-name")
|
|
443
|
+
},
|
|
444
|
+
pickup_time: o.pickupTime === "now" ? "now" : num(o.pickupTime, "pickup-time"),
|
|
445
|
+
passenger_name: need(o.passengerName, "passenger-name"),
|
|
446
|
+
passenger_phone: need(o.passengerPhone, "passenger-phone")
|
|
447
|
+
};
|
|
448
|
+
if (o.passengerCount !== void 0) body.passenger_count = num(o.passengerCount, "passenger-count");
|
|
449
|
+
if (o.luggageCount !== void 0) body.luggage_count = num(o.luggageCount, "luggage-count");
|
|
450
|
+
if (o.childrenCount !== void 0) body.children_count = num(o.childrenCount, "children-count");
|
|
451
|
+
if (o.passengerEmail !== void 0) body.passenger_email = o.passengerEmail;
|
|
452
|
+
const client = new ApiClient({ apiKey });
|
|
453
|
+
const spinner = createSpinner("Fetching quotes...");
|
|
454
|
+
try {
|
|
455
|
+
const data = await client.post("/ride/quote", { body });
|
|
456
|
+
spinner.stop();
|
|
457
|
+
emit(data, format);
|
|
458
|
+
} catch (err) {
|
|
459
|
+
spinner.fail("Quote request failed");
|
|
460
|
+
throw err;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
return attachSchemaHelp(cmd, quoteSchema);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/ride/book.ts
|
|
467
|
+
import { Command as Command2 } from "commander";
|
|
468
|
+
|
|
469
|
+
// src/utils/idempotency.ts
|
|
470
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
471
|
+
var KEY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
472
|
+
var RULE = "Use 1-128 characters from [A-Za-z0-9_-].";
|
|
473
|
+
function normalizeIdempotencyKey(key) {
|
|
474
|
+
const trimmed = key.trim();
|
|
475
|
+
if (!KEY_PATTERN.test(trimmed)) {
|
|
476
|
+
throw new CliError(
|
|
477
|
+
ErrorCodes.PARAM_IDEMPOTENCY_KEY_REQUIRED,
|
|
478
|
+
`Invalid idempotency key: "${key}".`,
|
|
479
|
+
RULE
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
return trimmed;
|
|
483
|
+
}
|
|
484
|
+
async function resolveIdempotencyKey(flagValue, options) {
|
|
485
|
+
if (flagValue !== void 0) return normalizeIdempotencyKey(flagValue);
|
|
486
|
+
if (options.yes) {
|
|
487
|
+
throw new CliError(
|
|
488
|
+
ErrorCodes.PARAM_IDEMPOTENCY_KEY_REQUIRED,
|
|
489
|
+
"An idempotency key is required for write operations.",
|
|
490
|
+
"Pass --idempotency-key <key>; the CLI never auto-generates one."
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
const entered = await input2({
|
|
494
|
+
message: "Idempotency key:",
|
|
495
|
+
validate: (v) => KEY_PATTERN.test(v.trim()) || RULE
|
|
496
|
+
});
|
|
497
|
+
return normalizeIdempotencyKey(entered);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/ride/book.ts
|
|
501
|
+
var bookSchema = {
|
|
502
|
+
description: "Book a ride against a previously returned quote.",
|
|
503
|
+
params: {
|
|
504
|
+
"quote-id": "string \u2014 required, quote id from `ride quote`",
|
|
505
|
+
"vehicle-class": "string \u2014 required, chosen vehicle class",
|
|
506
|
+
"price-amount": "number \u2014 required, fare in decimal currency units (not cents)",
|
|
507
|
+
"price-currency": "string \u2014 currency code (default USD)",
|
|
508
|
+
"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)",
|
|
509
|
+
"passenger-name": "string \u2014 required, passenger full name",
|
|
510
|
+
"passenger-phone": "string \u2014 required, passenger phone",
|
|
511
|
+
"passenger-email": "string \u2014 optional passenger email",
|
|
512
|
+
"luggage-count": "number \u2014 optional luggage count",
|
|
513
|
+
"special-requests": "string \u2014 optional free-text requests",
|
|
514
|
+
"pickup-lat": "number \u2014 required, pickup latitude",
|
|
515
|
+
"pickup-lng": "number \u2014 required, pickup longitude",
|
|
516
|
+
"pickup-name": "string \u2014 required, pickup location name",
|
|
517
|
+
"dropoff-lat": "number \u2014 required, dropoff latitude",
|
|
518
|
+
"dropoff-lng": "number \u2014 required, dropoff longitude",
|
|
519
|
+
"dropoff-name": "string \u2014 required, dropoff location name",
|
|
520
|
+
"pickup-time": 'number|string \u2014 required, epoch seconds or "now" (must match quote)',
|
|
521
|
+
"meet-and-greet": "boolean \u2014 optional meet & greet service",
|
|
522
|
+
"meet-and-greet-price": "number \u2014 optional meet & greet surcharge",
|
|
523
|
+
"welcome-sign": "string \u2014 optional welcome sign text",
|
|
524
|
+
"arrival-flight-no": "string \u2014 optional arrival flight number",
|
|
525
|
+
"arrival-airline": "string \u2014 optional arrival airline",
|
|
526
|
+
"departure-flight-no": "string \u2014 optional departure flight number",
|
|
527
|
+
"departure-airline": "string \u2014 optional departure airline",
|
|
528
|
+
"idempotency-key": "string \u2014 required, sent as the Idempotency-Key header (never in body)"
|
|
529
|
+
},
|
|
530
|
+
response: {
|
|
531
|
+
ride_id: "string \u2014 booked ride id, used by `ride get` / `ride cancel`",
|
|
532
|
+
order_id: "string \u2014 internal ride order id (rio_...)",
|
|
533
|
+
status: "string \u2014 initial ride status (e.g. INIT / Pending)",
|
|
534
|
+
price: "{ amount, currency, quote_id } \u2014 confirmed fare",
|
|
535
|
+
payment_status: "string \u2014 ON_ACCOUNT (monthly_settlement) or PAID (pay_per_call)",
|
|
536
|
+
billing_entry_id: "string \u2014 settlement ledger entry id (monthly_settlement only)"
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
function buildBookCommand() {
|
|
540
|
+
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 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) => {
|
|
541
|
+
const merged = command.optsWithGlobals();
|
|
542
|
+
const format = merged.format === "table" ? "table" : "json";
|
|
543
|
+
if (merged.help) {
|
|
544
|
+
const wantsSchema = process.argv.some((a) => a === "--format" || a.startsWith("--format="));
|
|
545
|
+
if (wantsSchema) emitSchema(bookSchema, format);
|
|
546
|
+
else command.outputHelp();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const yes = merged.yes === true;
|
|
550
|
+
const apiKey = await PromptEngine.resolveInput(merged.apiKey, {
|
|
551
|
+
message: "API key:",
|
|
552
|
+
type: "password"
|
|
553
|
+
});
|
|
554
|
+
const quoteId = await PromptEngine.resolveInput(merged.quoteId, { message: "Quote id:" });
|
|
555
|
+
const vehicleClass = await PromptEngine.resolveInput(merged.vehicleClass, {
|
|
556
|
+
message: "Vehicle class:"
|
|
557
|
+
});
|
|
558
|
+
const priceAmount = await PromptEngine.resolveInput(merged.priceAmount, {
|
|
559
|
+
message: "Price amount (decimal):"
|
|
560
|
+
});
|
|
561
|
+
const passengerName = await PromptEngine.resolveInput(merged.passengerName, {
|
|
562
|
+
message: "Passenger name:"
|
|
563
|
+
});
|
|
564
|
+
const passengerPhone = await PromptEngine.resolveInput(merged.passengerPhone, {
|
|
565
|
+
message: "Passenger phone:"
|
|
566
|
+
});
|
|
567
|
+
const body = {
|
|
568
|
+
quote_id: quoteId,
|
|
569
|
+
vehicle_class: vehicleClass,
|
|
570
|
+
price_amount: Number(priceAmount),
|
|
571
|
+
price_currency: merged.priceCurrency ?? "USD",
|
|
572
|
+
passenger_name: passengerName,
|
|
573
|
+
passenger_phone: passengerPhone
|
|
574
|
+
};
|
|
575
|
+
if (merged.paymentOrderId) body.payment_order_id = merged.paymentOrderId;
|
|
576
|
+
if (merged.passengerEmail) body.passenger_email = merged.passengerEmail;
|
|
577
|
+
if (merged.luggageCount !== void 0) body.luggage_count = Number(merged.luggageCount);
|
|
578
|
+
if (merged.specialRequests) body.special_requests = merged.specialRequests;
|
|
579
|
+
if (merged.pickupLat || merged.pickupLng || merged.pickupName) {
|
|
580
|
+
body.pickup = { lat: Number(merged.pickupLat), lng: Number(merged.pickupLng), name: merged.pickupName };
|
|
581
|
+
}
|
|
582
|
+
if (merged.dropoffLat || merged.dropoffLng || merged.dropoffName) {
|
|
583
|
+
body.dropoff = { lat: Number(merged.dropoffLat), lng: Number(merged.dropoffLng), name: merged.dropoffName };
|
|
584
|
+
}
|
|
585
|
+
if (merged.pickupTime) body.pickup_time = merged.pickupTime;
|
|
586
|
+
if (merged.meetAndGreet) body.meet_and_greet = true;
|
|
587
|
+
if (merged.meetAndGreetPrice !== void 0) body.meet_and_greet_price = Number(merged.meetAndGreetPrice);
|
|
588
|
+
if (merged.welcomeSign) body.welcome_sign = merged.welcomeSign;
|
|
589
|
+
if (merged.arrivalFlightNo || merged.arrivalAirline) {
|
|
590
|
+
body.arrival_flight = { flight_no: merged.arrivalFlightNo, airline: merged.arrivalAirline };
|
|
591
|
+
}
|
|
592
|
+
if (merged.departureFlightNo || merged.departureAirline) {
|
|
593
|
+
body.departure_flight = { flight_no: merged.departureFlightNo, airline: merged.departureAirline };
|
|
594
|
+
}
|
|
595
|
+
if (!await PromptEngine.confirm(`Book ride with quote ${quoteId}?`, yes)) {
|
|
596
|
+
console.log(Formatter.status("info", "Booking aborted."));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const idempotencyKey = await resolveIdempotencyKey(merged.idempotencyKey, { yes });
|
|
600
|
+
const client = new ApiClient({ apiKey });
|
|
601
|
+
const spinner = createSpinner("Booking ride...");
|
|
602
|
+
try {
|
|
603
|
+
const data = await client.post("/ride/book", { body, idempotencyKey });
|
|
604
|
+
spinner.stop();
|
|
605
|
+
emit(data, format);
|
|
606
|
+
} catch (err) {
|
|
607
|
+
spinner.fail("Booking failed");
|
|
608
|
+
throw err;
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/ride/get.ts
|
|
614
|
+
import { Command as Command3 } from "commander";
|
|
615
|
+
var rideGetSchema = {
|
|
616
|
+
description: "Retrieve a single ride order status by id (repeatable for polling).",
|
|
617
|
+
params: {
|
|
618
|
+
"order-id": "string \u2014 ride order id (the ride_id returned by `ride book`)"
|
|
619
|
+
},
|
|
620
|
+
response: {
|
|
621
|
+
status: "OrderStatus \u2014 case-sensitive; terminal states: At destination | Cancelled | Rejected | Customer no show | Driver no show",
|
|
622
|
+
from: "GeoPoint \u2014 pickup",
|
|
623
|
+
to: "GeoPoint \u2014 dropoff",
|
|
624
|
+
pickup_time: "number|string",
|
|
625
|
+
vehicle_class: "string",
|
|
626
|
+
price: "Price{amount,currency} \u2014 amount is a decimal in the currency standard unit",
|
|
627
|
+
passenger: "Passenger",
|
|
628
|
+
driver: "Driver \u2014 present after assignment",
|
|
629
|
+
vehicle: "Vehicle \u2014 present after assignment"
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
function buildRideGetCommand() {
|
|
633
|
+
const cmd = new Command3("get").description(rideGetSchema.description).option("--order-id <id>", "Ride order id (the ride_id returned by `ride book`)").action(async (_opts, command) => {
|
|
634
|
+
const o = command.optsWithGlobals();
|
|
635
|
+
const format = o.format === "table" ? "table" : "json";
|
|
636
|
+
const apiKey = await PromptEngine.resolveInput(o.apiKey, {
|
|
637
|
+
message: "API key:",
|
|
638
|
+
type: "password"
|
|
639
|
+
});
|
|
640
|
+
const orderId = await PromptEngine.resolveInput(o.orderId, { message: "Order id:" });
|
|
641
|
+
const client = new ApiClient({ apiKey });
|
|
642
|
+
const spinner = createSpinner("Fetching ride status...");
|
|
643
|
+
try {
|
|
644
|
+
const data = await client.get(
|
|
645
|
+
`/ride/${encodeURIComponent(orderId)}/status`
|
|
646
|
+
);
|
|
647
|
+
spinner.stop();
|
|
648
|
+
emit(data, format);
|
|
649
|
+
} catch (err) {
|
|
650
|
+
spinner.fail("Failed to fetch ride status");
|
|
651
|
+
throw err;
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
return attachSchemaHelp(cmd, rideGetSchema);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/ride/cancel.ts
|
|
658
|
+
import { Command as Command4 } from "commander";
|
|
659
|
+
var cancelSchema = {
|
|
660
|
+
description: "Cancel a ride order; may incur a cancellation fee.",
|
|
661
|
+
params: {
|
|
662
|
+
"order-id": "string \u2014 required, order id (a.k.a. ride id) to cancel",
|
|
663
|
+
"idempotency-key": "string \u2014 required, sent as the Idempotency-Key header (never in body)"
|
|
664
|
+
},
|
|
665
|
+
response: {
|
|
666
|
+
ride_id: "string \u2014 cancelled ride id",
|
|
667
|
+
ride_stat: "string \u2014 resulting ride status",
|
|
668
|
+
cancellation: "{ cancellation_fee, reversal_amount, currency }"
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
function buildCancelCommand() {
|
|
672
|
+
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) => {
|
|
673
|
+
const merged = command.optsWithGlobals();
|
|
674
|
+
const format = merged.format === "table" ? "table" : "json";
|
|
675
|
+
if (merged.help) {
|
|
676
|
+
const wantsSchema = process.argv.some((a) => a === "--format" || a.startsWith("--format="));
|
|
677
|
+
if (wantsSchema) emitSchema(cancelSchema, format);
|
|
678
|
+
else command.outputHelp();
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const yes = merged.yes === true;
|
|
682
|
+
const apiKey = await PromptEngine.resolveInput(merged.apiKey, {
|
|
683
|
+
message: "API key:",
|
|
684
|
+
type: "password"
|
|
685
|
+
});
|
|
686
|
+
const orderId = await PromptEngine.resolveInput(merged.orderId, { message: "Order id:" });
|
|
687
|
+
if (!await PromptEngine.confirm(`Cancel order ${orderId}? This may incur a fee.`, yes)) {
|
|
688
|
+
console.log(Formatter.status("info", "Cancellation aborted."));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const idempotencyKey = await resolveIdempotencyKey(merged.idempotencyKey, { yes });
|
|
692
|
+
const client = new ApiClient({ apiKey });
|
|
693
|
+
const spinner = createSpinner("Cancelling ride...");
|
|
694
|
+
try {
|
|
695
|
+
const data = await client.post(`/ride/${encodeURIComponent(orderId)}/cancel`, { idempotencyKey });
|
|
696
|
+
spinner.stop();
|
|
697
|
+
emit(data, format);
|
|
698
|
+
} catch (err) {
|
|
699
|
+
spinner.fail("Cancellation failed");
|
|
700
|
+
throw err;
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/ride/list-orders.ts
|
|
706
|
+
import { Command as Command5 } from "commander";
|
|
707
|
+
var listOrdersSchema = {
|
|
708
|
+
description: "List ride orders with pagination and optional filters.",
|
|
709
|
+
params: {
|
|
710
|
+
page: "number \u2014 page number (default 1)",
|
|
711
|
+
"page-size": "number \u2014 page size (default 20)",
|
|
712
|
+
status: "OrderStatus \u2014 optional status filter",
|
|
713
|
+
"order-type": "string \u2014 optional order type filter"
|
|
714
|
+
},
|
|
715
|
+
response: {
|
|
716
|
+
orders: "Order[]",
|
|
717
|
+
total: "number",
|
|
718
|
+
page: "number",
|
|
719
|
+
page_size: "number"
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
function buildListOrdersCommand() {
|
|
723
|
+
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) => {
|
|
724
|
+
const o = command.optsWithGlobals();
|
|
725
|
+
const format = o.format === "table" ? "table" : "json";
|
|
726
|
+
const apiKey = await PromptEngine.resolveInput(o.apiKey, {
|
|
727
|
+
message: "API key:",
|
|
728
|
+
type: "password"
|
|
729
|
+
});
|
|
730
|
+
const client = new ApiClient({ apiKey });
|
|
731
|
+
const spinner = createSpinner("Fetching orders...");
|
|
732
|
+
try {
|
|
733
|
+
const data = await client.get("/ride/orders", {
|
|
734
|
+
query: {
|
|
735
|
+
page: Number(o.page),
|
|
736
|
+
page_size: Number(o.pageSize),
|
|
737
|
+
status: o.status,
|
|
738
|
+
order_type: o.orderType
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
spinner.stop();
|
|
742
|
+
emit(data, format);
|
|
743
|
+
} catch (err) {
|
|
744
|
+
spinner.fail("Failed to fetch orders");
|
|
745
|
+
throw err;
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
return attachSchemaHelp(cmd, listOrdersSchema);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/services/list.ts
|
|
752
|
+
import { Command as Command6 } from "commander";
|
|
753
|
+
|
|
754
|
+
// src/services/registry.ts
|
|
755
|
+
var SERVICE_REGISTRY = [
|
|
756
|
+
{
|
|
757
|
+
service_id: "ride",
|
|
758
|
+
name: "Ride",
|
|
759
|
+
description: "On-demand ride ordering: quote a fare, book it, poll status, and cancel.",
|
|
760
|
+
version: "1.0.0",
|
|
761
|
+
provider: "elife",
|
|
762
|
+
cli_noun: "ride",
|
|
763
|
+
verbs: ["quote", "book", "get", "cancel", "list-orders"],
|
|
764
|
+
verb_descriptions: {
|
|
765
|
+
quote: "Request fare quotes for a ride between two points.",
|
|
766
|
+
book: "Book a ride using a quote_id returned by quote.",
|
|
767
|
+
get: "Retrieve a ride order by id (poll for status changes).",
|
|
768
|
+
cancel: "Cancel a ride order by id.",
|
|
769
|
+
"list-orders": "List previously placed ride orders."
|
|
770
|
+
},
|
|
771
|
+
workflow: ["quote", "book", "get (poll for status)", "cancel (optional)"],
|
|
772
|
+
since: "2026-06-01",
|
|
773
|
+
discovery: { help_command: "agenzo-merchant-cli ride --help" },
|
|
774
|
+
billing: { mode: "pay_per_call" }
|
|
775
|
+
}
|
|
776
|
+
];
|
|
777
|
+
function findService(serviceId) {
|
|
778
|
+
return SERVICE_REGISTRY.find((s) => s.service_id === serviceId);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/services/list.ts
|
|
782
|
+
var servicesListSchema = {
|
|
783
|
+
description: "List available merchant services from the registry.",
|
|
784
|
+
params: {},
|
|
785
|
+
response: {
|
|
786
|
+
services: "Service[] \u2014 each with service_id, name, provider, cli_noun, version, verbs, billing_mode"
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
function resolveFormat(cmd) {
|
|
790
|
+
return cmd.optsWithGlobals().format === "table" ? "table" : "json";
|
|
791
|
+
}
|
|
792
|
+
function buildServicesListCommand() {
|
|
793
|
+
return new Command6("list").description(servicesListSchema.description).helpOption(false).option("-h, --help", "Show help; with --format json prints the schema").action((opts, cmd) => {
|
|
794
|
+
const format = resolveFormat(cmd);
|
|
795
|
+
if (opts.help) {
|
|
796
|
+
emitSchema(servicesListSchema, format);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const services = SERVICE_REGISTRY.map((s) => ({
|
|
800
|
+
service_id: s.service_id,
|
|
801
|
+
name: s.name,
|
|
802
|
+
provider: s.provider,
|
|
803
|
+
cli_noun: s.cli_noun,
|
|
804
|
+
version: s.version,
|
|
805
|
+
verbs: s.verbs.join(", "),
|
|
806
|
+
billing_mode: s.billing.mode
|
|
807
|
+
}));
|
|
808
|
+
emit(services, format);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/services/get.ts
|
|
813
|
+
import { Command as Command7 } from "commander";
|
|
814
|
+
var serviceGetSchema = {
|
|
815
|
+
description: "Retrieve a single merchant service by id, with verb and workflow detail.",
|
|
816
|
+
params: {
|
|
817
|
+
"service-id": "string \u2014 service id to retrieve"
|
|
818
|
+
},
|
|
819
|
+
response: {
|
|
820
|
+
service_id: "string",
|
|
821
|
+
name: "string",
|
|
822
|
+
description: "string",
|
|
823
|
+
version: "string",
|
|
824
|
+
provider: "string",
|
|
825
|
+
cli_noun: "string",
|
|
826
|
+
verbs: "string[]",
|
|
827
|
+
verb_descriptions: "Record<verb, description>",
|
|
828
|
+
workflow: "string[] \u2014 ordered steps quote\u2192book\u2192get(poll)\u2192[cancel]",
|
|
829
|
+
since: "string \u2014 ISO date",
|
|
830
|
+
discovery: "{ help_command: string }",
|
|
831
|
+
billing: '{ mode: "pay_per_call" }'
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
function resolveFormat2(cmd) {
|
|
835
|
+
return cmd.optsWithGlobals().format === "table" ? "table" : "json";
|
|
836
|
+
}
|
|
837
|
+
function buildServiceGetCommand() {
|
|
838
|
+
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) => {
|
|
839
|
+
const format = resolveFormat2(cmd);
|
|
840
|
+
if (opts.help) {
|
|
841
|
+
emitSchema(serviceGetSchema, format);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const service = serviceId ? findService(serviceId) : void 0;
|
|
845
|
+
if (!service) {
|
|
846
|
+
throw new CliError(
|
|
847
|
+
ErrorCodes.SERVICE_NOT_FOUND,
|
|
848
|
+
`Service '${serviceId ?? ""}' not found in the registry (SERVICE_NOT_FOUND).`,
|
|
849
|
+
'Run "services list" to see available services.'
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
emit(service, format);
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/index.ts
|
|
857
|
+
function resolveFormat3(cmd) {
|
|
858
|
+
return cmd.optsWithGlobals().format === "table" ? "table" : "json";
|
|
859
|
+
}
|
|
860
|
+
async function main() {
|
|
861
|
+
const program = new Command8();
|
|
862
|
+
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");
|
|
863
|
+
const config = program.command("config").description("Local API host configuration");
|
|
864
|
+
config.command("set-host <url>").description("Set the API host").action((url) => {
|
|
865
|
+
setHost(url);
|
|
866
|
+
console.log(Formatter.status("success", `API host set to ${getHost()}`));
|
|
867
|
+
});
|
|
868
|
+
config.command("reset-host").description("Reset the API host to the default").action(() => {
|
|
869
|
+
resetHost();
|
|
870
|
+
console.log(Formatter.status("success", `API host reset to ${getHost()}`));
|
|
871
|
+
});
|
|
872
|
+
config.command("show").description("Show the current configuration").action((_options, cmd) => {
|
|
873
|
+
emit({ host: getHost() }, resolveFormat3(cmd));
|
|
874
|
+
});
|
|
875
|
+
const ride = program.command("ride").description("Ride ordering: quote, book, get, cancel, list-orders");
|
|
876
|
+
ride.addCommand(buildQuoteCommand());
|
|
877
|
+
ride.addCommand(buildBookCommand());
|
|
878
|
+
ride.addCommand(buildRideGetCommand());
|
|
879
|
+
ride.addCommand(buildCancelCommand());
|
|
880
|
+
ride.addCommand(buildListOrdersCommand());
|
|
881
|
+
const services = program.command("services").description("Service registry: list, get");
|
|
882
|
+
services.addCommand(buildServicesListCommand());
|
|
883
|
+
services.addCommand(buildServiceGetCommand());
|
|
884
|
+
await program.parseAsync(process.argv);
|
|
885
|
+
}
|
|
886
|
+
main().catch((error) => {
|
|
887
|
+
if (error instanceof CliError) {
|
|
888
|
+
console.error(Formatter.status("error", error.message));
|
|
889
|
+
if (error.recovery) console.error(Formatter.status("info", error.recovery));
|
|
890
|
+
process.exit(error.exitCode);
|
|
891
|
+
}
|
|
892
|
+
console.error(
|
|
893
|
+
Formatter.status("error", error instanceof Error ? error.message : "An unexpected error occurred")
|
|
894
|
+
);
|
|
895
|
+
process.exit(1);
|
|
896
|
+
});
|
|
897
|
+
//# sourceMappingURL=index.js.map
|