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