Lynx Skill
/install lynx-skill
lynx-skill
Stateless Go CLI that replicates every tool from lynx-mcp-server as standalone commands — no daemon, no SSE, no Bearer token.
Quick Install
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/
Credentials
The binary reads these environment variables. All three are required:
| Variable | Description |
|---|---|
LYNX_USERNAME |
Lynx username |
LYNX_PASSWORD |
Lynx password |
LYNX_COMPANY_CODE |
Company code |
Available Commands
| # | Command | Description |
|---|---|---|
| 1 | file-search-by-party-name |
Search files by customer last name |
| 2 | file-search-by-file-reference |
Search files by Lynx file reference |
| 3 | retrieve-itinerary |
Get detailed itinerary for a file |
| 4 | retrieve-file-documents |
Get documents for a transaction |
| 5 | attachment-upload |
Upload a file attachment from disk |
| 6 | file-document-save |
Save document at the file level |
| 7 | transaction-document-save |
Save document at the transaction level |
All commands output JSON to stdout. Errors go to stderr. Exit code 0 = success, 1 = failure.
1. file-search-by-party-name
Search for files by the customer's last name.
lynx file-search-by-party-name --party-name=LASTNAME
Flags:
| Flag | Type | Required | Description |
|---|---|---|---|
| --party-name` | string | yes | Customer last name to search for |
Output:
{
"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:
lynx file-search-by-party-name --party-name=Smith
lynx file-search-by-party-name --party-name=Smi
2. file-search-by-file-reference
Search for a file using its Lynx file reference (e.g. FTXXXXXXXXX).
lynx file-search-by-file-reference --file-reference=FTXXXXXXXXX
Flags:
| Flag | Type | Required | Description |
|---|---|---|---|
--file-reference |
string | yes | Lynx file reference |
Output: Same JSON structure as file-search-by-party-name (count + results array).
Example:
lynx file-search-by-file-reference --file-reference=FT16476987
3. retrieve-itinerary
Get the full itinerary for a given file.
lynx retrieve-itinerary --file-identifier=ID
Flags:
| Flag | Short | Type | Required | Default | Description |
|---|---|---|---|---|---|
--file-identifier |
-f |
string | yes | — | Numeric file identifier (from search results) |
--show-cancelled |
-c |
bool | no | false |
Include cancelled bookings in the results |
Output:
{
"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:
# Active bookings only (default)
lynx retrieve-itinerary --file-identifier=16476987
# Include cancelled bookings
lynx retrieve-itinerary --file-identifier=16476987 --show-cancelled
4. retrieve-file-documents
Retrieve all documents associated with a specific transaction within a file.
lynx retrieve-file-documents \
--file-identifier=ID \
--transaction-identifier=TXID
Flags:
| Flag | Type | Required | Description |
|---|---|---|---|
--file-identifier |
string | yes | Numeric file identifier |
--transaction-identifier |
string | yes | Transaction identifier (from itinerary) |
Output:
{
"count": 1,
"results": [
{
"fileIdentifier": "12345",
"transactionIdentifier": "btx12345",
"documentIdentifier": "doc_001",
"documentName": "Invoice",
"documentType": "SUPP",
"content": "\x3Cspan>Invoice details\x3C/span>",
"attachmentUrl": "/documents/file/f16476987/d20250709064401.pdf"
}
]
}
Example:
lynx retrieve-file-documents \
--file-identifier=16476987 \
--transaction-identifier=btx12345
5. attachment-upload
Upload a file (PDF, image, etc.) from disk and associate it with a document.
lynx attachment-upload \
--identifier=FILEID \
--file=/path/to/document.pdf
Flags:
| Flag | Type | Required | Description |
|---|---|---|---|
--identifier |
string | yes | Unique identifier for the attachment |
--file |
string | yes | Path to the file on disk |
Output:
{
"attachmentUrl": "/documents/file/f16476987/d20250709064401.pdf"
}
The returned attachmentUrl can be passed to file-document-save or transaction-document-save as --attachment-url.
Examples:
lynx attachment-upload --identifier=f16476987 --file=./documents/invoice.pdf
lynx attachment-upload --identifier=f16476987 --file=./documents/receipt.jpg
6. file-document-save
Save (create or update) a document at the file level.
lynx file-document-save \
--file-identifier=ID \
--name=NAME \
--content=CONTENT \
--type=TYPE \
[--attachment-url=URL]
Flags:
| Flag | Type | Required | Description |
|---|---|---|---|
--file-identifier |
string | yes | Numeric file identifier |
--name |
string | yes | Document name |
--content |
string | yes | Document content (plain text or HTML) |
--type |
string | yes | Document type code — see valid values below |
--attachment-url |
string | no | Attachment URL from attachment-upload |
Valid --type values:
| Code | Display Name |
|---|---|
SUPP |
Supplier Communication |
CLINT |
Agent Communication |
GEN |
General |
INV |
Accounting Communication |
AFTER |
After Hours Phone Inquiry |
EMAIL |
|
FLIGH |
Flight Information |
BOOKI |
Mail Merge Document |
PHONE |
Phone Conversation |
CLCOM |
Travelling 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:
{
"status": "ok"
}
Examples:
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="\x3Cspan>Invoice #1234 — 1500.00 EUR\x3C/span>" \
--type=INV \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
7. transaction-document-save
Save (create or update) a document at the transaction level.
lynx transaction-document-save \
--file-identifier=ID \
--transaction-identifier=TXID \
--name=NAME \
--content=CONTENT \
--type=TYPE \
[--attachment-url=URL]
Flags:
| Flag | Type | Required | Description |
|---|---|---|---|
--file-identifier |
string | yes | Numeric file identifier |
--transaction-identifier |
string | yes | Transaction identifier |
--name |
string | yes | Document name |
--content |
string | yes | Document content (plain text or HTML) |
--type |
string | yes | Document type code — see valid values in file-document-save |
--attachment-url |
string | no | Attachment URL from attachment-upload |
Output:
{
"status": "ok"
}
Examples:
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="\x3Cb>Paid: 500 EUR\x3C/b>" \
--type=INV \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
SAMPLES — Multi-Step Workflows
Sample 1: Find a customer file, inspect itinerary, save a note
# 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
Sample 2: Upload an invoice and attach it to a file
# 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="\x3Cspan>Invoice 1234 — 1500 EUR\x3C/span>" \
--type=INVOICE \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
Sample 3: Retrieve documents, check content, save a new receipt
# 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="\x3Cb>Paid: 500 EUR on 01-Jun-2026\x3C/b>" \
--type=RECEIPT \
--attachment-url=/documents/file/f16476987/d20250709064401.pdf
Sample 4: Full document management lifecycle
# 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="\x3Cspan>See attached PDF\x3C/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
Troubleshooting
| Symptom | Likely Cause |
|---|---|
authentication failed |
Missing or invalid LYNX_* env vars |
LYNX_USERNAME is not set |
Environment variable not defined |
invalid partyName argument |
Missing --party-name flag |
invalid file reference argument |
Missing --file-reference flag |
failed to parse response |
GWT-RPC format changed or invalid identifier |
unexpected response format |
Attachment upload was rejected (check file size) |
exit code 1, no stdout |
Error written to stderr; run lynx \x3Ccommand> 2>&1 to inspect |
Quick debug recipes
# 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
Architecture
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
Lessons from Implementation (AP-17)
1. GWT-RPC Requires Two Parsers
The Lynx backend returns two GWT-RPC serialization formats. The parser auto-detects:
- Old format (pre-mid 2025): Type strings like
com.lynxtraveltech.client.shared.model.FileSearchResponse/2457361185appear directly in the data array. The parser walks backward from the end of the parsed array, mapping indices to values. - Lazy serialization (current): Type strings are resolved through index-based references. The first mapped string in the data array determines which format is active.
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).
2. .env Auto-Load (Convenience vs Security)
cmd/cmd.go:13-33 implements a lightweight .env loader — no external dependency. It reads .env from the working directory at startup:
- Lines are split on
=, whitespace-trimmed - Only sets env vars that are not already set (env vars take precedence)
- Silently returns if
.envdoesn'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.
3. No Retry/Backoff (Deferred)
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:
- CLI is designed for agent use (one-shot commands), not high-throughput automation
- If 429 or transient failures are observed in practice, add a simple retry wrapper in
lynx/client.go
4. Attachment Upload — Consolidated Parsing
The 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:
- Strips
SUCCESS:prefix - Trims trailing colon and whitespace
- Validates the result starts with
/
5. MCP Identifier Typo Corrected
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.
6. Test Coverage
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:
cd lynx-travel-agent/lynx-skill && go test ./gwt/ -v
Differences from lynx-mcp-server
- Stateless: no background server, no SSE, no Bearer token
- Attachment upload: reads files from disk directly (not base64)
- Session: each command authenticates independently (fresh
JSESSIONID) - Flag style:
--kebab-caseflags (CLI convention); MCP uses camelCase - Binary name: the CLI binary is
lynx(vs the MCP server binarylynx-mcp-server) - Output: JSON to stdout, errors to stderr
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install lynx-skill - After installation, invoke the skill by name or use
/lynx-skill - Provide required inputs per the skill's parameter spec and get structured output
What is Lynx Skill?
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... It is an AI Agent Skill for Claude Code / OpenClaw, with 33 downloads so far.
How do I install Lynx Skill?
Run "/install lynx-skill" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Lynx Skill free?
Yes, Lynx Skill is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does Lynx Skill support?
Lynx Skill is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Lynx Skill?
It is built and maintained by Fred Ghilini (@fredguile); the current version is v1.0.0.