@gojinko/cli 2.28.0 → 2.29.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.
Files changed (46) hide show
  1. package/README.md +34 -8
  2. package/SKILL.md +21 -1
  3. package/dist/commands/cars/car-cancel.d.ts +46 -0
  4. package/dist/commands/cars/car-cancel.d.ts.map +1 -0
  5. package/dist/commands/cars/car-cancel.js +136 -0
  6. package/dist/commands/cars/car-cancel.js.map +1 -0
  7. package/dist/commands/cars/car-search.d.ts +42 -0
  8. package/dist/commands/cars/car-search.d.ts.map +1 -0
  9. package/dist/commands/cars/car-search.js +184 -0
  10. package/dist/commands/cars/car-search.js.map +1 -0
  11. package/dist/commands/cars/car-shared.d.ts +54 -0
  12. package/dist/commands/cars/car-shared.d.ts.map +1 -0
  13. package/dist/commands/cars/car-shared.js +89 -0
  14. package/dist/commands/cars/car-shared.js.map +1 -0
  15. package/dist/commands/exchange.d.ts.map +1 -1
  16. package/dist/commands/exchange.js +16 -13
  17. package/dist/commands/exchange.js.map +1 -1
  18. package/dist/commands/get-booking.d.ts +4 -2
  19. package/dist/commands/get-booking.d.ts.map +1 -1
  20. package/dist/commands/get-booking.js +27 -8
  21. package/dist/commands/get-booking.js.map +1 -1
  22. package/dist/commands/hotel-cancel.d.ts +0 -15
  23. package/dist/commands/hotel-cancel.d.ts.map +1 -1
  24. package/dist/commands/hotel-cancel.js +17 -45
  25. package/dist/commands/hotel-cancel.js.map +1 -1
  26. package/dist/commands/refund.d.ts +7 -9
  27. package/dist/commands/refund.d.ts.map +1 -1
  28. package/dist/commands/refund.js +26 -17
  29. package/dist/commands/refund.js.map +1 -1
  30. package/dist/commands/schema.d.ts.map +1 -1
  31. package/dist/commands/schema.js +61 -5
  32. package/dist/commands/schema.js.map +1 -1
  33. package/dist/commands/trip-status.d.ts.map +1 -1
  34. package/dist/commands/trip-status.js +4 -2
  35. package/dist/commands/trip-status.js.map +1 -1
  36. package/dist/commands/trip.d.ts.map +1 -1
  37. package/dist/commands/trip.js +3 -3
  38. package/dist/commands/trip.js.map +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +6 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/output/servicing.d.ts +2 -0
  43. package/dist/output/servicing.d.ts.map +1 -0
  44. package/dist/output/servicing.js +81 -0
  45. package/dist/output/servicing.js.map +1 -0
  46. package/package.json +2 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @gojinko/cli
2
2
 
3
- Command-line tool for the Jinko developer platform. Search flights and hotels, build trips (multi-domain — flights and hotels share one cart), and hand users off to a Jinko-hosted checkout page.
3
+ Command-line tool for the Jinko developer platform. Search flights, hotels and rental cars, build trips (multi-domain — flights, hotels and cars share one cart), and hand users off to a Jinko-hosted checkout page.
4
4
 
5
5
  ## Install
6
6
 
@@ -68,7 +68,8 @@ Prod and sandbox keys are stored separately in `~/.jinko/config.yaml`. Resolutio
68
68
  | `hotel-search` | Live hotel search by city / query / geo / hotel-ids. Returns hotels with rooms, rates, `htl_*` offer tokens. |
69
69
  | `hotel-details` | Lazy lookup for a specific hotel — images, amenities, location detail. Pairs with `hotel-search` when the user wants the full picture before booking. |
70
70
  | `ground-search` | Live rail / coach / ferry search. Each result's `id` is itself the `trip_item_token` — no prefix. |
71
- | `trip` | Create/update a trip, add flight or hotel items, set travelers |
71
+ | `car-search` | Live car-rental search by pick-up airport, free-text place or coordinates, a branch-local date-time window (no timezone) and the driver's age and residence. Each offer's `offer_id` (`car_*`) is the `trip_item_token`. An empty `cancellation_fees` means the fee is unknown, never free. |
72
+ | `trip` | Create/update a trip, add flight, hotel, ground or car items, set travelers + contact (a car rental needs `contact.title`, the honorific) |
72
73
 
73
74
  ### Legacy
74
75
 
@@ -83,7 +84,7 @@ Prod and sandbox keys are stored separately in `~/.jinko/config.yaml`. Resolutio
83
84
  | `agent-pay submit` | Pay programmatically with a Shared Payment Token: present `--trip-id` + `--token`; the BFF schedules the agent fulfillment and redeems the SPT. Returns the Jinko `booking_ref` (`JNK-…`) once authorized. Falls back to `checkout_url` on a 3DS step-up. |
84
85
  | `get-ancillaries` | List a trip's purchasable ancillaries (bags / seats / meals) without generating a checkout URL |
85
86
  | `select-ancillaries` | Optional: preselect bags / seats / meals before handoff |
86
- | `trip-status` | Full lifecycle state: cart, quote, fulfillment, bookings — plus the Jinko `booking_ref` once fulfillment has been scheduled (it can be present before payment completes — read `fulfillment.status` for paid), the value `get-booking` / `hotel-cancel` / `refund` / `exchange` take as `--booking-ref` |
87
+ | `trip-status` | Full lifecycle state: cart, quote, fulfillment, bookings — plus the Jinko `booking_ref` once fulfillment has been scheduled (it can be present before payment completes — read `fulfillment.status` for paid), the value `get-booking` / `hotel-cancel` / `car-cancel` / `refund` / `exchange` take as `--booking-ref` |
87
88
 
88
89
  ### Post-booking
89
90
 
@@ -97,7 +98,10 @@ Prod and sandbox keys are stored separately in `~/.jinko/config.yaml`. Resolutio
97
98
  | `exchange price` | Price a selected exchange option |
98
99
  | `exchange commit` | Commit a flight exchange |
99
100
  | `exchange status` | Check the status of a previously committed exchange |
100
- | `hotel-cancel` | Cancel a hotel booking (idempotent — safe to retry). Guest auth via `--ref` + `--last-name`, or authenticated via `--provider-booking-id`. |
101
+ | `hotel-cancel` | Cancel a hotel booking (idempotent — safe to retry). Use `--booking-ref` and the hotel `--item-id` from `get-booking`; guests also supply `--last-name`. |
102
+ | `car-cancel preview` | Quote the cancellation fee of a car rental and mint the `cancellation_id` a commit is bound to. `fee_known: false` is an unknown fee, not a free one; `manual_required: true` means a Jinko agent has to complete it. |
103
+ | `car-cancel commit` | Cancel the rental against a `--cancellation-id` from `preview` (safe to repeat). `--manual-ok` hands a `manual_required` preview to a Jinko agent — only once the customer has agreed. |
104
+ | `car-cancel status` | Read the latest cancellation attempt. There is no `car-exchange`: modifying a rental is not offered — cancel and book again. |
101
105
 
102
106
  ### Utility
103
107
 
@@ -167,13 +171,29 @@ jinko select-ancillaries \
167
171
  jinko trip-status --trip-id <trip_id>
168
172
 
169
173
  # Refunds
170
- jinko refund check --booking-ref <ref> --pnr <pnr>
171
- jinko refund commit --booking-ref <ref> --pnr <pnr> --reason "schedule change"
174
+ jinko get-booking --booking-ref <ref> --last-name <name>
175
+ jinko refund check --booking-ref <ref> --item-id <item_id> --last-name <name>
176
+ jinko refund commit --booking-ref <ref> --item-id <item_id> --last-name <name>
172
177
 
173
178
  # Hotel cancellations (idempotent — safe to retry)
174
- jinko hotel-cancel --booking-ref <booking_ref> --last-name <name>
179
+ jinko hotel-cancel --booking-ref <booking_ref> --item-id <item_id> --last-name <name>
175
180
  # Or via the authenticated shortcut:
176
- jinko hotel-cancel --provider-booking-id <id> --provider nuitee
181
+ jinko hotel-cancel --booking-ref <booking_ref> --item-id <item_id>
182
+
183
+ # Car rental — times are BRANCH-LOCAL with no timezone; driver age and residence change the price.
184
+ jinko car-search --pick-up-airport LYS --pick-up-date-time 2026-10-25T10:00:00 \
185
+ --drop-off-date-time 2026-10-28T10:00:00 --driver-age 30 --residence-country FR --currency EUR
186
+ # A free-text place that is ambiguous comes back as candidates, not offers — retry with one of them:
187
+ jinko car-search --pick-up-place "Lyon Part-Dieu" …
188
+ # Each offer_id (car_*) is the trip item token; a car booking needs the contact's honorific:
189
+ jinko trip --trip-item-token <car_offer_id> \
190
+ --travelers '[{"first_name":"Jane","last_name":"Doe","passenger_type":"ADULT"}]' \
191
+ --contact '{"email":"jane@example.com","phone":"+33612345678","title":"ms"}'
192
+
193
+ # Car cancellation — preview quotes the fee and mints the cancellation_id the commit is bound to
194
+ jinko car-cancel preview --booking-ref <booking_ref> --item-id <item_id> --last-name <name>
195
+ jinko car-cancel commit --booking-ref <booking_ref> --item-id <item_id> --last-name <name> --cancellation-id <ccl_…>
196
+ jinko car-cancel status --booking-ref <booking_ref> --item-id <item_id> --last-name <name>
177
197
  ```
178
198
 
179
199
  ## Flight search filters
@@ -245,3 +265,9 @@ JSON output stays parseable) and will be removed in the next major release.
245
265
  | `hotel-search` | `--guest-nationality` | `--nationality` |
246
266
  | `hotel-search` | `--star-rating` / `--max-star-rating` | `--stars` / `--max-stars` |
247
267
  | `get-booking`, `hotel-cancel` | `--booking-ref` | `--ref` |
268
+
269
+ ### Servicing a booked item
270
+
271
+ Run `get-booking` before every refund, exchange, hotel cancellation or car cancellation. Its `items[]` expose a stable numeric `item_id` and each item's `servicing` eligibility (`can_refund`, `can_void`, `can_exchange`, support levels and refusal reasons). Select the intended item and pass `--booking-ref` and `--item-id` on every subsequent step, including status polls. The ID remains unchanged after an exchange.
272
+
273
+ Guests must supply `--last-name`; credentials owning the booking may omit it. Omitting `--item-id` works only for a single item of the requested domain. A 409 `item_selection_required` carries safe `items[]` to choose from. CLI commands do not accept provider booking handles. Use the Jinko reference for servicing and the airline locator when speaking to the airline.
package/SKILL.md CHANGED
@@ -31,9 +31,15 @@ export JINKO_API_KEY=jnk_...
31
31
  Carries the full shop filter set (`--max-stops`, `--departure-time-range`, `--refundable-only`,
32
32
  `--checked-bag-included`, `--via-airports`, `--origin-alternate-airports`, …) — run
33
33
  `jinko flight-search --help` for the list.
34
+ - `car-search` — live car-rental offers for a pick-up place (`--pick-up-airport`, `--pick-up-place` or
35
+ `--pick-up-geo lat,lng,range` — exactly one), branch-local date-times with NO timezone, `--driver-age`
36
+ and `--residence-country` (both change the price — ask, never guess). Each offer's `offer_id` (`car_*`)
37
+ is the `trip_item_token`. An ambiguous `--pick-up-place` returns `candidates` instead of offers: put them
38
+ to the user and retry with one, never auto-pick.
34
39
 
35
40
  ### Trip building
36
- - `trip` — create/update a trip (add items, set travelers + contact). Returns `trip_id`.
41
+ - `trip` — create/update a trip (add items, set travelers + contact). Returns `trip_id`. A cart holding a
42
+ car rental needs `contact.title` (the honorific: mr / ms / mx) before checkout.
37
43
 
38
44
  ### Checkout
39
45
  - `checkout` — finalize a trip; returns BOTH `checkout_url` (a human opens it to pay) AND `agent_spt_params` (an agent uses them to pay programmatically), plus items + ancillaries + total. (`book` is a deprecated alias.)
@@ -45,6 +51,10 @@ export JINKO_API_KEY=jnk_...
45
51
 
46
52
  ### Post-booking
47
53
  - `refund check` / `refund commit`
54
+ - `car-cancel preview` / `car-cancel commit` / `car-cancel status` — quote the fee first (`preview` mints the
55
+ `cancellation_id` the commit is bound to), get the user's explicit confirmation, then `commit`. There is NO
56
+ `car-exchange`: changing a rental's dates, vehicle or extras is not offered — the options are keeping the
57
+ booking or cancelling it and running `car-search` again.
48
58
 
49
59
  ### Utility
50
60
  - `auth`, `config`, `schema`
@@ -88,6 +98,12 @@ export JINKO_API_KEY=jnk_...
88
98
  - Passenger types: ADULT | CHILD | INFANT.
89
99
  - Cabin classes: economy | premium_economy | business | first. Cabin is ADVISORY —
90
100
  providers may still return other cabins, so read each fare's own `cabin_class`.
101
+ - Car rental date-times are BRANCH-LOCAL and zone-less (`2026-10-25T10:00:00`) — a `Z` or an
102
+ offset is rejected because the rental desk works in its own local time.
103
+ - Car money: `price.pay_now` is the ONLY amount Jinko charges; `due_at_desk` is collected by the
104
+ desk and `deposit` is a card hold — never sum them. An empty `cancellation_fees` list, or
105
+ `fee_known: false` on a cancellation, means the fee is UNKNOWN — never say "free cancellation".
106
+ Amounts are `{ value, currency, decimal_places }` in minor units; print `display` when present.
91
107
  - `flight-search` reports what it enforced: `applied_filters` lists the requested
92
108
  filters the results honor, `unapplied_filters` the ones they do not, each with a
93
109
  reason. An unapplied filter is not an error — the call still returns 200. Either
@@ -97,3 +113,7 @@ export JINKO_API_KEY=jnk_...
97
113
  The anchor decides ranking, so put the airport that matters most in `--origin`.
98
114
  Against a city anchor the list is ignored and reported back under `origin` /
99
115
  `destination` in `unapplied_filters`.
116
+
117
+ ## Servicing a selected booked item
118
+
119
+ Run `get-booking --booking-ref <ref>` first (guests add `--last-name`). Select the intended `items[].item_id` and inspect its `servicing` eligibility. Pass `--booking-ref` and the same `--item-id` to refund, exchange, hotel-cancel and car-cancel actions, including status polls. The ID remains stable after an exchange. Owning credentials may omit the surname; guests supply it. When a 409 `item_selection_required` lists several items, choose one from `items[]` before retrying.
@@ -0,0 +1,46 @@
1
+ import type { Command } from 'commander';
2
+ import type { CarAmount } from '@gojinko/api-client';
3
+ /**
4
+ * The fields the table view reads: the union of what the three car_cancel_*
5
+ * responses carry. Only the preview answers `cancellable`, `manual_required`
6
+ * and `expires_at`; commit and status never do, so every field is optional.
7
+ */
8
+ export interface CarCancelView {
9
+ cancellation_id?: string;
10
+ state?: string;
11
+ cancellable?: boolean;
12
+ fee_known?: boolean;
13
+ fee?: CarAmount;
14
+ paid?: CarAmount;
15
+ refund_amount?: CarAmount;
16
+ non_refundable?: boolean;
17
+ manual_required?: boolean;
18
+ refund_pending_review?: boolean;
19
+ refund_review_reason?: string;
20
+ rejected_reason?: string;
21
+ expires_at?: string;
22
+ completed_at?: string;
23
+ }
24
+ /**
25
+ * Render a cancel preview/commit/status for the table view.
26
+ *
27
+ * Three distinctions a customer is harmed by if they are flattened:
28
+ * - `fee_known: false` means the fee could NOT be established — unknown, not
29
+ * free. It is rendered as an explicit sentence rather than an absent line,
30
+ * so a zero fee and an unestablished fee never look the same.
31
+ * - `manual_required: true` is not a dead end: the cancellation can be handed
32
+ * to a Jinko agent with `--manual-ok`, but only once the customer agrees, so
33
+ * the line says what the flag does rather than just naming it.
34
+ * - `refund_pending_review: true` means the booking IS cancelled (or recorded
35
+ * for an agent) but the refund figure is not final yet.
36
+ *
37
+ * `refund_review_reason` is printed whenever it is present: on a
38
+ * `cancellable: false` preview it is the supplier's own reason (seen live on
39
+ * dev: "the supplier reports this booking as Cancelled"), which would otherwise
40
+ * vanish from the table. `rejected_reason` is only labelled "rejected" when the
41
+ * state is — the API also uses it for the review note on a confirmed
42
+ * cancellation whose fee could not be established.
43
+ */
44
+ export declare function formatCarCancel(data: CarCancelView): string[];
45
+ export declare function registerCarCancelCommand(program: Command): void;
46
+ //# sourceMappingURL=car-cancel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"car-cancel.d.ts","sourceRoot":"","sources":["../../../src/commands/cars/car-cancel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAMrD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,EAAE,CAsC7D;AAqBD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyE/D"}
@@ -0,0 +1,136 @@
1
+ import { withClient, output, userIntentOption } from '../shared.js';
2
+ import { buildAuthFields, itemIdOption } from '../refund.js';
3
+ import { publicServicingResponse } from '../../output/servicing.js';
4
+ import { formatCarAmount } from './car-shared.js';
5
+ /**
6
+ * Render a cancel preview/commit/status for the table view.
7
+ *
8
+ * Three distinctions a customer is harmed by if they are flattened:
9
+ * - `fee_known: false` means the fee could NOT be established — unknown, not
10
+ * free. It is rendered as an explicit sentence rather than an absent line,
11
+ * so a zero fee and an unestablished fee never look the same.
12
+ * - `manual_required: true` is not a dead end: the cancellation can be handed
13
+ * to a Jinko agent with `--manual-ok`, but only once the customer agrees, so
14
+ * the line says what the flag does rather than just naming it.
15
+ * - `refund_pending_review: true` means the booking IS cancelled (or recorded
16
+ * for an agent) but the refund figure is not final yet.
17
+ *
18
+ * `refund_review_reason` is printed whenever it is present: on a
19
+ * `cancellable: false` preview it is the supplier's own reason (seen live on
20
+ * dev: "the supplier reports this booking as Cancelled"), which would otherwise
21
+ * vanish from the table. `rejected_reason` is only labelled "rejected" when the
22
+ * state is — the API also uses it for the review note on a confirmed
23
+ * cancellation whose fee could not be established.
24
+ */
25
+ export function formatCarCancel(data) {
26
+ const lines = [];
27
+ if (data.state)
28
+ lines.push(`state: ${data.state}`);
29
+ if (data.cancellable !== undefined)
30
+ lines.push(`cancellable: ${data.cancellable}`);
31
+ if (data.non_refundable === true)
32
+ lines.push('non-refundable');
33
+ if (data.manual_required === true) {
34
+ lines.push(`cannot be completed online: ${data.refund_review_reason ?? 'a person has to complete this cancellation'}.`, 'No refund figure is given and none may be quoted. To hand it to a Jinko agent, run `car-cancel commit` with --manual-ok once the customer has agreed.');
35
+ }
36
+ if (data.fee_known === false) {
37
+ lines.push('cancellation fee: UNKNOWN — not established yet; we will confirm it. This is NOT a zero fee.');
38
+ }
39
+ else {
40
+ const fee = formatCarAmount(data.fee);
41
+ if (fee)
42
+ lines.push(`cancellation fee: ${fee}`);
43
+ }
44
+ const paid = formatCarAmount(data.paid);
45
+ if (paid)
46
+ lines.push(`paid: ${paid}`);
47
+ const refund = formatCarAmount(data.refund_amount);
48
+ if (refund)
49
+ lines.push(`refund amount: ${refund}`);
50
+ if (data.refund_pending_review === true) {
51
+ lines.push('refund pending manual review — do not quote the refund amount as final');
52
+ }
53
+ if (data.manual_required !== true && data.refund_review_reason) {
54
+ lines.push(`${data.cancellable === false ? 'not cancellable' : 'reason'}: ${data.refund_review_reason}`);
55
+ }
56
+ if (data.rejected_reason) {
57
+ lines.push(`${data.state === 'rejected' ? 'rejected' : 'reason'}: ${data.rejected_reason}`);
58
+ }
59
+ if (data.completed_at)
60
+ lines.push(`completed_at: ${data.completed_at}`);
61
+ if (data.expires_at)
62
+ lines.push(`commit before: ${data.expires_at} (after that, preview again)`);
63
+ // Printed last and in full — it is what binds `car-cancel commit` to this quote.
64
+ if (data.cancellation_id)
65
+ lines.push(`cancellation_id: ${data.cancellation_id}`);
66
+ return lines;
67
+ }
68
+ /**
69
+ * Print a car_cancel_* response. Both formats go through the servicing
70
+ * projection first, like every other servicing command, so an older API
71
+ * deployment that still returns supplier handles never prints them.
72
+ */
73
+ function render(response, globals) {
74
+ const publicResponse = publicServicingResponse(response);
75
+ if (globals.format === 'table') {
76
+ console.log('');
77
+ for (const line of formatCarCancel(publicResponse))
78
+ console.log(line);
79
+ console.log('');
80
+ return;
81
+ }
82
+ output(publicResponse, { format: globals.format });
83
+ }
84
+ const LAST_NAME_HELP = 'Driver surname, required for guest lookup; owning credentials may omit it. JSON: `last_name`.';
85
+ export function registerCarCancelCommand(program) {
86
+ // There is deliberately no `car-exchange` beside this (JIN-2312): Auto Europe
87
+ // support is search, book and cancel, the API withdrew the exchange trio from
88
+ // the contract (0.16.0) and its routes answer 404. A different rental means
89
+ // cancelling this one and booking again with `car-search`.
90
+ const car = program
91
+ .command('car-cancel')
92
+ .description('Call get-booking first, choose the car item_id, then quote the fee for, commit, or poll a car-rental cancellation using that booking reference and stable item ID. Modifying a rental (dates, vehicle, extras) is not offered: the options are keeping the booking as is, or cancelling it (fee shown first) and booking a new rental with car-search.');
93
+ car
94
+ .command('preview')
95
+ .summary('Quote the cancellation fee for a car rental')
96
+ .description('Quote the cancellation fee for a booked car rental and mint the `cancellation_id` that binds a later commit to this quote. `fee_known: false` means the fee could NOT be established — unknown, not free. `manual_required: true` means it cannot be completed online; commit with --manual-ok, once the customer agrees, to hand it to a Jinko agent.')
97
+ .requiredOption('--booking-ref <ref>', 'Jinko booking reference from get-booking. JSON: `booking_ref`. [required]')
98
+ .option('--last-name <name>', LAST_NAME_HELP)
99
+ .addOption(itemIdOption())
100
+ .addOption(userIntentOption())
101
+ .action(withClient(async (client, globals, opts) => {
102
+ render(await client.carCancelPreview(buildAuthFields(opts)), globals);
103
+ }));
104
+ car
105
+ .command('commit')
106
+ .summary('Cancel a booked car rental')
107
+ .description('Cancel a booked car rental, bound to the fee quoted by a preceding `car-cancel preview`. Pass that preview\'s `cancellation_id`. A 409 refuses the commit without cancelling anything: `quote_expired` (preview again), `quote_drift` (a fresh `requote` reference and the figure now — show it, then commit against it), `active_operation_exists` (wait for it; poll `car-cancel status`), `not_cancellable`, `funds_unavailable`, or `manual_required` (resend with --manual-ok once the customer agrees).')
108
+ .requiredOption('--booking-ref <ref>', 'Jinko booking reference from get-booking. JSON: `booking_ref`. [required]')
109
+ .option('--last-name <name>', LAST_NAME_HELP)
110
+ .addOption(itemIdOption())
111
+ .requiredOption('--cancellation-id <id>', 'the "ccl_..." reference from `car-cancel preview` — binds this commit to the fee the customer saw. JSON: `cancellation_id`. [required]')
112
+ .option('--reason <text>', 'free-text cancellation reason. JSON: `reason`.')
113
+ .option('--manual-ok', 'commit a preview that answered `manual_required: true`: the cancellation is recorded for a Jinko agent — nothing is sent to the rental company by this call — and the answer is `state: pending` with `refund_pending_review: true`. Without it such a commit is refused 409 `manual_required`. Send it only once the customer has agreed; on a preview that can be committed online it changes nothing. JSON: `manual_ok`.')
114
+ .addOption(userIntentOption())
115
+ .action(withClient(async (client, globals, opts) => {
116
+ const response = await client.carCancelCommit({
117
+ ...buildAuthFields(opts),
118
+ cancellation_id: opts.cancellationId,
119
+ ...(opts.reason && { reason: opts.reason }),
120
+ ...(opts.manualOk === true && { manual_ok: true }),
121
+ });
122
+ render(response, globals);
123
+ }));
124
+ car
125
+ .command('status')
126
+ .summary('Check the status of a car-rental cancellation')
127
+ .description('Poll the current status of a previously committed car-rental cancellation. Read-only. `pending` with `refund_pending_review: true` means a Jinko agent has it — do not commit again.')
128
+ .requiredOption('--booking-ref <ref>', 'Jinko booking reference from get-booking. JSON: `booking_ref`. [required]')
129
+ .option('--last-name <name>', LAST_NAME_HELP)
130
+ .addOption(itemIdOption())
131
+ .addOption(userIntentOption())
132
+ .action(withClient(async (client, globals, opts) => {
133
+ render(await client.carCancelStatus(buildAuthFields(opts)), globals);
134
+ }));
135
+ }
136
+ //# sourceMappingURL=car-cancel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"car-cancel.js","sourceRoot":"","sources":["../../../src/commands/cars/car-cancel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { Command } from 'commander';
2
+ import type { CarSearchRequest, CarOffer, CarResolvedPlace } from '@gojinko/api-client';
3
+ /** Resolved, validated inputs for a car_search request. */
4
+ export interface CarSearchInputs {
5
+ pickUpDateTime: string;
6
+ pickUpAirport?: string;
7
+ pickUpPlace?: string;
8
+ pickUpGeo?: string;
9
+ dropOffDateTime?: string;
10
+ dropOffAirport?: string;
11
+ dropOffPlace?: string;
12
+ dropOffGeo?: string;
13
+ driverAge: string;
14
+ residenceCountry: string;
15
+ currency?: string;
16
+ lang?: string;
17
+ }
18
+ /**
19
+ * Build the canonical car_search body (jinko-api POST /v1/car_search).
20
+ *
21
+ * Two rules the contract enforces at runtime but cannot express in JSON Schema,
22
+ * so they are checked here instead of surfacing as an opaque 400:
23
+ * 1. `pick_up` names exactly ONE of airport_code / place / geo.
24
+ * 2. a drop-off time must come from `drop_off.date_time` or the top-level
25
+ * `drop_off_date_time`.
26
+ *
27
+ * A drop-off LOCATION is optional — omitting it means returning the car to the
28
+ * pick-up branch, which is the common case and a different thing from omitting
29
+ * the drop-off TIME.
30
+ *
31
+ * Pure — exported for tests.
32
+ */
33
+ export declare function buildCarSearchBody(input: CarSearchInputs): CarSearchRequest;
34
+ /**
35
+ * Table view.
36
+ *
37
+ * `offer_id` is printed in full on its own line and never truncated: it is the
38
+ * trip item token, so a shortened one is silently unusable.
39
+ */
40
+ export declare function formatCarTable(offers: CarOffer[], candidates?: CarResolvedPlace[]): string[];
41
+ export declare function registerCarSearchCommand(program: Command): void;
42
+ //# sourceMappingURL=car-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"car-search.d.ts","sourceRoot":"","sources":["../../../src/commands/cars/car-search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAIxF,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAgD3E;AAiCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,QAAQ,EAAE,EAClB,UAAU,GAAE,gBAAgB,EAAO,GAClC,MAAM,EAAE,CAoDV;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsE/D"}
@@ -0,0 +1,184 @@
1
+ import { withClient, output, userIntentOption, exitWithError, UsageError } from '../shared.js';
2
+ import { formatCarAmount, requireLocalDateTime, resolveLocation } from './car-shared.js';
3
+ /**
4
+ * Build the canonical car_search body (jinko-api POST /v1/car_search).
5
+ *
6
+ * Two rules the contract enforces at runtime but cannot express in JSON Schema,
7
+ * so they are checked here instead of surfacing as an opaque 400:
8
+ * 1. `pick_up` names exactly ONE of airport_code / place / geo.
9
+ * 2. a drop-off time must come from `drop_off.date_time` or the top-level
10
+ * `drop_off_date_time`.
11
+ *
12
+ * A drop-off LOCATION is optional — omitting it means returning the car to the
13
+ * pick-up branch, which is the common case and a different thing from omitting
14
+ * the drop-off TIME.
15
+ *
16
+ * Pure — exported for tests.
17
+ */
18
+ export function buildCarSearchBody(input) {
19
+ requireLocalDateTime(input.pickUpDateTime, '--pick-up-date-time');
20
+ requireLocalDateTime(input.dropOffDateTime, '--drop-off-date-time');
21
+ const pickUpLocation = resolveLocation('pick-up', { airport: input.pickUpAirport, place: input.pickUpPlace, geo: input.pickUpGeo }, { required: true });
22
+ const dropOffLocation = resolveLocation('drop-off', { airport: input.dropOffAirport, place: input.dropOffPlace, geo: input.dropOffGeo }, { required: false });
23
+ const hasDropOffLocation = Object.keys(dropOffLocation).length > 0;
24
+ if (!input.dropOffDateTime && !hasDropOffLocation) {
25
+ throw new UsageError('--drop-off-date-time is required (or give a --drop-off-* location carrying its own time).');
26
+ }
27
+ const driverAge = Number(input.driverAge);
28
+ if (!/^\d+$/.test(input.driverAge.trim()) || driverAge < 18 || driverAge > 99) {
29
+ throw new UsageError(`--driver-age must be a whole number between 18 and 99 (got "${input.driverAge}").`);
30
+ }
31
+ if (!/^[A-Za-z]{2}$/.test(input.residenceCountry)) {
32
+ throw new UsageError(`--residence-country must be a 2-letter ISO 3166-1 alpha-2 code, e.g. FR (got "${input.residenceCountry}").`);
33
+ }
34
+ const body = {
35
+ pick_up: { date_time: input.pickUpDateTime, ...pickUpLocation },
36
+ driver_age: driverAge,
37
+ residence_country: input.residenceCountry.toUpperCase(),
38
+ };
39
+ if (hasDropOffLocation) {
40
+ body.drop_off = { ...dropOffLocation, ...(input.dropOffDateTime && { date_time: input.dropOffDateTime }) };
41
+ }
42
+ else if (input.dropOffDateTime) {
43
+ body.drop_off_date_time = input.dropOffDateTime;
44
+ }
45
+ if (input.currency)
46
+ body.currency = input.currency;
47
+ if (input.lang)
48
+ body.lang = input.lang;
49
+ return body;
50
+ }
51
+ /** The vehicle line: "Compact · VW Golf or similar [CDMR] · 5 seats · manual". */
52
+ function vehicleLine(offer) {
53
+ const v = offer.vehicle;
54
+ return [
55
+ v?.category,
56
+ v?.name,
57
+ v?.acriss_code && `[${v.acriss_code}]`,
58
+ v?.seats && `${v.seats} seats`,
59
+ v?.transmission,
60
+ v?.air_conditioned === true && 'A/C',
61
+ ]
62
+ .filter(Boolean)
63
+ .join(' · ');
64
+ }
65
+ /**
66
+ * Cancellation summary for one offer.
67
+ *
68
+ * An EMPTY `cancellation_fees[]` means the fee could not be established —
69
+ * UNKNOWN, not free. Printing nothing (or "free cancellation") there would tell
70
+ * a customer they can walk away at no cost when they may not be able to.
71
+ */
72
+ function cancellationLine(offer) {
73
+ const fees = offer.cancellation_fees ?? [];
74
+ if (!fees.length)
75
+ return 'cancellation fee: unknown — confirm before booking';
76
+ const applicable = fees.find((f) => f.applicable_now) ?? fees[0];
77
+ if (applicable?.non_refundable)
78
+ return 'non-refundable';
79
+ const amount = formatCarAmount(applicable?.fee);
80
+ return amount ? `cancellation fee: ${amount}` : 'cancellation fee: unknown — confirm before booking';
81
+ }
82
+ /**
83
+ * Table view.
84
+ *
85
+ * `offer_id` is printed in full on its own line and never truncated: it is the
86
+ * trip item token, so a shortened one is silently unusable.
87
+ */
88
+ export function formatCarTable(offers, candidates = []) {
89
+ // Candidates instead of offers is the ambiguous-place RETRY signal, not an
90
+ // empty result — say so, and name the values to retry with.
91
+ if (!offers.length && candidates.length) {
92
+ return [
93
+ 'That location was ambiguous — no offers returned. Retry with one of:',
94
+ ...candidates.map((c) => ` --pick-up-place "${c.name ?? '?'}"${[c.kind, c.city, c.country].filter(Boolean).length ? ` (${[c.kind, c.city, c.country].filter(Boolean).join(', ')})` : ''}`),
95
+ ];
96
+ }
97
+ if (!offers.length) {
98
+ return [
99
+ 'No car offers found.',
100
+ 'Check the pick-up/drop-off times are branch-local (no Z suffix) and that the driver age is eligible for this branch.',
101
+ ];
102
+ }
103
+ const lines = [];
104
+ for (const offer of offers) {
105
+ const price = formatCarAmount(offer.price?.pay_now);
106
+ const dueAtDesk = formatCarAmount(offer.price?.due_at_desk);
107
+ lines.push([
108
+ price ? `${price} now` : '',
109
+ dueAtDesk && `+ ${dueAtDesk} at desk`,
110
+ offer.on_request === true && '[ON REQUEST]',
111
+ ]
112
+ .filter(Boolean)
113
+ .join(' '));
114
+ lines.push(` ${vehicleLine(offer)}`);
115
+ const pkg = offer.package;
116
+ const pkgLine = [
117
+ pkg?.supplier_name,
118
+ pkg?.name,
119
+ pkg?.mileage_unlimited === true ? 'unlimited mileage' : pkg?.mileage_allowance,
120
+ pkg?.fuel_policy,
121
+ ]
122
+ .filter(Boolean)
123
+ .join(' · ');
124
+ if (pkgLine)
125
+ lines.push(` ${pkgLine}`);
126
+ const branch = offer.pick_up;
127
+ if (branch?.name || branch?.address) {
128
+ lines.push(` pick-up: ${[branch.name, branch.address, branch.city].filter(Boolean).join(', ')}`);
129
+ }
130
+ lines.push(` ${cancellationLine(offer)}`);
131
+ if (offer.expires_at)
132
+ lines.push(` expires_at: ${offer.expires_at}`);
133
+ lines.push(` trip_item_token: ${offer.offer_id ?? '(missing)'}`);
134
+ lines.push('');
135
+ }
136
+ return lines;
137
+ }
138
+ export function registerCarSearchCommand(program) {
139
+ program
140
+ .command('car-search')
141
+ .summary('Search live car-rental offers')
142
+ .description('Search live car-rental inventory for a pick-up location, date-time window and driver age. Pick-up and drop-off times are BRANCH-LOCAL with no timezone (2026-09-12T10:00:00) — the rental desk\'s own wall clock. Give exactly one of --pick-up-airport, --pick-up-place or --pick-up-geo; a free-text --pick-up-place that resolves ambiguously comes back as a candidate list instead of offers, which is a retry signal, not an error. Each offer\'s `offer_id` is a trip item token you can hand straight to the trip command to start a booking.')
143
+ .option('--pick-up-date-time <datetime>', 'pick-up date-time, branch-local, no timezone: 2026-09-12T10:00:00. JSON: `pick_up.date_time`. [required]')
144
+ .option('--pick-up-airport <code>', 'pick-up IATA airport code, e.g. LYS. JSON: `pick_up.airport_code`.')
145
+ .option('--pick-up-place <text>', 'free-text pick-up location — a city, district or landmark, e.g. "Lyon city centre". Resolved server-side. JSON: `pick_up.place`.')
146
+ .option('--pick-up-geo <lat,lng,range>', 'pick-up coordinates and search radius in metres (min 50), e.g. 45.72,4.94,5000. JSON: `pick_up.geo`.')
147
+ .option('--drop-off-date-time <datetime>', 'drop-off date-time, branch-local, no timezone. JSON: `drop_off_date_time`. [required unless a --drop-off-* location carries its own time]')
148
+ .option('--drop-off-airport <code>', 'drop-off IATA airport code, for a one-way rental. JSON: `drop_off.airport_code`.')
149
+ .option('--drop-off-place <text>', 'free-text drop-off location, for a one-way rental. JSON: `drop_off.place`.')
150
+ .option('--drop-off-geo <lat,lng,range>', 'drop-off coordinates and radius, for a one-way rental. JSON: `drop_off.geo`.')
151
+ .option('--driver-age <age>', "driver's age at pick-up, 18-99. Pricing and availability are age-dependent. JSON: `driver_age`. [required]")
152
+ .option('--residence-country <code>', "driver's country of residence, ISO 3166-1 alpha-2, e.g. FR. Rates and inclusions vary by residence. JSON: `residence_country`. [required]")
153
+ .option('--currency <code>', 'ISO 4217 display currency, e.g. EUR. Defaults to USD. JSON: `currency`.')
154
+ .option('--lang <tag>', 'BCP-47 language tag for vehicle and branch text, e.g. en-gb. JSON: `lang`.')
155
+ .addOption(userIntentOption())
156
+ .action(withClient(async (client, globals, opts) => {
157
+ let body;
158
+ try {
159
+ if (!opts.pickUpDateTime) {
160
+ throw new UsageError('--pick-up-date-time is required.');
161
+ }
162
+ if (!opts.driverAge)
163
+ throw new UsageError('--driver-age is required.');
164
+ if (!opts.residenceCountry)
165
+ throw new UsageError('--residence-country is required.');
166
+ body = buildCarSearchBody(opts);
167
+ }
168
+ catch (error) {
169
+ exitWithError(error, globals);
170
+ }
171
+ const data = await client.carSearch(body);
172
+ if (globals.format === 'table') {
173
+ const where = opts.pickUpAirport ?? opts.pickUpPlace ?? opts.pickUpGeo ?? '(pick-up)';
174
+ const resolved = data.resolved_place?.name;
175
+ console.log(`\nCar offers: ${where}${resolved && resolved !== where ? ` → ${resolved}` : ''} (${opts.pickUpDateTime}${opts.dropOffDateTime ? ` → ${opts.dropOffDateTime}` : ''})\n`);
176
+ for (const line of formatCarTable(data.offers ?? [], data.candidates ?? [])) {
177
+ console.log(line);
178
+ }
179
+ return;
180
+ }
181
+ output(data, { format: globals.format });
182
+ }));
183
+ }
184
+ //# sourceMappingURL=car-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"car-search.js","sourceRoot":"","sources":["../../../src/commands/cars/car-search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { type CarAmount, type CarGeo } from '@gojinko/api-client';
2
+ /**
3
+ * Branch-local date-time, no timezone: `2026-09-12T10:00:00`.
4
+ *
5
+ * Car rentals are quoted against the branch's own wall clock, so the contract
6
+ * deliberately carries no offset. Accepting a `Z`-suffixed or offset-bearing
7
+ * value here would send the provider a time it silently reinterprets as local,
8
+ * shifting a pick-up by hours without any error.
9
+ */
10
+ export declare const LOCAL_DATETIME_RE: RegExp;
11
+ /** Validate a branch-local date-time, throwing a UsageError naming the flag. */
12
+ export declare function requireLocalDateTime(value: string | undefined, flag: string): void;
13
+ /**
14
+ * Render a CarAmount for the table view, through the api-client money reader
15
+ * (JIN-2220): the server's `display` string when the amount carries one, else
16
+ * "EUR 412.50" built from the reading — ISO code first, the same shape every
17
+ * other money line the CLI prints.
18
+ *
19
+ * A CarAmount is integer minor units under `value` with `decimal_places`. The
20
+ * reader divides by the scale that came with the amount and, when the field is
21
+ * genuinely absent, falls back to the currency's published ISO digits — never
22
+ * an assumed 2, which would print a JPY deposit of {value: 20000} as
23
+ * "JPY 200.00". An explicit 0 is a real price (a zero cancellation fee) and
24
+ * prints as one; an absent or unreadable amount prints nothing rather than a
25
+ * plausible wrong figure.
26
+ */
27
+ export declare function formatCarAmount(amount: CarAmount | undefined): string;
28
+ /**
29
+ * Parse `--*-geo <lat,lng,range>` into a CarGeo.
30
+ *
31
+ * One flag rather than three keeps the three parts inseparable: a geo search is
32
+ * meaningless with a latitude but no range, and Commander cannot express
33
+ * "these three flags are required together".
34
+ */
35
+ export declare function parseGeo(raw: string, flag: string): CarGeo;
36
+ /**
37
+ * Resolve the exactly-one-of location flags for one side of the rental.
38
+ *
39
+ * `airport_code`, `place` and `geo` are mutually exclusive server-side — a rule
40
+ * the JSON Schema cannot express, so it is enforced here rather than surfacing
41
+ * as an opaque 400.
42
+ */
43
+ export declare function resolveLocation(side: 'pick-up' | 'drop-off', opts: {
44
+ airport?: string;
45
+ place?: string;
46
+ geo?: string;
47
+ }, { required }: {
48
+ required: boolean;
49
+ }): {
50
+ airport_code?: string;
51
+ place?: string;
52
+ geo?: CarGeo;
53
+ };
54
+ //# sourceMappingURL=car-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"car-shared.d.ts","sourceRoot":"","sources":["../../../src/commands/cars/car-shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAGhF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,QAA0C,CAAC;AAEzE,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAMlF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAKrE;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAa1D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,GAAG,UAAU,EAC5B,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACxD,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,GAClC;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAsBzD"}