Stateless Go CLI for the Lynx Reservations (www.lynx-reservations.com) travel agency system. Use this skill whenever the user needs to search files, retrieve...
Stateless Go CLI that replicates every tool from lynx-mcp-server as standalone commands — no daemon, no SSE, no Bearer token.
bash
cd lynx-travel-agent/lynx-skill
go build -o bin/lynx .
# optional: put it in PATH
cp bin/lynx ~/bin/lynx # or /usr/local/bin/
The binary reads these environment variables. All three are required:
VariableDescriptionLYNX_USERNAMELynx usernameLYNX_PASSWORDLynx passwordLYNX_COMPANY_CODECompany code
#CommandDescription1file-search-by-party-nameSearch files by customer last name2file-search-by-file-referenceSearch files by Lynx file reference3retrieve-itineraryGet detailed itinerary for a file4retrieve-file-documentsGet documents for a transaction5attachment-uploadUpload a file attachment from disk6file-document-saveSave document at the file level7transaction-document-saveSave document at the transaction level
All commands output JSON to stdout. Errors go to stderr. Exit code 0 = success, 1 = failure.
file-search-by-party-name#Search for files by the customer's last name.
text
lynx file-search-by-party-name --party-name=LASTNAME
Flags:
FlagTypeRequiredDescription--party-name`stringyesCustomer last name to search for
Output:
json
{
"count": 1,
"results": [
{
"companyCode": "XX",
"clientIdentifier": "12345",
"clientReference": "REF-ABC",
"currency": "EUR",
"fileIdentifier": "12345",
"fileReference": "FTXXXXXXXXX",
"partyName": "SMITH",
"status": "Active",
"travelDate": "2026-06-15"
}
]
}
Examples:
bash
lynx file-search-by-party-name --party-name=Smith
lynx file-search-by-party-name --party-name=Smi
file-search-by-file-reference#Search for a file using its Lynx file reference (e.g. FTXXXXXXXXX).
text
lynx file-search-by-file-reference --file-reference=FTXXXXXXXXX
Flags:
FlagTypeRequiredDescription--file-referencestringyesLynx file reference
Output: Same JSON structure as file-search-by-party-name ( count + results array).
Example:
bash
lynx file-search-by-file-reference --file-reference=FT16476987
retrieve-itinerary#Get the full itinerary for a given file.
text
lynx retrieve-itinerary --file-identifier=ID
Flags:
FlagShortTypeRequiredDefaultDescription--file-identifier``-fstringyes—Numeric file identifier (from search results)--show-cancelled``-cboolnofalseInclude cancelled bookings in the results
Output:
json
{
"type": "...",
"partyName": "SMITH",
"fileReference": "FTXXXXXXXXX",
"fileIdentifier": "12345",
"clientIdentifier": "12345",
"agentReference": "AGT001",
"itineraryCount": 2,
"itineraries": [
{
"voucherIdentifier": "V123",
"date": "15 Jun 2026",
"transactionIdentifier": "btx12345",
"supplier": "Hotel ABC",
"status": "Confirmed",
"confirmationNumber": "CONF-001",
"location": "Paris"
}
]
}
Examples:
bash
# Active bookings only (default)
lynx retrieve-itinerary --file-identifier=16476987
# Include cancelled bookings
lynx retrieve-itinerary --file-identifier=16476987 --show-cancelled
retrieve-file-documents#Retrieve all documents associated with a specific transaction within a file.
text
lynx retrieve-file-documents \
--file-identifier=ID \
--transaction-identifier=TXID
Flags:
FlagTypeRequiredDescription--file-identifierstringyesNumeric file identifier--transaction-identifierstringyesTransaction identifier (from itinerary)
Output:
json
{
"count": 1,
"results": [
{
"fileIdentifier": "12345",
"transactionIdentifier": "btx12345",
"documentIdentifier": "doc_001",
"documentName": "Invoice",
"documentType": "SUPP",
"content": "<span>Invoice details</span>",
"attachmentUrl": "/documents/file/f16476987/d20250709064401.pdf"
}
]
}
Example:
bash
lynx retrieve-file-documents \
--file-identifier=16476987 \
--transaction-identifier=btx12345
attachment-upload#Upload a file (PDF, image, etc.) from disk and associate it with a document.
text
lynx attachment-upload \
--identifier=FILEID \
--file=/path/to/document.pdf
Flags:
FlagTypeRequiredDescription--identifierstringyesUnique identifier for the attachment--filestringyesPath to the file on disk
Output:
json
{
"attachmentUrl": "/documents/file/f16476987/d20250709064401.pdf"
}
The returned attachmentUrl can be passed to file-document-save or transaction-document-save as --attachment-url.
Examples:
bash
lynx attachment-upload --identifier=f16476987 --file=./documents/invoice.pdf
lynx attachment-upload --identifier=f16476987 --file=./documents/receipt.jpg
file-document-save#Save (create or update) a document at the file level.
text
lynx file-document-save \
--file-identifier=ID \
--name=NAME \
--content=CONTENT \
--type=TYPE \
[--attachment-url=URL]
Flags:
FlagTypeRequiredDescription--file-identifierstringyesNumeric file identifier--namestringyesDocument name--contentstringyesDocument content (plain text or HTML)--typestringyesDocument type code — see valid values below--attachment-urlstringnoAttachment URL from attachment-upload
Valid --type values:
CodeDisplay NameSUPPSupplier CommunicationCLINTAgent CommunicationGENGeneralINVAccounting CommunicationAFTERAfter Hours Phone InquiryEMAILEmailFLIGHFlight InformationBOOKIMail Merge DocumentPHONEPhone ConversationCLCOMTravelling Client Communication
⚠️ Only these exact codes are accepted. Passing an invalid code will be rejected before the request is sent. The
documentTypefield returned byretrieve-file-documentsis the internal code (e.g.SUPP), which can be passed directly to--type.
Output:
json
{
"status": "ok"
}
Examples:
bash
lynx file-document-save \
--file-identifier=16476987 \
--name="Note" \
--content="Customer requested change of date" \
--type=SUPP
lynx file-document-save \
--file-identifier=16476987 \
--name="Invoice" \
--content="<span>Invoice #1234 — 1500.00 EUR</span>" \
--type=INV \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
transaction-document-save#Save (create or update) a document at the transaction level.
text
lynx transaction-document-save \
--file-identifier=ID \
--transaction-identifier=TXID \
--name=NAME \
--content=CONTENT \
--type=TYPE \
[--attachment-url=URL]
Flags:
FlagTypeRequiredDescription--file-identifierstringyesNumeric file identifier--transaction-identifierstringyesTransaction identifier--namestringyesDocument name--contentstringyesDocument content (plain text or HTML)--typestringyesDocument type code — see valid values in file-document-save``--attachment-urlstringnoAttachment URL from attachment-upload
Output:
json
{
"status": "ok"
}
Examples:
bash
lynx transaction-document-save \
--file-identifier=16476987 \
--transaction-identifier=btx12345 \
--name="Voucher" \
--content="Confirmation voucher for hotel booking" \
--type=SUPP
lynx transaction-document-save \
--file-identifier=16476987 \
--transaction-identifier=btx12345 \
--name="Invoice" \
--content="<b>Paid: 500 EUR</b>" \
--type=INV \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
bash
# Step 1 — Search by customer last name
lynx file-search-by-party-name --party-name=Smith
# Step 2 — From the results, grab the fileIdentifier (e.g. 16476987)
# and inspect the itinerary
lynx retrieve-itinerary --file-identifier=16476987
# Step 3 — From the itinerary, get a transactionIdentifier (e.g. btx12345)
# and save a note at transaction level
lynx transaction-document-save \
--file-identifier=16476987 \
--transaction-identifier=btx12345 \
--name="Note" \
--content="Customer requested date change" \
--type=SUPP
bash
# Step 1 — Search file by reference
lynx file-search-by-file-reference --file-reference=FT16476987
# Step 2 — Upload the invoice PDF from disk
lynx attachment-upload \
--identifier=f16476987 \
--file=./invoices/invoice_1234.pdf
# Step 3 — Save the document at file level with the attachment URL
lynx file-document-save \
--file-identifier=16476987 \
--name="Invoice #1234" \
--content="<span>Invoice 1234 — 1500 EUR</span>" \
--type=INVOICE \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
bash
# Step 1 — Retrieve all documents for a transaction
lynx retrieve-file-documents \
--file-identifier=16476987 \
--transaction-identifier=btx12345
# Step 2 — Pipe the JSON output through jq to inspect document names
lynx retrieve-file-documents \
--file-identifier=16476987 \
--transaction-identifier=btx12345 \
| jq '.results[].documentName'
# Step 3 — Upload a payment receipt
lynx attachment-upload \
--identifier=f16476987 \
--file=./receipts/payment_receipt.pdf
# Step 4 — Save the receipt at transaction level
lynx transaction-document-save \
--file-identifier=16476987 \
--transaction-identifier=btx12345 \
--name="Payment Receipt" \
--content="<b>Paid: 500 EUR on 01-Jun-2026</b>" \
--type=RECEIPT \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
bash
# 1. Find the file
lynx file-search-by-party-name --party-name=Smith
# 2. Get the itinerary (find transaction identifiers)
lynx retrieve-itinerary --file-identifier=16476987
# 3. Upload an attachment
lynx attachment-upload \
--identifier=f16476987 \
--file=./invoice.pdf
# 4. Save the document at file level
lynx file-document-save \
--file-identifier=16476987 \
--name="Invoice" \
--content="<span>See attached PDF</span>" \
--type=SUPP \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
# 5. Verify the document was saved by retrieving all documents
lynx retrieve-file-documents \
--file-identifier=16476987 \
--transaction-identifier=btx12345
SymptomLikely Causeauthentication failedMissing or invalid LYNX_* env varsLYNX_USERNAME is not setEnvironment variable not definedinvalid partyName argumentMissing --party-name flaginvalid file reference argumentMissing --file-reference flagfailed to parse responseGWT-RPC format changed or invalid identifierunexpected response formatAttachment upload was rejected (check file size)exit code 1, no stdoutError written to stderr; run lynx <command> 2>&1 to inspect
bash
# Capture stderr
lynx file-search-by-party-name --party-name=Smith 2>&1
# Pretty-print JSON
lynx file-search-by-party-name --party-name=Smith | jq
# Extract specific field
lynx file-search-by-party-name --party-name=Smith | jq '.results[].fileIdentifier'
# Check env vars are set
env | grep LYNX
text
lynx-skill/
├── main.go # Entry point — calls cmd.Run()
├── go.mod # Module: dodmcdund.cc/lynx-travel-agent/lynxskill (Go 1.23.10)
├── .gitignore # Ignores /bin/, /lynxskill, .env
├── SKILL.md # This file — OpenClaw skill definition
├── README.md # User-facing documentation
├── lynx_architecture.md # Full architecture document (decisions, comparisons)
│
├── cmd/ # CLI commands — one file per command + dispatcher
│ ├── cmd.go # Command registry, dispatcher, .env auto-loader, config
│ ├── file_search_by_party_name.go
│ ├── file_search_by_file_reference.go
│ ├── retrieve_itinerary.go
│ ├── retrieve_file_documents.go
│ ├── attachment_upload.go
│ ├── file_document_save.go
│ └── transaction_document_save.go
│
├── gwt/ # GWT-RPC protocol layer
│ ├── types.go # GWT type constants (class names)
│ ├── build.go # GWT-RPC body builders (one func per action)
│ ├── parse.go # GWT response parsers + response structs
│ └── parse_test.go # Parser tests (10-result FileSearchResponse)
│
└── lynx/ # HTTP client layer
├── auth.go # GWT-RPC login → JSESSIONID + http.Client with cookiejar
└── client.go # HTTP helpers: DoGWTRequest, DoMultipartRequest, GWT error parse
The Lynx backend returns two GWT-RPC serialization formats. The parser auto-detects:
com.lynxtraveltech.client.shared.model.FileSearchResponse/2457361185 appear directly in the data array. The parser walks backward from the end of the parsed array, mapping indices to values.The ParseFileSearchResponse function uses backward scanning: it iterates from the last element backward, identifies FileSearchResults type markers, and extracts 10 fields per result. This approach correctly handles multi-result responses (tested with 10 results in parse_test.go).
cmd/cmd.go:13-33 implements a lightweight .env loader — no external dependency. It reads .env from the working directory at startup:
=, whitespace-trimmed.env doesn't exist.gitignore already includes .env to prevent credential leaks. If .env exists with real credentials, it will be ignored by git. This resolves the "ACTION REQUIRED" item from the initial architecture.
The MCP server implements exponential backoff retry (0s → 5s → 10s → 30s → 30s, max 5 attempts) with RetryHTTPRequest. The CLI does not implement retry yet. Rationale:
lynx/client.goThe MCP server has duplicated parseResponseBody functions in pkg/tools/attachment_upload.go:155 and pkg/rest/attachment_upload.go:146. The CLI consolidates this into a single parseAttachmentResponse in cmd/attachment_upload.go:84-101.
The Lynx backend returns SUCCESS:/path/to/file: (colon-delimited). The parser:
SUCCESS: prefix/The MCP server parameter is named identifer (missing 'i', consistent throughout the codebase). The CLI parameter correctly uses --identifier. This is a breaking change from the MCP naming but fixes the original typo.
gwt/parse_test.go contains a parser test with a real GWT-RPC response for file-search-by-party-name ("BRAY" search yielding 10 results). Run tests:
bash
cd lynx-travel-agent/lynx-skill && go test ./gwt/ -v
JSESSIONID)--kebab-case flags (CLI convention); MCP uses camelCaselynx (vs the MCP server binary lynx-mcp-server)