fortrip 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENT_GUIDE.md ADDED
@@ -0,0 +1,124 @@
1
+ # Fortrip hotels
2
+
3
+ Fortrip sells hotel rooms at supplier net prices. The
4
+ `fortrip` CLI (npm package `fortrip`) calls the Fortrip API as the
5
+ signed-in user and prints JSON.
6
+
7
+ ## When to use
8
+
9
+ - The user wants hotels in a city, near a point, or by name, for given dates.
10
+ - The user wants live rooms, meal plans, cancellation terms or prices for
11
+ specific hotels.
12
+ - The user has picked a rate and wants to book it (you return a payment link;
13
+ the user pays in their browser).
14
+
15
+ Not for flights, activities, or changing/cancelling an existing booking.
16
+
17
+ ## Setup
18
+
19
+ Run every command as `npx -y fortrip@latest <command>`, the same version that
20
+ printed this guide. The examples below shorten that to `fortrip`.
21
+
22
+ When the user first asks about hotels, run `fortrip whoami` before asking
23
+ follow-up questions, so a login can happen while they answer.
24
+
25
+ If a command exits with code **3** and stderr has `loginUrl`, show it to the
26
+ user as a link together with `userCode`, e.g. "Sign in to Fortrip: <loginUrl>
27
+ (code BCDF-GHJK) and tell me when you're done." When they confirm, rerun the
28
+ same command; it finishes the login itself. Don't run `fortrip login` — it
29
+ waits for up to 10 minutes. If the rerun gives a new `loginUrl`, the old one
30
+ expired or was denied; show the new one.
31
+
32
+ If exit 3 has no `loginUrl`, the saved token was revoked: run `fortrip logout`,
33
+ then rerun the command to get a link.
34
+
35
+ In CI, the user can set `FORTRIP_TOKEN` instead. Never ask the user to paste a
36
+ token into the chat.
37
+
38
+ ## Exit codes
39
+
40
+ | Code | Meaning | What to do |
41
+ |---|---|---|
42
+ | 0 | Success; JSON on stdout | Continue |
43
+ | 1 | Other error (supplier error, rate no longer bookable, network) | Read `error` on stderr; re-run `rates` if a rate went stale |
44
+ | 2 | Invalid arguments | Fix the flags; stderr `issues` lists the bad fields |
45
+ | 3 | Not logged in | Show `loginUrl` to the user, then rerun (above) |
46
+ | 4 | Rate limited | Wait `retryAfterSeconds`, then retry once |
47
+
48
+ ## Recommended order
49
+
50
+ 1. **Search** — find candidates and indicative prices:
51
+
52
+ ```bash
53
+ fortrip search --query "Bangkok" --check-in 2026-11-10 --check-out 2026-11-12 \
54
+ --star-min 4 --sort price-asc --page-size 10
55
+ ```
56
+
57
+ Use `--near lat,lng,radiusKm` for "near X", `--city-code` when known.
58
+ `--facility` names must match the catalogue exactly (e.g. `Swimming pool`,
59
+ `Parking`), otherwise nothing matches. Prices here (`price.nightlyFrom`,
60
+ `totalFrom`, CNY) are **indicative floors**, not quotes — say so.
61
+
62
+ 2. **Hotel detail** (optional) — description, policies, facilities, photos:
63
+
64
+ ```bash
65
+ fortrip hotel 266000 [--with-rooms]
66
+ ```
67
+
68
+ 3. **Rates** — live rooms and prices for up to 15 hotels:
69
+
70
+ ```bash
71
+ fortrip rates --hotel 266000 --hotel 266001 --check-in 2026-11-10 \
72
+ --check-out 2026-11-12 --rooms 1 --adults 2 [--child-age 6] \
73
+ [--nationality CN] [--board BB] [--free-cancellation]
74
+ ```
75
+
76
+ This call takes about 15 seconds. `totalPrice` is CNY at a fixed exchange
77
+ rate. Present each option with meal plan (`boardCode`: RO room only,
78
+ BB breakfast, HB half board, FB full board, AI all inclusive),
79
+ `freeCancelUntil` / `refundable`, `isInstantConfirm`, and any `fees`
80
+ (taxes paid at the hotel, in their own currency).
81
+
82
+ 4. **Book** — only after the user picks one rate and gives guest details:
83
+
84
+ ```bash
85
+ fortrip book --hotel 266000 --room-id 9191658 --rate-plan "<ratePlanCode>" \
86
+ --check-in 2026-11-10 --check-out 2026-11-12 --rooms 1 --adults 2 \
87
+ --guest "John Smith" --contact-name "John Smith" \
88
+ --contact-phone "+8613800000000" [--contact-email john@example.com]
89
+ ```
90
+
91
+ `--hotel`, `--room-id`, `--rate-plan`, dates, `--rooms`, `--adults` and
92
+ `--child-age` must be exactly the values used in `rates`. Guest names must
93
+ be in Latin letters as on the passport ("First Last"; last word is the last
94
+ name; `"First Last:2"` puts the guest in room 2). Each room needs at least
95
+ one guest. `--special-requests` must be in English and isn't guaranteed.
96
+
97
+ ## Before giving the payment link
98
+
99
+ `book` returns `paymentUrl`, `amount`, `currency`, `terms`, `statusUrl` and
100
+ `orderUrl`. Before you hand over `paymentUrl`:
101
+
102
+ 1. **Show the `terms`** and get the user's confirmation: meal plan
103
+ (`boardCode`, `boardCount`), cancellation (`refundable`, `freeCancelUntil`,
104
+ the `cancellation` ladder), confirmation (`isInstantConfirm`,
105
+ `confirmByMins`) and `payAtHotelFees`.
106
+ 2. **Explain the amount.** `rates` showed CNY, but the charge is `amount` in
107
+ `currency` (usually USD), and the Stripe page may display the user's local
108
+ currency at Stripe's rate. The amount on the payment page is what will be
109
+ charged; the CNY figure was a reference.
110
+ 3. The link is for **this user only**: whoever has it can pay. Don't post it
111
+ in shared channels, logs or tickets. It expires after about 3 hours 45
112
+ minutes; call `book` again with the same details for a fresh one.
113
+
114
+ After paying, the user lands on `statusUrl`, which shows confirmation progress
115
+ without signing in. `orderUrl` is the signed-in order page.
116
+
117
+ ## Errors from `book`
118
+
119
+ - `409` with `agentOrderId`: this exact stay is already paid or completed —
120
+ show `orderUrl`, don't book again.
121
+ - `409` with `changes`: price or terms changed — re-run `rates` and confirm
122
+ again with the user.
123
+ - `422`: the booking was refused (guest names, too many attempts, bookings
124
+ paused, or a preview deployment). Read `error`.
package/README.md CHANGED
@@ -53,9 +53,16 @@ Tokens don't expire; `logout` revokes them.
53
53
 
54
54
  ## Agent skill
55
55
 
56
- [`skills/fortrip-hotels/SKILL.md`](skills/fortrip-hotels/SKILL.md) tells an AI
57
- agent when to use the CLI, the call order, and what to show the user before
58
- handing over a payment link.
56
+ ```bash
57
+ npx skills add FortripEngineering/fortrip-cli
58
+ ```
59
+
60
+ [`skills/fortrip-hotels/SKILL.md`](skills/fortrip-hotels/SKILL.md) only tells
61
+ the agent to run `npx -y fortrip@latest skill`, which prints
62
+ [`AGENT_GUIDE.md`](AGENT_GUIDE.md): when to use the CLI, the call order, login,
63
+ and what to show the user before handing over a payment link. The guide ships
64
+ with the package, so a CLI release updates every installed skill. Edit
65
+ `AGENT_GUIDE.md`, not `SKILL.md`.
59
66
 
60
67
  ## Development
61
68
 
package/dist/cli.js CHANGED
@@ -22,7 +22,10 @@ function usageError(message) {
22
22
  }
23
23
 
24
24
  // src/version.ts
25
- var VERSION = "0.1.0";
25
+ import { readFileSync } from "fs";
26
+ var VERSION = JSON.parse(
27
+ readFileSync(new URL("../package.json", import.meta.url), "utf8")
28
+ ).version;
26
29
 
27
30
  // src/api.ts
28
31
  var LOGIN_HINT = "Run `npx fortrip login` first.";
@@ -96,7 +99,7 @@ function unwrap(response) {
96
99
  }
97
100
 
98
101
  // src/args.ts
99
- import { readFileSync } from "fs";
102
+ import { readFileSync as readFileSync2 } from "fs";
100
103
  import { parseArgs } from "util";
101
104
  var BODY_OPTION = { body: { type: "string" } };
102
105
  function parse(argv, options) {
@@ -112,7 +115,7 @@ function baseBody(values) {
112
115
  let text = raw;
113
116
  if (raw.startsWith("@")) {
114
117
  try {
115
- text = readFileSync(raw.slice(1), "utf8");
118
+ text = readFileSync2(raw.slice(1), "utf8");
116
119
  } catch (error) {
117
120
  throw usageError(`Cannot read --body file: ${error.message}`);
118
121
  }
@@ -513,6 +516,12 @@ function deviceLabel() {
513
516
  return hostname().replace(/[^\w .@-]/g, "").slice(0, 64);
514
517
  }
515
518
 
519
+ // src/guide.ts
520
+ import { readFile as readFile2 } from "fs/promises";
521
+ function readGuide() {
522
+ return readFile2(new URL("../AGENT_GUIDE.md", import.meta.url), "utf8");
523
+ }
524
+
516
525
  // src/cli.ts
517
526
  var HELP = `fortrip ${VERSION} \u2014 search, price and book Fortrip hotels
518
527
 
@@ -526,6 +535,7 @@ Commands:
526
535
  hotel <hotelId> Static hotel detail [--with-rooms]
527
536
  rates Live rooms and rates (CNY) for up to 15 hotels
528
537
  book Create an order and get a payment link
538
+ skill Print the guide for AI agents (Markdown)
529
539
 
530
540
  search: --query <text> | --city-code <n> | --near <lat,lng,km>
531
541
  --check-in YYYY-MM-DD --check-out YYYY-MM-DD
@@ -562,6 +572,11 @@ async function run(argv) {
562
572
  `);
563
573
  return void 0;
564
574
  }
575
+ if (command === "skill") {
576
+ parse(rest, {});
577
+ process.stdout.write(await readGuide());
578
+ return void 0;
579
+ }
565
580
  const apiUrl = resolveApiUrl();
566
581
  const log = (line) => process.stderr.write(`${line}
567
582
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fortrip",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Search, price and book Fortrip hotels from the terminal or an AI agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "dist",
11
11
  "skills",
12
+ "AGENT_GUIDE.md",
12
13
  "README.md"
13
14
  ],
14
15
  "engines": {
@@ -5,125 +5,16 @@ description: Search hotels, compare live room rates and create a hotel booking w
5
5
 
6
6
  # Fortrip hotels
7
7
 
8
- Fortrip sells hotel rooms at supplier net prices. The
9
- `fortrip` CLI (npm package `fortrip`) calls the Fortrip API as the
10
- signed-in user and prints JSON.
8
+ Before anything else, run:
11
9
 
12
- ## When to use
10
+ ```bash
11
+ npx -y fortrip@latest skill
12
+ ```
13
13
 
14
- - The user wants hotels in a city, near a point, or by name, for given dates.
15
- - The user wants live rooms, meal plans, cancellation terms or prices for
16
- specific hotels.
17
- - The user has picked a rate and wants to book it (you return a payment link;
18
- the user pays in their browser).
14
+ It prints the current instructions for the Fortrip CLI: commands, call order,
15
+ login, and what to show the user before giving a payment link. Follow them.
16
+ They ship with each CLI release, so this file stays short and doesn't go out
17
+ of date.
19
18
 
20
- Not for flights, activities, or changing/cancelling an existing booking.
21
-
22
- ## Setup
23
-
24
- Run commands with `npx -y fortrip <command>` (or `fortrip <command>`
25
- if installed globally).
26
-
27
- When the user first asks about hotels, run `fortrip whoami` before asking
28
- follow-up questions, so a login can happen while they answer.
29
-
30
- If a command exits with code **3** and stderr has `loginUrl`, show it to the
31
- user as a link together with `userCode`, e.g. "Sign in to Fortrip: <loginUrl>
32
- (code BCDF-GHJK) and tell me when you're done." When they confirm, rerun the
33
- same command; it finishes the login itself. Don't run `fortrip login` — it
34
- waits for up to 10 minutes. If the rerun gives a new `loginUrl`, the old one
35
- expired or was denied; show the new one.
36
-
37
- If exit 3 has no `loginUrl`, the saved token was revoked: run `fortrip logout`,
38
- then rerun the command to get a link.
39
-
40
- In CI, the user can set `FORTRIP_TOKEN` instead. Never ask the user to paste a
41
- token into the chat.
42
-
43
- ## Exit codes
44
-
45
- | Code | Meaning | What to do |
46
- |---|---|---|
47
- | 0 | Success; JSON on stdout | Continue |
48
- | 1 | Other error (supplier error, rate no longer bookable, network) | Read `error` on stderr; re-run `rates` if a rate went stale |
49
- | 2 | Invalid arguments | Fix the flags; stderr `issues` lists the bad fields |
50
- | 3 | Not logged in | Show `loginUrl` to the user, then rerun (above) |
51
- | 4 | Rate limited | Wait `retryAfterSeconds`, then retry once |
52
-
53
- ## Recommended order
54
-
55
- 1. **Search** — find candidates and indicative prices:
56
-
57
- ```bash
58
- fortrip search --query "Bangkok" --check-in 2026-11-10 --check-out 2026-11-12 \
59
- --star-min 4 --sort price-asc --page-size 10
60
- ```
61
-
62
- Use `--near lat,lng,radiusKm` for "near X", `--city-code` when known.
63
- `--facility` names must match the catalogue exactly (e.g. `Swimming pool`,
64
- `Parking`), otherwise nothing matches. Prices here (`price.nightlyFrom`,
65
- `totalFrom`, CNY) are **indicative floors**, not quotes — say so.
66
-
67
- 2. **Hotel detail** (optional) — description, policies, facilities, photos:
68
-
69
- ```bash
70
- fortrip hotel 266000 [--with-rooms]
71
- ```
72
-
73
- 3. **Rates** — live rooms and prices for up to 15 hotels:
74
-
75
- ```bash
76
- fortrip rates --hotel 266000 --hotel 266001 --check-in 2026-11-10 \
77
- --check-out 2026-11-12 --rooms 1 --adults 2 [--child-age 6] \
78
- [--nationality CN] [--board BB] [--free-cancellation]
79
- ```
80
-
81
- This call takes about 15 seconds. `totalPrice` is CNY at a fixed exchange
82
- rate. Present each option with meal plan (`boardCode`: RO room only,
83
- BB breakfast, HB half board, FB full board, AI all inclusive),
84
- `freeCancelUntil` / `refundable`, `isInstantConfirm`, and any `fees`
85
- (taxes paid at the hotel, in their own currency).
86
-
87
- 4. **Book** — only after the user picks one rate and gives guest details:
88
-
89
- ```bash
90
- fortrip book --hotel 266000 --room-id 9191658 --rate-plan "<ratePlanCode>" \
91
- --check-in 2026-11-10 --check-out 2026-11-12 --rooms 1 --adults 2 \
92
- --guest "John Smith" --contact-name "John Smith" \
93
- --contact-phone "+8613800000000" [--contact-email john@example.com]
94
- ```
95
-
96
- `--hotel`, `--room-id`, `--rate-plan`, dates, `--rooms`, `--adults` and
97
- `--child-age` must be exactly the values used in `rates`. Guest names must
98
- be in Latin letters as on the passport ("First Last"; last word is the last
99
- name; `"First Last:2"` puts the guest in room 2). Each room needs at least
100
- one guest. `--special-requests` must be in English and isn't guaranteed.
101
-
102
- ## Before giving the payment link
103
-
104
- `book` returns `paymentUrl`, `amount`, `currency`, `terms`, `statusUrl` and
105
- `orderUrl`. Before you hand over `paymentUrl`:
106
-
107
- 1. **Show the `terms`** and get the user's confirmation: meal plan
108
- (`boardCode`, `boardCount`), cancellation (`refundable`, `freeCancelUntil`,
109
- the `cancellation` ladder), confirmation (`isInstantConfirm`,
110
- `confirmByMins`) and `payAtHotelFees`.
111
- 2. **Explain the amount.** `rates` showed CNY, but the charge is `amount` in
112
- `currency` (usually USD), and the Stripe page may display the user's local
113
- currency at Stripe's rate. The amount on the payment page is what will be
114
- charged; the CNY figure was a reference.
115
- 3. The link is for **this user only**: whoever has it can pay. Don't post it
116
- in shared channels, logs or tickets. It expires after about 3 hours 45
117
- minutes; call `book` again with the same details for a fresh one.
118
-
119
- After paying, the user lands on `statusUrl`, which shows confirmation progress
120
- without signing in. `orderUrl` is the signed-in order page.
121
-
122
- ## Errors from `book`
123
-
124
- - `409` with `agentOrderId`: this exact stay is already paid or completed —
125
- show `orderUrl`, don't book again.
126
- - `409` with `changes`: price or terms changed — re-run `rates` and confirm
127
- again with the user.
128
- - `422`: the booking was refused (guest names, too many attempts, bookings
129
- paused, or a preview deployment). Read `error`.
19
+ Needs Node.js 22 or newer. If the command fails, tell the user why instead of
20
+ guessing the CLI's flags.