🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@voyagier/cli

Package Overview
Dependencies
Maintainers
2
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@voyagier/cli - npm Package Compare versions

Comparing version
2.12.0
to
2.13.0
+23
-1
AGENT.md

@@ -168,2 +168,23 @@ # Voyagier CLI — Agent Reference

### Loyalty programs (optional, best-effort at checkout)
Travellers can carry loyalty programs; checkout applies them automatically.
**A booking never fails or blocks because of loyalty** — if a program can't be
applied it is silently skipped, so no error ≠ guaranteed credit.
- **Frequent flyer:** `--frequent-flyer AIRLINE:NUMBER` (repeatable, e.g.
`--frequent-flyer DL:1234567`). The member number is sent to the airline **verbatim**
— pass it exactly as issued. Applied per matching passenger on flight checkout.
- **Hotel:** `--hotel-loyalty CHAIN:NUMBER` (repeatable, e.g.
`--hotel-loyalty HI:12345678`). The member number is **digits only — do NOT
include the chain code prefix** (checkout builds the id as chain + number; a
prefixed number would double the chain and never apply). Applied for the
**primary guest only**, and only when the program's chain matches the booked
property's chain.
- On `travellers update`: `--frequent-flyer`/`--hotel-loyalty` **replace** the full
list; `--clear-frequent-flyer`/`--clear-hotel-loyalty` remove all programs. Omitting
the flags leaves programs untouched.
- Numbers are encrypted at rest server-side; reads only ever return the
code + `last4` — there is no way to read a stored number back.
---

@@ -404,4 +425,5 @@

voyagier travellers add --plan <id> --first <f> --last <l> --type Adult|Child|Infant --json
voyagier travellers add --plan <id> --first <f> --last <l> --frequent-flyer DL:1234567 --hotel-loyalty HI:12345678 --json
voyagier travellers list --plan <id> --json
voyagier travellers update <travellerId> [...] --json
voyagier travellers update <travellerId> [...] --json # incl. --frequent-flyer / --hotel-loyalty (replace) and --clear-frequent-flyer / --clear-hotel-loyalty
voyagier travellers remove <travellerId> --json

@@ -408,0 +430,0 @@ ```

@@ -14,2 +14,65 @@ import { printPlanFooter } from "../plan-footer.js";

}
function collect(value, previous) {
return [...previous, value];
}
function maskLoyaltyValue(value) {
return value.length > 4 ? `••••${value.slice(-4)}` : "••••";
}
function requirePassportNumberWithMetadata(opts) {
if (opts.passportNumber)
return;
const provided = [
opts.passportCountry && "--passport-country",
opts.passportNationality && "--passport-nationality",
opts.passportExpiry && "--passport-expiry",
].filter(Boolean);
if (provided.length > 0) {
throw new CliError(CliErrorCode.VALIDATION, `${provided.join(", ")} require${provided.length === 1 ? "s" : ""} --passport-number — passport metadata is ignored without it`);
}
}
function parseLoyalty(raw, kind) {
const label = kind === "air" ? "--frequent-flyer" : "--hotel-loyalty";
const example = kind === "air" ? "DL:1234567" : "HI:12345678";
const sep = raw.indexOf(":");
if (sep === -1) {
throw new CliError(CliErrorCode.VALIDATION, `${label} expects CODE:NUMBER (e.g. ${example}), got: "${maskLoyaltyValue(raw)}"`);
}
const code = raw.slice(0, sep).trim().toUpperCase();
const membershipNumber = raw.slice(sep + 1).trim();
const codePattern = kind === "air" ? /^[A-Z0-9]{2}$/ : /^[A-Z]{2}$/;
if (!codePattern.test(code)) {
const kindWord = kind === "air" ? "airline code" : "chain code";
throw new CliError(CliErrorCode.VALIDATION, `${label}: ${kindWord} must be exactly 2 ${kind === "air" ? "characters" : "letters"} (e.g. ${example.split(":")[0]}), got: "${code}"`);
}
if (membershipNumber.length === 0) {
throw new CliError(CliErrorCode.VALIDATION, `${label}: missing member number after "${code}:"`);
}
if (kind === "hotel" && !/^\d+$/.test(membershipNumber)) {
const hint = membershipNumber.toUpperCase().startsWith(code)
? ` — do not include the chain code, checkout prefixes "${code}" automatically`
: "";
throw new CliError(CliErrorCode.VALIDATION, `${label}: member number must be digits only${hint}, got: "${maskLoyaltyValue(membershipNumber)}"`);
}
return { code, membershipNumber };
}
function toAirLoyaltyInput(values) {
return values.map((v) => {
const p = parseLoyalty(v, "air");
return { airlineCode: p.code, membershipNumber: p.membershipNumber };
});
}
function toHotelLoyaltyInput(values) {
return values.map((v) => {
const p = parseLoyalty(v, "hotel");
return { chainCode: p.code, membershipNumber: p.membershipNumber };
});
}
function loyaltySummary(t) {
const bits = [];
for (const p of t.frequentFlyerPrograms ?? [])
bits.push(`✈ ${p.airlineCode}${p.last4 ? ` ••••${p.last4}` : ""}`);
for (const p of t.hotelLoyaltyPrograms ?? [])
bits.push(`🏨 ${p.chainCode}${p.last4 ? ` ••••${p.last4}` : ""}`);
return bits.length > 0 ? bits.join(" · ") : undefined;
}
export function registerTravellerCommands(program) {

@@ -32,2 +95,4 @@ const travellers = program.command("travellers").description("Manage trip plan travellers");

.option("--passport-expiry <date>", "Passport expiration (YYYY-MM)")
.option("--frequent-flyer <program>", "Frequent-flyer program AIRLINE:NUMBER (repeatable, e.g. DL:1234567)", collect, [])
.option("--hotel-loyalty <program>", "Hotel loyalty program CHAIN:NUMBER — member number digits only, no chain prefix (repeatable, e.g. HI:12345678)", collect, [])
.option("--self", "Auto-fill from your saved profile (voyagier auth setup)")

@@ -132,2 +197,3 @@ .option("--json", "Output raw JSON")

}
requirePassportNumberWithMetadata(opts);
if (opts.passportNumber) {

@@ -143,2 +209,6 @@ const passportInput = {

}
if (opts.frequentFlyer.length > 0)
input.frequentFlyerPrograms = toAirLoyaltyInput(opts.frequentFlyer);
if (opts.hotelLoyalty.length > 0)
input.hotelLoyaltyPrograms = toHotelLoyaltyInput(opts.hotelLoyalty);
const data = await graphql(CREATE_TRAVELLER, { tripPlanId: opts.plan, input });

@@ -161,2 +231,5 @@ const t = data.createTripPlanTraveller;

console.log(chalk.dim(` Gender: ${t.gender}`));
const loyalty = loyaltySummary(t);
if (loyalty)
console.log(chalk.dim(` Loyalty: ${loyalty}`));
await printPlanFooter(opts.plan);

@@ -223,2 +296,5 @@ }

console.log(chalk.dim(` ${details.join(" · ")}`));
const loyalty = loyaltySummary(t);
if (loyalty)
console.log(chalk.dim(` Loyalty: ${loyalty}`));
const missing = [];

@@ -276,2 +352,6 @@ if (!t.dateOfBirth)

.option("--passport-expiry <date>", "Passport expiration (YYYY-MM)")
.option("--frequent-flyer <program>", "Replace frequent-flyer programs with AIRLINE:NUMBER (repeatable, e.g. DL:1234567)", collect, [])
.option("--hotel-loyalty <program>", "Replace hotel loyalty programs with CHAIN:NUMBER — member number digits only, no chain prefix (repeatable, e.g. HI:12345678)", collect, [])
.option("--clear-frequent-flyer", "Remove all frequent-flyer programs")
.option("--clear-hotel-loyalty", "Remove all hotel loyalty programs")
.option("--json", "Output raw JSON")

@@ -301,2 +381,3 @@ .action(async (id, opts) => {

}
requirePassportNumberWithMetadata(opts);
if (opts.passportNumber) {

@@ -312,4 +393,18 @@ const passportInput = {

}
if (opts.frequentFlyer.length > 0 && opts.clearFrequentFlyer) {
throw new CliError(CliErrorCode.VALIDATION, "--frequent-flyer and --clear-frequent-flyer are mutually exclusive");
}
if (opts.hotelLoyalty.length > 0 && opts.clearHotelLoyalty) {
throw new CliError(CliErrorCode.VALIDATION, "--hotel-loyalty and --clear-hotel-loyalty are mutually exclusive");
}
if (opts.clearFrequentFlyer)
input.frequentFlyerPrograms = [];
else if (opts.frequentFlyer.length > 0)
input.frequentFlyerPrograms = toAirLoyaltyInput(opts.frequentFlyer);
if (opts.clearHotelLoyalty)
input.hotelLoyaltyPrograms = [];
else if (opts.hotelLoyalty.length > 0)
input.hotelLoyaltyPrograms = toHotelLoyaltyInput(opts.hotelLoyalty);
if (Object.keys(input).length === 0) {
fatal("Nothing to update. Provide at least one of: --first, --last, --email, --dob, --gender, --type, --phone, --passport-number");
fatal("Nothing to update. Provide at least one of: --first, --last, --email, --dob, --gender, --type, --phone, --passport-number, --passport-country, --passport-nationality, --passport-expiry, --frequent-flyer, --hotel-loyalty, --clear-frequent-flyer, --clear-hotel-loyalty");
}

@@ -327,2 +422,5 @@ const data = await graphql(UPDATE_TRAVELLER, { id, input });

console.log(chalk.dim(` Email: ${t.email}`));
const loyalty = loyaltySummary(t);
if (loyalty)
console.log(chalk.dim(` Loyalty: ${loyalty}`));
}

@@ -329,0 +427,0 @@ catch (err) {

@@ -46,3 +46,9 @@ import { z } from "zod";

export function buildAddTravellerArgs(i) {
return ["travellers", "add", "--plan", i.plan_id, "--first", i.first, "--last", i.last, "--type", i.type ?? "Adult", "--json"];
const args = ["travellers", "add", "--plan", i.plan_id, "--first", i.first, "--last", i.last, "--type", i.type ?? "Adult"];
for (const p of i.frequent_flyer ?? [])
args.push("--frequent-flyer", p);
for (const p of i.hotel_loyalty ?? [])
args.push("--hotel-loyalty", p);
args.push("--json");
return args;
}

@@ -157,3 +163,3 @@ export function buildSearchFlightsArgs(i) {

name: "add_traveller",
description: "Add a traveller to a trip plan. Travellers are required before search. Gender and date of birth are required at flight checkout; passport data hard-gates international reservations (set those via the CLI travellers update later).",
description: "Add a traveller to a trip plan. Travellers are required before search. Gender and date of birth are required at flight checkout; passport data hard-gates international reservations (set those via the CLI travellers update later). Loyalty programs are applied at checkout best-effort — a booking never fails because of them.",
timeoutMs: T.short,

@@ -165,2 +171,4 @@ inputSchema: {

type: z.string().optional().describe("Traveller type: Adult | Child | Infant. Default Adult."),
frequent_flyer: z.array(z.string()).optional().describe('Frequent-flyer programs as "AIRLINE:NUMBER", e.g. ["DL:1234567"]. Member number exactly as the airline issued it.'),
hotel_loyalty: z.array(z.string()).optional().describe('Hotel loyalty programs as "CHAIN:NUMBER", e.g. ["HI:12345678"]. Member number is digits only — do NOT include the chain code prefix.'),
},

@@ -167,0 +175,0 @@ buildArgs: (i) => buildAddTravellerArgs(i),

@@ -326,2 +326,4 @@ export const GET_CART = `

id firstName lastName email dateOfBirth gender declaredTravellerType
frequentFlyerPrograms { airlineCode last4 }
hotelLoyaltyPrograms { chainCode last4 }
}

@@ -342,2 +344,4 @@ }

passport { last4 issueCountry }
frequentFlyerPrograms { airlineCode last4 }
hotelLoyaltyPrograms { chainCode last4 }
}

@@ -360,2 +364,4 @@ }

id firstName lastName email dateOfBirth gender declaredTravellerType
frequentFlyerPrograms { airlineCode last4 }
hotelLoyaltyPrograms { chainCode last4 }
}

@@ -362,0 +368,0 @@ }

+2
-1
{
"name": "@voyagier/cli",
"version": "2.12.0",
"version": "2.13.0",
"mcpName": "com.voyagier/cli",
"description": "Agent-ready travel CLI — search, plan, quote, and book real trips (flights, hotels, activities) against the Voyagier platform. Built for AI agents: --json everywhere, uniform error codes, price-gated checkout, printable agent reference (voyagier agent-docs).",

@@ -5,0 +6,0 @@ "type": "module",

@@ -70,2 +70,4 @@ ---

voyagier travellers add --plan <PLAN_ID> --first John --last Smith --type Adult --json
# Optional loyalty (applied at checkout best-effort — never blocks a booking):
# --frequent-flyer DL:1234567 (FF number verbatim) · --hotel-loyalty HI:12345678 (digits only, NO chain prefix)

@@ -72,0 +74,0 @@ # 4. Search → select. search --json returns a COMPACT envelope: