@voyagier/cli
Advanced tools
+3
-1
@@ -277,2 +277,4 @@ # Voyagier CLI — Agent Reference | ||
| Argument-parse failures (unknown option, missing required option/argument, invalid argument value) also honor this envelope **when you pass `--json`**: they emit `{ "error": true, "code": "VALIDATION", "message": ... }` on stdout and exit 1. Without `--json` those failures print a bare `error: ...` line to stderr instead — so always drive the CLI with `--json` if you parse stdout. | ||
| ### Error codes (what the CLI actually emits today) | ||
@@ -444,3 +446,3 @@ | ||
| voyagier travellers list --plan <id> --json | ||
| voyagier travellers update <travellerId> [...] --json # incl. --frequent-flyer / --hotel-loyalty (replace) and --clear-frequent-flyer / --clear-hotel-loyalty | ||
| voyagier travellers update <travellerId> [--plan <id>] [...] --json # --plan is optional context (the traveller id already identifies the record); incl. --frequent-flyer / --hotel-loyalty (replace) and --clear-frequent-flyer / --clear-hotel-loyalty | ||
| voyagier travellers remove <travellerId> --json | ||
@@ -447,0 +449,0 @@ ``` |
| import { Command } from "commander"; | ||
| import { CliErrorCode } from "./errors.js"; | ||
| import { registerAuthCommands } from "./commands/auth.js"; | ||
@@ -27,2 +28,21 @@ import { registerChatCommands } from "./commands/chat.js"; | ||
| import { registerMcpCommand } from "./commands/mcp.js"; | ||
| export function routeParseErrorsToJson(cmd) { | ||
| cmd.configureOutput({ | ||
| outputError: (str, write) => { | ||
| if (argvRequestsJson(process.argv)) { | ||
| const message = str.replace(/\n+$/, ""); | ||
| process.stdout.write(JSON.stringify({ error: true, code: CliErrorCode.VALIDATION, message }, null, 2) + "\n"); | ||
| } | ||
| else { | ||
| write(str); | ||
| } | ||
| }, | ||
| }); | ||
| cmd.commands.forEach(routeParseErrorsToJson); | ||
| } | ||
| export function argvRequestsJson(argv) { | ||
| const terminator = argv.indexOf("--"); | ||
| const options = terminator === -1 ? argv : argv.slice(0, terminator); | ||
| return options.includes("--json"); | ||
| } | ||
| export function buildProgram(version) { | ||
@@ -69,3 +89,4 @@ const program = new Command(); | ||
| registerMcpCommand(program); | ||
| routeParseErrorsToJson(program); | ||
| return program; | ||
| } |
@@ -11,3 +11,3 @@ import chalk from "chalk"; | ||
| import { CliError } from "../errors.js"; | ||
| import { DOCTOR_PING } from "../queries.js"; | ||
| import { DOCTOR_PING, DOCTOR_IDENTITY } from "../queries.js"; | ||
| import * as queries from "../queries.js"; | ||
@@ -54,2 +54,12 @@ export const PERIPHERAL_OP_PATTERN = /(^|_)(PLACES?|COMMENTS?|BOOKING_RECORDS?)(_|$)/; | ||
| } | ||
| async function resolveAuthIdentity() { | ||
| const ctx = getUserContext(); | ||
| try { | ||
| const { me } = await graphql(DOCTOR_IDENTITY); | ||
| return me?.email ?? me?.name ?? ctx?.email ?? ctx?.name ?? "unknown"; | ||
| } | ||
| catch { | ||
| return ctx?.email ?? ctx?.name ?? "unknown"; | ||
| } | ||
| } | ||
| async function checkAuth() { | ||
@@ -65,9 +75,2 @@ if (!credentialsExist()) { | ||
| await graphql(DOCTOR_PING); | ||
| const ctx = getUserContext(); | ||
| const who = ctx?.email ?? ctx?.name ?? "unknown"; | ||
| return { | ||
| name: "auth", | ||
| status: "PASS", | ||
| message: `Authenticated as ${who}`, | ||
| }; | ||
| } | ||
@@ -88,2 +91,8 @@ catch (err) { | ||
| } | ||
| const who = await resolveAuthIdentity(); | ||
| return { | ||
| name: "auth", | ||
| status: "PASS", | ||
| message: `Authenticated as ${sanitizeExternalText(who)}`, | ||
| }; | ||
| } | ||
@@ -90,0 +99,0 @@ const DOCTOR_PROBE_TIMEOUT_MS = 5000; |
@@ -7,3 +7,3 @@ import { printPlanFooter } from "../plan-footer.js"; | ||
| import { loadSearchState, clearSearchState, isSearchStateStale } from "../state.js"; | ||
| import { deriveBaseUrl, shellArg } from "../utils.js"; | ||
| import { deriveBaseUrl, shellArg, validateId } from "../utils.js"; | ||
| import { clientPlanUrl, planUrls } from "../plan-urls.js"; | ||
@@ -212,13 +212,15 @@ import { GET_PLAN_STATUS } from "../queries.js"; | ||
| } | ||
| if (opts.selectionId || opts.optionId) { | ||
| if (!opts.selectionId || !opts.optionId) { | ||
| if (opts.selectionId !== undefined || opts.optionId !== undefined) { | ||
| if (opts.selectionId === undefined || opts.optionId === undefined) { | ||
| throw new CliError(CliErrorCode.VALIDATION, "Direct mode requires BOTH --selection-id and --option-id."); | ||
| } | ||
| const selectionId = validateId(opts.selectionId, "--selection-id"); | ||
| const optionId = validateId(opts.optionId, "--option-id"); | ||
| try { | ||
| if (!opts.json) | ||
| progress("Selecting option..."); | ||
| const result = await setSelectedOption(opts.selectionId, opts.optionId, opts); | ||
| const name = result.parentOption?.name ?? opts.optionId; | ||
| const result = await setSelectedOption(selectionId, optionId, opts); | ||
| const name = result.parentOption?.name ?? optionId; | ||
| const forScope = scopeLabel(opts); | ||
| const waitOutcome = opts.wait ? await runPickWait(opts.selectionId, opts.optionId, opts) : undefined; | ||
| const waitOutcome = opts.wait ? await runPickWait(selectionId, optionId, opts) : undefined; | ||
| if (opts.json) { | ||
@@ -225,0 +227,0 @@ jsonOutput({ |
@@ -342,2 +342,3 @@ import { printPlanFooter } from "../plan-footer.js"; | ||
| .description("Update a traveller's details") | ||
| .option("--plan <id>", "Trip plan ID (optional context; the traveller id already identifies the record)") | ||
| .option("--first <name>", "First name") | ||
@@ -344,0 +345,0 @@ .option("--last <name>", "Last name") |
+2
-2
| #!/usr/bin/env node | ||
| import { readFileSync } from "fs"; | ||
| import { buildProgram } from "./build-program.js"; | ||
| import { buildProgram, argvRequestsJson } from "./build-program.js"; | ||
| import { trackCommand, getTraceId, isTelemetryEnabled, telemetryErrorCode } from "./telemetry.js"; | ||
@@ -58,3 +58,3 @@ import { gracefulExit } from "./exit.js"; | ||
| if (err instanceof CliError) { | ||
| const isJson = process.argv.includes("--json"); | ||
| const isJson = argvRequestsJson(process.argv); | ||
| if (isJson) { | ||
@@ -61,0 +61,0 @@ const payload = { error: true, code: err.code, message: err.message }; |
+5
-0
@@ -652,2 +652,7 @@ export const GET_CART = ` | ||
| `; | ||
| export const DOCTOR_IDENTITY = ` | ||
| query DoctorIdentity { | ||
| me { email name } | ||
| } | ||
| `; | ||
| export const GET_TRIP_PLAN_EVENTS = ` | ||
@@ -654,0 +659,0 @@ query TripPlanEvents($id: String!) { |
+8
-0
@@ -98,2 +98,10 @@ import chalk from "chalk"; | ||
| } | ||
| export function validateId(value, flagName) { | ||
| const trimmed = value.trim(); | ||
| const lowered = trimmed.toLowerCase(); | ||
| if (trimmed === "" || lowered === "null" || lowered === "undefined") { | ||
| throw new CliError(CliErrorCode.VALIDATION, `Invalid ${flagName}: "${value}". Pass a real id (an empty or "null"/"undefined" value usually means a broken script or jq extraction).`); | ||
| } | ||
| return trimmed; | ||
| } | ||
| export function subSelectionLabel(type) { | ||
@@ -100,0 +108,0 @@ switch (type) { |
+1
-1
| { | ||
| "name": "@voyagier/cli", | ||
| "version": "2.19.0", | ||
| "version": "2.19.1", | ||
| "mcpName": "com.voyagier/cli", | ||
@@ -5,0 +5,0 @@ "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).", |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1283222
0.18%20351
0.23%