fortrip 1.0.0 → 2.0.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.
@@ -14855,20 +14855,20 @@ var roomNoError = `roomNo must be a whole number from 1 to ${LIMITS.rooms}`;
14855
14855
  var contactEmailError = "contactEmail must be an email address";
14856
14856
  var createBookingInput = strictInput({
14857
14857
  hotelId: positiveId("hotelId").describe("Hotel id."),
14858
- roomId: nonEmpty("roomId").describe("roomId from get_rates"),
14858
+ roomId: nonEmpty("roomId").describe("roomId from get_hotel_rates"),
14859
14859
  ratePlanCode: nonEmpty("ratePlanCode").describe(
14860
- "ratePlanCode from get_rates"
14860
+ "ratePlanCode from get_hotel_rates"
14861
14861
  ),
14862
14862
  checkIn: isoDate("checkIn").describe(stayDescriptions.checkIn),
14863
14863
  checkOut: isoDate("checkOut").describe(stayDescriptions.checkOut),
14864
14864
  rooms: wholeNumber("rooms", 1, LIMITS.rooms).describe(
14865
- "Number of rooms, as in get_rates; every room has the same occupancy"
14865
+ "Number of rooms, as in get_hotel_rates; every room has the same occupancy"
14866
14866
  ),
14867
14867
  adults: wholeNumber("adults (per room)", 1, LIMITS.adultsPerRoom).describe(
14868
- "Adults per room, not in total, as in get_rates (2 rooms for 4 adults = rooms 2, adults 2)"
14868
+ "Adults per room, not in total, as in get_hotel_rates (2 rooms for 4 adults = rooms 2, adults 2)"
14869
14869
  ),
14870
14870
  childrenAges: childrenAges(LIMITS.childrenPerRoom).default([]).describe(
14871
- "Age of each child in one room (0-17), per room, as in get_rates"
14871
+ "Age of each child in one room (0-17), per room, as in get_hotel_rates"
14872
14872
  ),
14873
14873
  guests: external_exports.array(
14874
14874
  strictInput({
@@ -14989,7 +14989,7 @@ var ratesFlags = flagsOf(getRatesInput, [
14989
14989
  type: "id",
14990
14990
  multiple: true,
14991
14991
  placeholder: ID,
14992
- description: `Hotels to price, by id from \`search\`: repeat the flag or comma-separate, up to ${LIMITS.ratesHotels}. Each call takes about 15 seconds.`
14992
+ description: `Hotels to price, by id from \`hotels search\`: repeat the flag or comma-separate, up to ${LIMITS.ratesHotels}. Each call takes about 15 seconds.`
14993
14993
  },
14994
14994
  { flag: "check-in", field: "checkIn", type: "string", placeholder: DATE },
14995
14995
  { flag: "check-out", field: "checkOut", type: "string", placeholder: DATE },
@@ -15049,14 +15049,14 @@ var bookFlags = flagsOf(createBookingInput, [
15049
15049
  field: "roomId",
15050
15050
  type: "string",
15051
15051
  placeholder: ID,
15052
- description: "`roomId` from `rates`"
15052
+ description: "`roomId` from `hotels rates`"
15053
15053
  },
15054
15054
  {
15055
15055
  flag: "rate-plan",
15056
15056
  field: "ratePlanCode",
15057
15057
  type: "string",
15058
15058
  placeholder: "<code>",
15059
- description: "`ratePlanCode` from `rates`"
15059
+ description: "`ratePlanCode` from `hotels rates`"
15060
15060
  },
15061
15061
  { flag: "check-in", field: "checkIn", type: "string", placeholder: DATE },
15062
15062
  { flag: "check-out", field: "checkOut", type: "string", placeholder: DATE },
@@ -15065,14 +15065,14 @@ var bookFlags = flagsOf(createBookingInput, [
15065
15065
  field: "rooms",
15066
15066
  type: "number",
15067
15067
  placeholder: COUNT,
15068
- description: "Number of rooms, as in `rates`; every room has the same occupancy"
15068
+ description: "Number of rooms, as in `hotels rates`; every room has the same occupancy"
15069
15069
  },
15070
15070
  {
15071
15071
  flag: "adults",
15072
15072
  field: "adults",
15073
15073
  type: "number",
15074
15074
  placeholder: COUNT,
15075
- description: "Adults per room, not in total, as in `rates` (2 rooms for 4 adults = `--rooms 2 --adults 2`)"
15075
+ description: "Adults per room, not in total, as in `hotels rates` (2 rooms for 4 adults = `--rooms 2 --adults 2`)"
15076
15076
  },
15077
15077
  {
15078
15078
  flag: "child-age",
@@ -15080,7 +15080,7 @@ var bookFlags = flagsOf(createBookingInput, [
15080
15080
  type: "number",
15081
15081
  multiple: true,
15082
15082
  placeholder: "<age>",
15083
- description: "Age of each child in one room (0-17), per room, as in `rates`"
15083
+ description: "Age of each child in one room (0-17), per room, as in `hotels rates`"
15084
15084
  },
15085
15085
  {
15086
15086
  flag: "guest",
@@ -15121,8 +15121,12 @@ var BODY_OPTION = {
15121
15121
  placeholder: "<json|@file.json>",
15122
15122
  description: "The raw request body as a JSON object, or `@file.json` to read it from a file. Flags override its fields."
15123
15123
  };
15124
+ var COMMAND_GROUPS = [
15125
+ { name: "hotels", summary: "Search, price and book hotels" }
15126
+ ];
15124
15127
  var COMMANDS = [
15125
15128
  {
15129
+ group: "hotels",
15126
15130
  name: "search",
15127
15131
  summary: "Find hotels with indicative prices",
15128
15132
  tool: "search_hotels",
@@ -15130,27 +15134,31 @@ var COMMANDS = [
15130
15134
  options: [BODY_OPTION]
15131
15135
  },
15132
15136
  {
15133
- name: "hotel",
15137
+ group: "hotels",
15138
+ name: "get",
15134
15139
  summary: "Static hotel detail: description, policies, facilities and photos",
15135
15140
  tool: "get_hotel",
15136
15141
  flags: hotelFlags,
15137
15142
  positionals: ["hotelId"]
15138
15143
  },
15139
15144
  {
15145
+ group: "hotels",
15140
15146
  name: "rates",
15141
15147
  summary: `Live rooms and rates for up to ${LIMITS.ratesHotels} hotels`,
15142
- tool: "get_rates",
15148
+ tool: "get_hotel_rates",
15143
15149
  flags: ratesFlags,
15144
15150
  options: [BODY_OPTION]
15145
15151
  },
15146
15152
  {
15153
+ group: "hotels",
15147
15154
  name: "book",
15148
15155
  summary: "Create an order and get a payment link",
15149
- tool: "create_booking",
15156
+ tool: "book_hotel",
15150
15157
  flags: bookFlags,
15151
15158
  options: [BODY_OPTION]
15152
15159
  },
15153
15160
  {
15161
+ group: null,
15154
15162
  name: "login",
15155
15163
  summary: "Sign in through the browser (device code)",
15156
15164
  tool: null,
@@ -15164,18 +15172,21 @@ var COMMANDS = [
15164
15172
  ]
15165
15173
  },
15166
15174
  {
15175
+ group: null,
15167
15176
  name: "logout",
15168
15177
  summary: "Revoke and forget the saved token",
15169
15178
  tool: null,
15170
15179
  flags: []
15171
15180
  },
15172
15181
  {
15182
+ group: null,
15173
15183
  name: "whoami",
15174
15184
  summary: "Show the signed-in account",
15175
15185
  tool: null,
15176
15186
  flags: []
15177
15187
  },
15178
15188
  {
15189
+ group: null,
15179
15190
  name: "guide",
15180
15191
  summary: "Print the guide for AI agents (Markdown)",
15181
15192
  tool: "get_planning_guide",
@@ -15183,18 +15194,22 @@ var COMMANDS = [
15183
15194
  positionals: ["topic"]
15184
15195
  },
15185
15196
  {
15197
+ group: null,
15186
15198
  name: "mcp",
15187
15199
  summary: "Run an MCP server over stdio for local clients, bridged to Fortrip's hosted server",
15188
15200
  tool: null,
15189
15201
  flags: []
15190
15202
  }
15191
15203
  ];
15204
+ function commandInvocation(spec) {
15205
+ return spec.group === null ? `fortrip ${spec.name}` : `fortrip ${spec.group} ${spec.name}`;
15206
+ }
15192
15207
  var GLOBAL_FLAGS = [
15193
15208
  {
15194
15209
  flag: "help",
15195
15210
  short: "h",
15196
15211
  type: "boolean",
15197
- description: "Show help for the command, or for the CLI when no command is given."
15212
+ description: "Show help for the command or group, or for the CLI when no command is given."
15198
15213
  },
15199
15214
  {
15200
15215
  flag: "version",
@@ -15204,31 +15219,18 @@ var GLOBAL_FLAGS = [
15204
15219
  }
15205
15220
  ];
15206
15221
 
15207
- // ../contract/src/paths.ts
15208
- var API = {
15209
- me: "/api/agent/v1/me",
15210
- authConfig: "/api/agent/v1/auth/config",
15211
- search: "/api/agent/v1/hotels/search",
15212
- rates: "/api/agent/v1/hotels/rates",
15213
- bookings: "/api/agent/v1/hotels/bookings",
15214
- mcp: "/api/mcp",
15215
- hotel(id, includeRooms) {
15216
- return `/api/agent/v1/hotels/${id}${includeRooms ? "?include=rooms" : ""}`;
15217
- }
15218
- };
15219
-
15220
15222
  // ../contract/src/guide-data.generated.ts
15221
15223
  var FRAGMENTS = {
15222
- "booking": '{{#if claude-skill cli}}\n## Booking\n\n{{/if}}\n- Call `{{tool:book}}` only for the rate the user picked{{#if claude-skill cli}}, with exactly the hotel, `{{arg:roomId}}`, `{{arg:ratePlanCode}}`, dates, `{{arg:rooms}}`, `{{arg:adults}}` and `{{arg:childrenAges}}` used in `{{tool:rates}}`{{/if}}.\n- Guest names: Latin letters as on the passport (M\xFCller \u2192 Mueller), first and last name 1\u2013{{limit:guestName}} characters each{{#if cli}}, as `--guest "First Last"` (the last word is the last name; `"First Last:2"` puts the guest in room 2){{/if}}.{{#if claude-skill cli}} At least one guest per room, no more than the room sleeps.{{/if}}\n{{#if claude-skill cli}}\n- A contact name (up to {{limit:contactName}} characters) and phone ({{limit:contactPhoneMin}}\u2013{{limit:contactPhoneMax}} characters) are required; an email is optional. Special requests must be in English and aren\'t guaranteed.\n- One booking covers at most {{limit:nights}} nights; book a longer stay as separate bookings.\n{{/if}}\n- The `{{arg:nationality}}` given to `{{tool:rates}}` isn\'t sent with the booking: a rate restricted by nationality may change or be refused.\n- `{{tool:book}}` can take up to about two minutes{{#if cli}}: allow the command at least 3 minutes{{/if}}. If it times out, {{#if cli}}run it again with identical flags{{/if}}{{#if mcp claude-skill}}call it again with the same details{{/if}}: it resumes the same order{{#if claude-skill cli}}, with the same or a refreshed payment link{{/if}}.\n{{#if cli}}\n- `{{tool:book}}` takes only these fields: a `--body` with any other field (such as `nationality`) is rejected with exit 2.\n{{/if}}\n',
15223
- "errors": "{{#if claude-skill cli}}\n## Errors\n\n{{#if claude-skill}}A tool error{{/if}}{{#if cli}}An error on stderr{{/if}} carries `error` (what went wrong) and `status`, and most carry a `reason`:\n\n| `reason` | Do this |\n|---|---|\n| `already_booked` (409) | This stay is already paid or completed: show `orderUrl` and don't book again |\n| `price_or_terms_changed` (409) | The rate changed; `changes` says how. Show the user what changed and re-run `{{tool:rates}}`; if they still want it, call `{{tool:book}}` again and confirm the new price and terms before giving the link |\n| `rate_unavailable` (422) | The rate can no longer be booked: re-run `{{tool:rates}}` and let the user choose again |\n| `supplier_unavailable` (503 or 504) | Wait `retryAfterSeconds` and retry once with the same details; if it fails again, tell the user |\n| `refused` (422) | The booking was refused (a guest name, too many attempts, bookings paused): read `error` and explain it to the user |\n{{#if claude-skill}}\n\nWithout a `reason`:\n\n- **429**: wait `retryAfterSeconds`, then retry once.\n- **503** with `retryable: true` is a brief failure on Fortrip's side (for example, the same booking started twice at once): wait `retryAfterSeconds`, then retry once with the same details.\n- **500** and **502** are generic failures: read `error` and tell the user instead of guessing.\n{{/if}}\n{{/if}}\n",
15224
+ "booking": '{{#if claude-skill cli}}\n## Booking\n\n{{/if}}\n- Call `{{tool:hotels book}}` only for the rate the user picked{{#if claude-skill cli}}, with exactly the hotel, `{{arg:roomId}}`, `{{arg:ratePlanCode}}`, dates, `{{arg:rooms}}`, `{{arg:adults}}` and `{{arg:childrenAges}}` used in `{{tool:hotels rates}}`{{/if}}.\n- Guest names: Latin letters as on the passport (M\xFCller \u2192 Mueller), first and last name 1\u2013{{limit:guestName}} characters each{{#if cli}}, as `--guest "First Last"` (the last word is the last name; `"First Last:2"` puts the guest in room 2){{/if}}.{{#if claude-skill cli}} At least one guest per room, no more than the room sleeps.{{/if}}\n{{#if claude-skill cli}}\n- A contact name (up to {{limit:contactName}} characters) and phone ({{limit:contactPhoneMin}}\u2013{{limit:contactPhoneMax}} characters) are required; an email is optional. Special requests must be in English and aren\'t guaranteed.\n- One booking covers at most {{limit:nights}} nights; book a longer stay as separate bookings.\n{{/if}}\n- The `{{arg:nationality}}` given to `{{tool:hotels rates}}` isn\'t sent with the booking: a rate restricted by nationality may change or be refused.\n- `{{tool:hotels book}}` can take up to about two minutes{{#if cli}}: allow the command at least 3 minutes{{/if}}. If it times out, {{#if cli}}run it again with identical flags{{/if}}{{#if mcp claude-skill}}call it again with the same details{{/if}}: it resumes the same order{{#if claude-skill cli}}, with the same or a refreshed payment link{{/if}}.\n{{#if cli}}\n- `{{tool:hotels book}}` takes only these fields: a `--body` with any other field (such as `nationality`) is rejected with exit 2.\n{{/if}}\n',
15225
+ "errors": "{{#if claude-skill cli}}\n## Errors\n\n{{#if claude-skill}}A tool error{{/if}}{{#if cli}}An error on stderr{{/if}} carries `error` (what went wrong) and `status`, and most carry a `reason`:\n\n| `reason` | Do this |\n|---|---|\n| `already_booked` (409) | This stay is already paid or completed: show `orderUrl` and don't book again |\n| `price_or_terms_changed` (409) | The rate changed; `changes` says how. Show the user what changed and re-run `{{tool:hotels rates}}`; if they still want it, call `{{tool:hotels book}}` again and confirm the new price and terms before giving the link |\n| `rate_unavailable` (422) | The rate can no longer be booked: re-run `{{tool:hotels rates}}` and let the user choose again |\n| `supplier_unavailable` (503 or 504) | Wait `retryAfterSeconds` and retry once with the same details; if it fails again, tell the user |\n| `refused` (422) | The booking was refused (a guest name, too many attempts, bookings paused): read `error` and explain it to the user |\n{{#if claude-skill}}\n\nWithout a `reason`:\n\n- **429**: wait `retryAfterSeconds`, then retry once.\n- **503** with `retryable: true` is a brief failure on Fortrip's side (for example, the same booking started twice at once): wait `retryAfterSeconds`, then retry once with the same details.\n- **500** and **502** are generic failures: read `error` and tell the user instead of guessing.\n{{/if}}\n{{/if}}\n",
15224
15226
  "meals": "{{#if claude-skill cli}}\n## Presenting a rate\n\nGive the user, for each option:\n\n- the meal plan: `boardCode` (RO room only, BB breakfast, HB breakfast + dinner, FB breakfast + lunch + dinner, BL lunch, BD dinner, HBBL breakfast + lunch, HBLD lunch + dinner, AI all inclusive) with `boardCount`, how many guests per room the meal plan covers. Fewer than the adults per room means not everyone gets the meals (BB with `boardCount` 1 for 2 adults is breakfast for 1 of 2): say who is covered;\n- cancellation: `refundable` and `freeCancelUntil`;\n- confirmation: `isInstantConfirm`;\n- any `fees`: taxes paid at the hotel, in their own currency.\n{{/if}}\n",
15225
- "occupancy": "{{#if claude-skill cli}}\n## Occupancy\n\n{{/if}}\n- `{{tool:rates}}` and `{{tool:book}}` both require `{{arg:rooms}}` and `{{arg:adults}}`. `{{arg:adults}}` and `{{arg:childrenAges}}` are **per room**, the same in every room: 2 rooms for 4 adults is {{#if cli}}`--rooms 2 --adults 2`{{/if}}{{#if mcp claude-skill}}`rooms: 2, adults: 2`{{/if}}.\n{{#if claude-skill cli}}\n- If the party doesn't divide evenly across the rooms (3 adults in 2 rooms), ask the user whether to price every room for the larger number or to price and book each occupancy as its own stay.\n- Ask each child's age; `{{tool:rates}}` and `{{tool:book}}` need it.\n- Limits: 1\u2013{{limit:rooms}} rooms, 1\u2013{{limit:adultsPerRoom}} adults and 0\u2013{{limit:childrenPerRoom}} children (aged 0\u2013{{limit:childAgeMax}}) per room, 1\u2013{{limit:nights}} nights, up to {{limit:ratesHotels}} hotels per `{{tool:rates}}` call.\n{{/if}}\n",
15226
- "order": '{{#if claude-skill cli}}\n## Workflow\n\n1. **Search** with `{{tool:search}}` for candidate hotels and indicative prices. It needs `{{arg:checkIn}}` and `{{arg:checkOut}}`.\n{{#if cli}}\n\n ```bash\n {{tool:search}} --query "Bangkok" --check-in 2026-11-10 --check-out 2026-11-12 \\\n --star-min 4 --sort price-asc --page-size 10\n ```\n\n{{/if}}\n2. **Details** (optional) with `{{tool:hotel}}`, when the user asks about a hotel or a stated preference needs evidence (a pool, a policy, room size). For a details-only question, call it directly when you know the hotel\'s id; otherwise find the hotel with `{{tool:search}}` using the user\'s dates (ask for them if they gave none).\n{{#if cli}}\n\n ```bash\n {{tool:hotel}} 266000 [--with-rooms]\n ```\n\n{{/if}}\n3. **Rates** with `{{tool:rates}}` for a few shortlisted hotels, using the dates and occupancy the user will book. It takes about 15 seconds. A hotel is available only once this returns a live option for it.\n{{#if cli}}\n\n ```bash\n {{tool:rates}} --hotel 266000 --hotel 266001 --check-in 2026-11-10 \\\n --check-out 2026-11-12 --rooms 1 --adults 2 [--child-age 6] \\\n [--nationality CN] [--board BB] [--free-cancellation] [--currency USD]\n ```\n\n{{/if}}\n4. **Book** with `{{tool:book}}`, once the user has picked one rate and given the guest details (see Booking).\n{{#if cli}}\n\n ```bash\n {{tool:book}} --hotel 266000 --room-id 9191658 --rate-plan "<ratePlanCode>" \\\n --check-in 2026-11-10 --check-out 2026-11-12 --rooms 1 --adults 2 \\\n --guest "John Smith" --contact-name "John Smith" \\\n --contact-phone "+8613800000000" [--contact-email john@example.com]\n ```\n\n{{/if}}\n5. **Confirm, then give the payment link**, as in "Before giving the payment link" below.\n\n## Search\n\n{{/if}}\n- Search prices come from a minimum-price feed, not live rates: quote `{{tool:rates}}` prices, and never call a search result available or bookable.{{#if claude-skill cli}} When you show search prices, say they are indicative minimums, not quotes.{{/if}}\n- A search that matches too many hotels fails with `matched` and `maxMatches`: narrow it{{#if claude-skill cli}} (a city, an area, stars or a price range){{/if}}.\n{{#if claude-skill cli}}\n- Use `{{arg:near}}` for "near X"{{#if cli}} (`lat,lng,radiusKm`){{/if}}, and `{{arg:cityCode}}` when you know the city\'s code.\n- Facility names in `{{arg:facilities}}` must match the catalogue exactly (`Swimming pool`, `Parking`); near-synonyms match nothing.\n{{/if}}\n',
15227
- "payment-link": "{{#if claude-skill cli}}\n## Before giving the payment link\n\n`{{tool:book}}` returns `paymentUrl`, `statusUrl`, `orderUrl`, `amount`, `currency`, `terms` and `stay`. Nothing is charged until the user pays.\n\n1. Show the `stay` (hotel, room, dates, nights, rooms, adults and children's ages per room), the `amount` and `currency` to be charged, and every item in `terms`: meal plan (`boardCode` with `boardCount`), cancellation (`refundable`, `freeCancelUntil`, the `cancellation` ladder), confirmation (`isInstantConfirm`, `confirmByMins`) and `payAtHotelFees`.\n2. End that reply by asking the user to confirm, and wait for a clear yes. Urgency is not consent.\n3. Then give `paymentUrl` exactly as returned: don't shorten, rebuild or re-encode it. It opens a Fortrip page that recaps the booking and continues to payment on Stripe.\n\n{{/if}}\n{{#if mcp}}\n- Before you give `paymentUrl`, show the user the `stay`, `amount`, `currency` and every item in `terms` that `{{tool:book}}` returned, end that reply by asking the user to confirm, and wait for a clear yes. Then give `paymentUrl` exactly as returned, to this user only: anyone holding it can pay.\n{{/if}}\n- If `amount` or `terms` is null, don't give the link: call `{{tool:book}}` again with the same details, and if either is still null, tell the user to contact {{support}}.\n{{#if claude-skill cli}}\n- `paymentUrl` works like a payment credential: anyone holding it can pay. Give it to this user only, never in shared channels, documents, logs or tickets. It expires about 3 hours 45 minutes after it was created; for a fresh one, call `{{tool:book}}` again with the same details.\n- `statusUrl` is the same page: after payment it shows the booking's confirmation progress, without signing in. `orderUrl` is the user's signed-in order page.\n{{/if}}\n- Never ask for, accept or pass on payment card details; payment happens only on the payment page.{{#if claude-skill cli}} A card number in a guest name, the contact name or the special requests is rejected{{#if cli}} with exit 2{{/if}}.{{/if}}\n",
15228
- "planning": "# Planning a hotel stay\n\nRead this when the request is more than one hotel for fixed dates: flexible dates, a budget, split stays, several destinations, or \"find me the best\". The rules in {{#if mcp}}the server instructions{{/if}}{{#if claude-skill}}SKILL.md{{/if}}{{#if cli}}`{{tool:guide}}`{{/if}} still apply (call order, occupancy per room, confirming the terms `{{tool:book}}` returns before giving the payment link); this covers what to ask, how to compare and how to present.{{#if cli}} As there, `fortrip` is short for `npx -y fortrip@latest`.{{/if}}\n\n## What to collect\n\nKeep track of: dates (or the flexible window), nights, destination(s), rooms and the guests in each room, room constraints (beds, connecting rooms, accessibility), budget, and what the user said matters most (cheapest, most flexible, closest to X).\n\nOccupancy is per room and the same in every room, so if the party doesn't split evenly across the rooms, ask how it splits.\n\nAsk only for what changes which hotels qualify or how they rank. If children come along, ask each child's age; `{{tool:rates}}` and `{{tool:book}}` need it. Don't ask for trip type or style: infer it from the conversation or leave it general.\n\n## Evidence\n\n- A `{{tool:search}}` price is an indicative minimum, not a quote. Never call a search result available or bookable.\n- Use `{{tool:hotel}}` only when a stated preference needs evidence (a pool, a policy, room size).\n- A hotel is available only once `{{tool:rates}}` returned a live option for it.\n- Anything {{#if mcp claude-skill}}the tools{{/if}}{{#if cli}}the CLI{{/if}} didn't return, such as reviews, style, price trends or exchange rates other than `fx`, is unknown. Say so instead of filling it in.\n\n## Ranking\n\n1. Drop options that break a hard constraint (dates, guests, budget cap, required facility). Compare a budget in the unit the user gave it: per night, per room or for the whole stay.\n2. Keep only options with a live rate.\n3. Order by the user's stated main goal. Goals they didn't state don't count.\n4. Use other preferences only to break ties.\n\nUse the cheapest comparable live room as the baseline unless the user fixed the room type. Show at most three plans, each with its price difference from the baseline and what it trades off (meal plan, cancellation, location, instant confirmation). Keep hotel names as returned and dates in the user's notation.\n\nFor a split stay or several destinations, price each leg with its own `{{tool:rates}}` call and add up the legs. Each leg is a separate `{{tool:book}}` call, with its own confirmation and its own payment link.\n\n## Stop and say so\n\n- `{{tool:rates}}` covers up to {{limit:ratesHotels}} hotels per call and takes about 15 seconds. If the request needs more hotels or date combinations than a few calls can cover, narrow it with the user, or present the result as a comparison of the hotels you priced, not the best overall.\n- Urgency is not consent. Call `{{tool:book}}` only for the rate the user picked, and give its payment link only after the user confirms the amount, currency, stay and terms that `{{tool:book}}` returned.\n",
15229
- "prices": "{{#if claude-skill cli}}\n## Prices and currency\n\n{{/if}}\n- In `{{tool:rates}}`, `totalPrice` is the whole stay for all rooms, and `dailyPrices` are per room per night.{{#if claude-skill cli}} To compare with a per-night or per-room budget, divide the total by the nights and rooms, or use `dailyPrices`.{{/if}}\n- Pass `{{arg:currency}}`{{#if claude-skill cli}} to `{{tool:search}}` and `{{tool:rates}}`{{/if}}: `CNY` (the default) or `USD`, to suit the user's language or location.{{#if cli}} Without `--currency`, the CLI uses the currency in `--body`, then `FORTRIP_CURRENCY`, then `CNY`.{{/if}}{{#if claude-skill cli}} Prices are converted at Stripe's exchange rate, or a fixed fallback rate when Stripe is unavailable; `fx` says which, and is `null` when nothing needed converting.{{/if}}\n{{#if claude-skill cli}}\n- `{{arg:priceMin}}` and `{{arg:priceMax}}` on `{{tool:search}}` bound the indicative nightly price, in that currency.\n{{/if}}\n- The charge is the `amount` in the `currency` that `{{tool:book}}` returns (currently USD). A converted price is a reference: never present it as the charge.{{#if claude-skill cli}} Stripe's page may also show the charge in the user's local currency, at Stripe's rate. Fees paid at the hotel are separate, in their own currency.{{/if}}\n",
15230
- "scope": "{{#if claude-skill cli}}\n# Fortrip hotel stays\n\n{{/if}}\nFortrip: hotel search, live rates and booking payment links.{{#if claude-skill cli}} Not for flights, activities, or changing or cancelling an existing booking.{{/if}}\n\n{{#if mcp}}\nCall order: `{{tool:search}}` \u2192 (optional) `{{tool:hotel}}` \u2192 `{{tool:rates}}` \u2192 `{{tool:book}}`. `{{tool:guide}}` returns Fortrip's guidance for flexible dates, budgets, split stays, several destinations or \"find the best\" requests.\n{{/if}}\n{{#if claude-skill}}\nThe tools are on Fortrip's MCP server, named `fortrip` in this plugin (a Fortrip connector added on claude.ai can show under its own name). They're written `fortrip:<tool>` below:\n{{/if}}\n{{#if cli}}\nThe `fortrip` CLI (npm package `fortrip`) calls Fortrip as the signed-in user and prints JSON:\n{{/if}}\n{{#if claude-skill cli}}\n\n| {{#if claude-skill}}Tool{{/if}}{{#if cli}}Command{{/if}} | Returns |\n|---|---|\n| `{{tool:search}}` | Hotels by name, brand or city (`{{arg:query}}`), `{{arg:cityCode}}`, or a point and radius (`{{arg:near}}`), with indicative prices |\n| `{{tool:hotel}}` | One hotel's address, check-in and check-out times, policies, facilities and photos; no prices |\n| `{{tool:rates}}` | Live rooms, meal plans, cancellation terms and prices for up to {{limit:ratesHotels}} hotels |\n| `{{tool:book}}` | An unpaid booking for one rate, with its terms and a payment link |\n\nFor flexible dates, a budget, split stays, several destinations or \"find me the best\", {{#if claude-skill}}read [references/planning.md](references/planning.md){{/if}}{{#if cli}}also run `{{tool:guide}} planning`{{/if}} before searching.\n{{/if}}\n",
15231
- "setup": "{{#if cli}}\n## Setup\n\nRun every command as `npx -y fortrip@latest <command>` (Node.js 22 or newer). The examples below shorten that to `fortrip`; `fortrip <command> --help` lists a command's flags. `{{tool:search}}`, `{{tool:rates}}` and `{{tool:book}}` also take `--body '<json>'` (or `--body @file.json`), the raw request body; flags override its fields.\n\nWhen the user first asks about hotels, run `{{tool:whoami}}` before asking follow-up questions, so the sign-in can happen while they answer.\n\n{{/if}}\n{{#if claude-skill cli}}\n## Sign-in\n\n{{/if}}\nSign-in happens only in the browser. Never ask the user to paste a token.\n{{#if claude-skill}}\n\nIf none of these tools are available, Fortrip isn't connected: tell the user to add the Fortrip connector or plugin and sign in.\n{{/if}}\n{{#if mcp claude-skill}}\nIf a tool says the user isn't signed in, ask them to reconnect Fortrip and sign in again.\n{{/if}}\n{{#if cli}}\n\nIf a command exits with code **3** and stderr has `loginUrl`, show it to the user as a link together with `userCode`, e.g. \"Sign in to Fortrip: <loginUrl> (code <userCode>), then tell me when you're done.\" When they confirm, rerun the same command; it finishes the sign-in itself. Don't run `{{tool:login}}`: it waits for up to 10 minutes. Commands that exit 3 together share one `loginUrl`; show it once. If the rerun gives a different `loginUrl`, show that one.\n\nWhen Fortrip no longer accepts the saved sign-in (it was revoked, or it expired and couldn't be renewed), the command also exits 3 with a `loginUrl`; handle it the same way. The saved sign-in is kept until the user approves the new link.\n\nIn CI or a sandbox without a browser, the user can set `FORTRIP_TOKEN` instead. Exit 3 with `tokenSource: \"FORTRIP_TOKEN\"` means Fortrip didn't accept that token: tell the user to replace or unset it.\n\n## Exit codes\n\n| Code | Meaning | What to do |\n|---|---|---|\n| 0 | Success; JSON on stdout | Continue |\n| 1 | Any other error: an error from Fortrip (see Errors), a network error or a timeout | Read `error` on stderr. If `retryable` is true, wait `retryAfterSeconds` (or a few seconds) and retry once |\n| 2 | Invalid arguments | Fix the flags; stderr `issues` lists each bad field with a message |\n| 3 | Not signed in | Show `loginUrl` to the user, then rerun (above) |\n| 4 | Rate limited | Wait `retryAfterSeconds`, then retry once |\n\nOutput on stdout or stderr can carry `warning` (with `recoveryFile` when a sign-in couldn't be saved): pass it on to the user.\n{{/if}}\n",
15227
+ "occupancy": "{{#if claude-skill cli}}\n## Occupancy\n\n{{/if}}\n- `{{tool:hotels rates}}` and `{{tool:hotels book}}` both require `{{arg:rooms}}` and `{{arg:adults}}`. `{{arg:adults}}` and `{{arg:childrenAges}}` are **per room**, the same in every room: 2 rooms for 4 adults is {{#if cli}}`--rooms 2 --adults 2`{{/if}}{{#if mcp claude-skill}}`rooms: 2, adults: 2`{{/if}}.\n{{#if claude-skill cli}}\n- If the party doesn't divide evenly across the rooms (3 adults in 2 rooms), ask the user whether to price every room for the larger number or to price and book each occupancy as its own stay.\n- Ask each child's age; `{{tool:hotels rates}}` and `{{tool:hotels book}}` need it.\n- Limits: 1\u2013{{limit:rooms}} rooms, 1\u2013{{limit:adultsPerRoom}} adults and 0\u2013{{limit:childrenPerRoom}} children (aged 0\u2013{{limit:childAgeMax}}) per room, 1\u2013{{limit:nights}} nights, up to {{limit:ratesHotels}} hotels per `{{tool:hotels rates}}` call.\n{{/if}}\n",
15228
+ "order": '{{#if claude-skill cli}}\n## Workflow\n\n1. **Search** with `{{tool:hotels search}}` for candidate hotels and indicative prices. It needs `{{arg:checkIn}}` and `{{arg:checkOut}}`.\n{{#if cli}}\n\n ```bash\n {{tool:hotels search}} --query "Bangkok" --check-in 2026-11-10 --check-out 2026-11-12 \\\n --star-min 4 --sort price-asc --page-size 10\n ```\n\n{{/if}}\n2. **Details** (optional) with `{{tool:hotels get}}`, when the user asks about a hotel or a stated preference needs evidence (a pool, a policy, room size). For a details-only question, call it directly when you know the hotel\'s id; otherwise find the hotel with `{{tool:hotels search}}` using the user\'s dates (ask for them if they gave none).\n{{#if cli}}\n\n ```bash\n {{tool:hotels get}} 266000 [--with-rooms]\n ```\n\n{{/if}}\n3. **Rates** with `{{tool:hotels rates}}` for a few shortlisted hotels, using the dates and occupancy the user will book. It takes about 15 seconds. A hotel is available only once this returns a live option for it.\n{{#if cli}}\n\n ```bash\n {{tool:hotels rates}} --hotel 266000 --hotel 266001 --check-in 2026-11-10 \\\n --check-out 2026-11-12 --rooms 1 --adults 2 [--child-age 6] \\\n [--nationality CN] [--board BB] [--free-cancellation] [--currency USD]\n ```\n\n{{/if}}\n4. **Book** with `{{tool:hotels book}}`, once the user has picked one rate and given the guest details (see Booking).\n{{#if cli}}\n\n ```bash\n {{tool:hotels book}} --hotel 266000 --room-id 9191658 --rate-plan "<ratePlanCode>" \\\n --check-in 2026-11-10 --check-out 2026-11-12 --rooms 1 --adults 2 \\\n --guest "John Smith" --contact-name "John Smith" \\\n --contact-phone "+8613800000000" [--contact-email john@example.com]\n ```\n\n{{/if}}\n5. **Confirm, then give the payment link**, as in "Before giving the payment link" below.\n\n## Search\n\n{{/if}}\n- Search prices come from a minimum-price feed, not live rates: quote `{{tool:hotels rates}}` prices, and never call a search result available or bookable.{{#if claude-skill cli}} When you show search prices, say they are indicative minimums, not quotes.{{/if}}\n- A search that matches too many hotels fails with `matched` and `maxMatches`: narrow it{{#if claude-skill cli}} (a city, an area, stars or a price range){{/if}}.\n{{#if claude-skill cli}}\n- Use `{{arg:near}}` for "near X"{{#if cli}} (`lat,lng,radiusKm`){{/if}}, and `{{arg:cityCode}}` when you know the city\'s code.\n- Facility names in `{{arg:facilities}}` must match the catalogue exactly (`Swimming pool`, `Parking`); near-synonyms match nothing.\n{{/if}}\n',
15229
+ "payment-link": "{{#if claude-skill cli}}\n## Before giving the payment link\n\n`{{tool:hotels book}}` returns `paymentUrl`, `statusUrl`, `orderUrl`, `amount`, `currency`, `terms` and `stay`. Nothing is charged until the user pays.\n\n1. Show the `stay` (hotel, room, dates, nights, rooms, adults and children's ages per room), the `amount` and `currency` to be charged, and every item in `terms`: meal plan (`boardCode` with `boardCount`), cancellation (`refundable`, `freeCancelUntil`, the `cancellation` ladder), confirmation (`isInstantConfirm`, `confirmByMins`) and `payAtHotelFees`.\n2. End that reply by asking the user to confirm, and wait for a clear yes. Urgency is not consent.\n3. Then give `paymentUrl` exactly as returned: don't shorten, rebuild or re-encode it. It opens a Fortrip page that recaps the booking and continues to payment on Stripe.\n\n{{/if}}\n{{#if mcp}}\n- Before you give `paymentUrl`, show the user the `stay`, `amount`, `currency` and every item in `terms` that `{{tool:hotels book}}` returned, end that reply by asking the user to confirm, and wait for a clear yes. Then give `paymentUrl` exactly as returned, to this user only: anyone holding it can pay.\n{{/if}}\n- If `amount` or `terms` is null, don't give the link: call `{{tool:hotels book}}` again with the same details, and if either is still null, tell the user to contact {{support}}.\n{{#if claude-skill cli}}\n- `paymentUrl` works like a payment credential: anyone holding it can pay. Give it to this user only, never in shared channels, documents, logs or tickets. It expires about 3 hours 45 minutes after it was created; for a fresh one, call `{{tool:hotels book}}` again with the same details.\n- `statusUrl` is the same page: after payment it shows the booking's confirmation progress, without signing in. `orderUrl` is the user's signed-in order page.\n{{/if}}\n- Never ask for, accept or pass on payment card details; payment happens only on the payment page.{{#if claude-skill cli}} A card number in a guest name, the contact name or the special requests is rejected{{#if cli}} with exit 2{{/if}}.{{/if}}\n",
15230
+ "planning": "# Planning a hotel stay\n\nRead this when the request is more than one hotel for fixed dates: flexible dates, a budget, split stays, several destinations, or \"find me the best\". The rules in {{#if mcp}}the server instructions{{/if}}{{#if claude-skill}}SKILL.md{{/if}}{{#if cli}}`{{tool:guide}}`{{/if}} still apply (call order, occupancy per room, confirming the terms `{{tool:hotels book}}` returns before giving the payment link); this covers what to ask, how to compare and how to present.{{#if cli}} As there, `fortrip` is short for `npx -y fortrip@latest`.{{/if}}\n\n## What to collect\n\nKeep track of: dates (or the flexible window), nights, destination(s), rooms and the guests in each room, room constraints (beds, connecting rooms, accessibility), budget, and what the user said matters most (cheapest, most flexible, closest to X).\n\nOccupancy is per room and the same in every room, so if the party doesn't split evenly across the rooms, ask how it splits.\n\nAsk only for what changes which hotels qualify or how they rank. If children come along, ask each child's age; `{{tool:hotels rates}}` and `{{tool:hotels book}}` need it. Don't ask for trip type or style: infer it from the conversation or leave it general.\n\n## Evidence\n\n- A `{{tool:hotels search}}` price is an indicative minimum, not a quote. Never call a search result available or bookable.\n- Use `{{tool:hotels get}}` only when a stated preference needs evidence (a pool, a policy, room size).\n- A hotel is available only once `{{tool:hotels rates}}` returned a live option for it.\n- Anything {{#if mcp claude-skill}}the tools{{/if}}{{#if cli}}the CLI{{/if}} didn't return, such as reviews, style, price trends or exchange rates other than `fx`, is unknown. Say so instead of filling it in.\n\n## Ranking\n\n1. Drop options that break a hard constraint (dates, guests, budget cap, required facility). Compare a budget in the unit the user gave it: per night, per room or for the whole stay.\n2. Keep only options with a live rate.\n3. Order by the user's stated main goal. Goals they didn't state don't count.\n4. Use other preferences only to break ties.\n\nUse the cheapest comparable live room as the baseline unless the user fixed the room type. Show at most three plans, each with its price difference from the baseline and what it trades off (meal plan, cancellation, location, instant confirmation). Keep hotel names as returned and dates in the user's notation.\n\nFor a split stay or several destinations, price each leg with its own `{{tool:hotels rates}}` call and add up the legs. Each leg is a separate `{{tool:hotels book}}` call, with its own confirmation and its own payment link.\n\n## Stop and say so\n\n- `{{tool:hotels rates}}` covers up to {{limit:ratesHotels}} hotels per call and takes about 15 seconds. If the request needs more hotels or date combinations than a few calls can cover, narrow it with the user, or present the result as a comparison of the hotels you priced, not the best overall.\n- Urgency is not consent. Call `{{tool:hotels book}}` only for the rate the user picked, and give its payment link only after the user confirms the amount, currency, stay and terms that `{{tool:hotels book}}` returned.\n",
15231
+ "prices": "{{#if claude-skill cli}}\n## Prices and currency\n\n{{/if}}\n- In `{{tool:hotels rates}}`, `totalPrice` is the whole stay for all rooms, and `dailyPrices` are per room per night.{{#if claude-skill cli}} To compare with a per-night or per-room budget, divide the total by the nights and rooms, or use `dailyPrices`.{{/if}}\n- Pass `{{arg:currency}}`{{#if claude-skill cli}} to `{{tool:hotels search}}` and `{{tool:hotels rates}}`{{/if}}: `CNY` (the default) or `USD`, to suit the user's language or location.{{#if cli}} Without `--currency`, the CLI uses the currency in `--body`, then `FORTRIP_CURRENCY`, then `CNY`.{{/if}}{{#if claude-skill cli}} Prices are converted at Stripe's exchange rate, or a fixed fallback rate when Stripe is unavailable; `fx` says which, and is `null` when nothing needed converting.{{/if}}\n{{#if claude-skill cli}}\n- `{{arg:priceMin}}` and `{{arg:priceMax}}` on `{{tool:hotels search}}` bound the indicative nightly price, in that currency.\n{{/if}}\n- The charge is the `amount` in the `currency` that `{{tool:hotels book}}` returns (currently USD). A converted price is a reference: never present it as the charge.{{#if claude-skill cli}} Stripe's page may also show the charge in the user's local currency, at Stripe's rate. Fees paid at the hotel are separate, in their own currency.{{/if}}\n",
15232
+ "scope": "{{#if claude-skill cli}}\n# Fortrip hotel stays\n\n{{/if}}\nFortrip: hotel search, live rates and booking payment links.{{#if claude-skill cli}} Not for flights, activities, or changing or cancelling an existing booking.{{/if}}\n\n{{#if mcp}}\nCall order: `{{tool:hotels search}}` \u2192 (optional) `{{tool:hotels get}}` \u2192 `{{tool:hotels rates}}` \u2192 `{{tool:hotels book}}`. `{{tool:guide}}` returns Fortrip's guidance for flexible dates, budgets, split stays, several destinations or \"find the best\" requests.\n{{/if}}\n{{#if claude-skill}}\nThe tools are on Fortrip's MCP server, named `fortrip` in this plugin (a Fortrip connector added on claude.ai can show under its own name). They're written `fortrip:<tool>` below:\n{{/if}}\n{{#if cli}}\nThe `fortrip` CLI (npm package `fortrip`) calls Fortrip as the signed-in user and prints JSON:\n{{/if}}\n{{#if claude-skill cli}}\n\n| {{#if claude-skill}}Tool{{/if}}{{#if cli}}Command{{/if}} | Returns |\n|---|---|\n| `{{tool:hotels search}}` | Hotels by name, brand or city (`{{arg:query}}`), `{{arg:cityCode}}`, or a point and radius (`{{arg:near}}`), with indicative prices |\n| `{{tool:hotels get}}` | One hotel's address, check-in and check-out times, policies, facilities and photos; no prices |\n| `{{tool:hotels rates}}` | Live rooms, meal plans, cancellation terms and prices for up to {{limit:ratesHotels}} hotels |\n| `{{tool:hotels book}}` | An unpaid booking for one rate, with its terms and a payment link |\n\nFor flexible dates, a budget, split stays, several destinations or \"find me the best\", {{#if claude-skill}}read [references/planning.md](references/planning.md){{/if}}{{#if cli}}also run `{{tool:guide}} planning`{{/if}} before searching.\n{{/if}}\n",
15233
+ "setup": "{{#if cli}}\n## Setup\n\nRun every command as `npx -y fortrip@latest <command>` (Node.js 22 or newer). The examples below shorten that to `fortrip`; `fortrip <command> --help` lists a command's flags. `{{tool:hotels search}}`, `{{tool:hotels rates}}` and `{{tool:hotels book}}` also take `--body '<json>'` (or `--body @file.json`), the raw request body; flags override its fields.\n\nWhen the user first asks about hotels, run `{{tool:whoami}}` before asking follow-up questions, so the sign-in can happen while they answer.\n\n{{/if}}\n{{#if claude-skill cli}}\n## Sign-in\n\n{{/if}}\nSign-in happens only in the browser. Never ask the user to paste a token.\n{{#if claude-skill}}\n\nIf none of these tools are available, Fortrip isn't connected: tell the user to add the Fortrip connector or plugin and sign in.\n{{/if}}\n{{#if mcp claude-skill}}\nIf a tool says the user isn't signed in, ask them to reconnect Fortrip and sign in again.\n{{/if}}\n{{#if cli}}\n\nIf a command exits with code **3** and stderr has `loginUrl`, show it to the user as a link together with `userCode`, e.g. \"Sign in to Fortrip: <loginUrl> (code <userCode>), then tell me when you're done.\" When they confirm, rerun the same command; it finishes the sign-in itself. Don't run `{{tool:login}}`: it waits for up to 10 minutes. Commands that exit 3 together share one `loginUrl`; show it once. If the rerun gives a different `loginUrl`, show that one.\n\nWhen Fortrip no longer accepts the saved sign-in (it was revoked, or it expired and couldn't be renewed), the command also exits 3 with a `loginUrl`; handle it the same way. The saved sign-in is kept until the user approves the new link.\n\nIn CI or a sandbox without a browser, the user can set `FORTRIP_TOKEN` instead. Exit 3 with `tokenSource: \"FORTRIP_TOKEN\"` means Fortrip didn't accept that token: tell the user to replace or unset it.\n\n## Exit codes\n\n| Code | Meaning | What to do |\n|---|---|---|\n| 0 | Success; JSON on stdout | Continue |\n| 1 | Any other error: an error from Fortrip (see Errors), a network error or a timeout | Read `error` on stderr. If `retryable` is true, wait `retryAfterSeconds` (or a few seconds) and retry once |\n| 2 | Invalid arguments | Fix the flags; stderr `issues` lists each bad field with a message |\n| 3 | Not signed in | Show `loginUrl` to the user, then rerun (above) |\n| 4 | Rate limited | Wait `retryAfterSeconds`, then retry once |\n\nOutput on stdout or stderr can carry `warning` (with `recoveryFile` when a sign-in couldn't be saved): pass it on to the user.\n{{/if}}\n",
15232
15234
  "support": "{{#if claude-skill cli}}\n## Changes and support\n\n{{/if}}\n{{#if mcp claude-skill}}These tools{{/if}}{{#if cli}}The CLI{{/if}} can't change or cancel a booking: for changes, cancellations, refunds or other problems with a booking, the user emails {{support}} with the booking's `orderUrl` (never `paymentUrl` or `statusUrl`).\n"
15233
15235
  };
15234
15236
 
@@ -15317,7 +15319,9 @@ function resolve(token, surface, shown) {
15317
15319
  return String(LIMITS[name]);
15318
15320
  }
15319
15321
  if (kind === "tool") {
15320
- const spec = COMMANDS.find((command) => command.name === name);
15322
+ const spec = COMMANDS.find(
15323
+ (command) => commandInvocation(command) === `fortrip ${name}`
15324
+ );
15321
15325
  if (spec) return shown ? toolName(spec, surface, token) : token;
15322
15326
  }
15323
15327
  if (kind === "arg") {
@@ -15333,13 +15337,26 @@ function resolve(token, surface, shown) {
15333
15337
  throw new Error(`Unknown guide token ${token}`);
15334
15338
  }
15335
15339
  function toolName(spec, surface, token) {
15336
- if (surface === "cli") return `fortrip ${spec.name}`;
15340
+ if (surface === "cli") return commandInvocation(spec);
15337
15341
  if (spec.tool === null || surface === "claude-skill" && spec.tool === "get_planning_guide") {
15338
15342
  throw new Error(`Guide token ${token} has no tool on ${surface}`);
15339
15343
  }
15340
15344
  return surface === "mcp" ? spec.tool : `fortrip:${spec.tool}`;
15341
15345
  }
15342
15346
 
15347
+ // ../contract/src/paths.ts
15348
+ var API = {
15349
+ me: "/api/agent/v1/me",
15350
+ authConfig: "/api/agent/v1/auth/config",
15351
+ search: "/api/agent/v1/hotels/search",
15352
+ rates: "/api/agent/v1/hotels/rates",
15353
+ bookings: "/api/agent/v1/hotels/bookings",
15354
+ mcp: "/api/mcp",
15355
+ hotel(id, includeRooms) {
15356
+ return `/api/agent/v1/hotels/${id}${includeRooms ? "?include=rooms" : ""}`;
15357
+ }
15358
+ };
15359
+
15343
15360
  // ../contract/src/outputs.ts
15344
15361
  var bookingResult = external_exports.object({
15345
15362
  agentOrderId: external_exports.string(),
@@ -15393,6 +15410,137 @@ var authConfig = external_exports.object({
15393
15410
  revocationEndpoint: external_exports.string()
15394
15411
  });
15395
15412
 
15413
+ // ../contract/src/tools.ts
15414
+ var MCP_MAX_ROOMS_PER_HOTEL = 5;
15415
+ var MCP_RATES_INPUT = getRatesInput.safeExtend({
15416
+ maxRoomsPerHotel: wholeNumber("maxRoomsPerHotel", 1, LIMITS.maxRoomsPerHotel).default(MCP_MAX_ROOMS_PER_HOTEL).describe(
15417
+ `Return the N cheapest rooms per hotel. Defaults to ${MCP_MAX_ROOMS_PER_HOTEL}.`
15418
+ )
15419
+ });
15420
+ var MCP_BOOKING_RESULT = bookingResult.extend({
15421
+ nextStep: external_exports.string()
15422
+ });
15423
+ var MEAL_CODES = "RO room only; BB breakfast; HB breakfast + dinner; FB breakfast + lunch + dinner; BL lunch; BD dinner; HBBL breakfast + lunch; HBLD lunch + dinner; AI all inclusive";
15424
+ var TOOLS = [
15425
+ {
15426
+ name: "get_planning_guide",
15427
+ title: "Get hotel planning guide",
15428
+ description: `Returns Fortrip's planning guide for a request that is more than one hotel for fixed dates (flexible dates, a budget, split stays, several destinations, "find me the best"): what to ask the user, which results count as evidence, how to rank and how to present options.`,
15429
+ annotations: {
15430
+ readOnlyHint: true,
15431
+ idempotentHint: true,
15432
+ openWorldHint: false,
15433
+ destructiveHint: false
15434
+ },
15435
+ input: null,
15436
+ output: null
15437
+ },
15438
+ {
15439
+ name: "search_hotels",
15440
+ title: "Search hotels",
15441
+ description: "Find hotels by name/brand/city text (query), catalogue cityCode or coordinates (near), with per-night prices for the dates in currency (CNY by default, or USD). Those prices come from the supplier's minimum-price feed, not live rates; get_hotel_rates returns the bookable price. facilities must match catalogue names exactly. Refused (422) when more than 30,000 hotels match: narrow the search.",
15442
+ annotations: {
15443
+ readOnlyHint: true,
15444
+ idempotentHint: true,
15445
+ openWorldHint: true,
15446
+ destructiveHint: false
15447
+ },
15448
+ input: searchHotelsInput,
15449
+ output: null
15450
+ },
15451
+ {
15452
+ name: "get_hotel",
15453
+ title: "Get hotel details",
15454
+ description: "Static catalogue facts for one hotel: address, contact, intro, check-in/out times, policies, facilities and photos. No prices or availability.",
15455
+ annotations: {
15456
+ readOnlyHint: true,
15457
+ idempotentHint: true,
15458
+ openWorldHint: true,
15459
+ destructiveHint: false
15460
+ },
15461
+ input: getHotelInput,
15462
+ output: null
15463
+ },
15464
+ {
15465
+ name: "get_hotel_rates",
15466
+ title: "Get live hotel rates",
15467
+ description: `Live rooms and rate plans for up to ${LIMITS.ratesHotels} hotels, prices in currency (CNY by default, or USD) at Stripe's exchange rate. rooms, adults and childrenAges are per room: every room has the same occupancy. Each room carries its catalogue facts (static) and the cheapest rate per meal plan \xD7 free-cancellation variant, with roomId and ratePlanCode for book_hotel. boardCode is the meal plan (${MEAL_CODES}); boardCount is how many guests per room it covers: fewer than adults per room means the meals don't cover everyone (BB with boardCount 1 for 2 adults is breakfast for 1 of 2). Takes about 15 seconds; query a few hotels at a time. Returns the ${MCP_MAX_ROOMS_PER_HOTEL} cheapest rooms per hotel unless maxRoomsPerHotel says otherwise.`,
15468
+ annotations: {
15469
+ readOnlyHint: true,
15470
+ idempotentHint: true,
15471
+ openWorldHint: true,
15472
+ destructiveHint: false
15473
+ },
15474
+ input: MCP_RATES_INPUT,
15475
+ output: null
15476
+ },
15477
+ {
15478
+ name: "book_hotel",
15479
+ title: "Create hotel booking payment link",
15480
+ description: `Create (or resume) a booking for one rate from get_hotel_rates, with the same rooms, adults and childrenAges (per room). Returns the amount and currency the user will be charged, the accepted terms (meal plan, cancellation, confirmation, pay-at-hotel fees), the stay as booked, and paymentUrl: a Fortrip page that recaps the booking and continues to payment. Nothing is charged until the user pays there; anyone holding paymentUrl can pay. The same user and stay always returns the same order; a stay that is already paid returns 409. This tool can't change or cancel a booking: for changes, cancellations or refunds the user emails ${SUPPORT_EMAIL} with the booking's orderUrl. Can take up to about two minutes; if the call times out, call it again with the same details \u2014 it resumes the same order.`,
15481
+ annotations: {
15482
+ readOnlyHint: false,
15483
+ idempotentHint: true,
15484
+ openWorldHint: true,
15485
+ destructiveHint: false
15486
+ },
15487
+ input: createBookingInput,
15488
+ output: MCP_BOOKING_RESULT
15489
+ }
15490
+ ];
15491
+
15492
+ // ../contract/package.json
15493
+ var package_default = {
15494
+ name: "@fortrip/agent-contract",
15495
+ version: "2.0.0",
15496
+ license: "Apache-2.0",
15497
+ description: "The contract of Fortrip's agent API (/api/agent/v1): limits, paths, input schemas and field rules.",
15498
+ type: "module",
15499
+ repository: {
15500
+ type: "git",
15501
+ url: "git+https://github.com/FortripEngineering/fortrip.git",
15502
+ directory: "packages/contract"
15503
+ },
15504
+ exports: {
15505
+ ".": {
15506
+ types: "./dist/index.d.ts",
15507
+ import: "./dist/index.js"
15508
+ },
15509
+ "./guest-name": {
15510
+ types: "./dist/guest-name.d.ts",
15511
+ import: "./dist/guest-name.js"
15512
+ }
15513
+ },
15514
+ files: ["dist", "NOTICE"],
15515
+ sideEffects: false,
15516
+ engines: {
15517
+ node: ">=22"
15518
+ },
15519
+ publishConfig: {
15520
+ access: "public"
15521
+ },
15522
+ scripts: {
15523
+ "embed-guide": "node --import tsx scripts/embed-guide.ts",
15524
+ build: "pnpm embed-guide && tsup",
15525
+ typecheck: "tsc --noEmit",
15526
+ test: "pnpm build && node --import tsx --test test/*.test.ts",
15527
+ prepublishOnly: "pnpm typecheck && pnpm test && pnpm build"
15528
+ },
15529
+ peerDependencies: {
15530
+ zod: "^4.4.0"
15531
+ },
15532
+ devDependencies: {
15533
+ "@types/node": "^22.15.0",
15534
+ tsup: "^8.5.1",
15535
+ tsx: "^4.20.0",
15536
+ typescript: "^5.9.0",
15537
+ zod: "4.4.3"
15538
+ }
15539
+ };
15540
+
15541
+ // ../contract/src/version.ts
15542
+ var CONTRACT_VERSION = package_default.version;
15543
+
15396
15544
  // src/errors.ts
15397
15545
  var EXIT = {
15398
15546
  ok: 0,
@@ -16313,10 +16461,14 @@ async function whoami(apiUrl, token) {
16313
16461
  }
16314
16462
 
16315
16463
  export {
16464
+ COMMAND_GROUPS,
16316
16465
  COMMANDS,
16466
+ commandInvocation,
16317
16467
  GLOBAL_FLAGS,
16318
16468
  renderGuide,
16319
16469
  API,
16470
+ TOOLS,
16471
+ CONTRACT_VERSION,
16320
16472
  EXIT,
16321
16473
  CliError,
16322
16474
  notices,
@@ -16324,6 +16476,7 @@ export {
16324
16476
  VERSION,
16325
16477
  unwrap,
16326
16478
  resolveApiUrl,
16479
+ loadSession,
16327
16480
  requireToken,
16328
16481
  refreshSession,
16329
16482
  authorizedRequest,