一键生成领馆级签证行程计划书 — Generate consulate-grade visa itinerary from natural language. Real flyai data, zero hallucination. PDF + booking links with Fliggy.
一句话说明: 输入"4个人4月27号从杭州去意大利和法国,5月4号回",一键生成领馆级签证行程计划书(PDF)+ 飞猪预订链接。省 ¥30-110 代做费,省 3-5 小时手工排版。
Generate a consulate-grade visa itinerary document with one command. Real data from flyai, zero hallucination.
You are a strict executor of this skill, not a co-designer. Follow every step in the exact order written. Do NOT:
When in doubt, follow the literal instruction. When instructions conflict with your judgment, the instruction wins.
When this skill is activated, first run these checks before doing anything else. This step catches environment problems early — skipping it leads to silent failures mid-execution that are harder to debug.
# 1. Check node (required by flyai-cli)
which node > /dev/null 2>&1 || echo "MISSING: node"
# 2. Check flyai-cli binary
which flyai > /dev/null 2>&1 || echo "MISSING: flyai-cli"
# 3. Check python3
which python3 > /dev/null 2>&1 || echo "MISSING: python3"
# 4. Check playwright (for PDF generation)
python3 -c "import playwright" 2>/dev/null || echo "MISSING: playwright"
If anything is missing, ask the user for permission before installing. Do NOT install silently — always confirm first.
npm i -g @fly-ai/flyai-cli)" → if user agrees, run the install commandpip3 install playwright && python3 -m playwright install chromium)" → if user agrees, run the install commandsAfter all dependencies are present, verify flyai actually works:
flyai fliggy-fast-search --query "test" > /dev/null 2>&1 && echo "flyai OK" || echo "flyai ERROR"
If flyai returns an error, warn the user but do not stop — it may still work for specific queries.
Only proceed to Step 1 when all dependencies are confirmed present.
Activate when the user wants to:
The user provides a natural language description of their trip. Extract these parameters:
| Parameter | Required | Example |
|---|---|---|
destination | Yes | "Italy and France" |
dates | Yes | "Apr 27 - May 4" |
travelers | Yes (default: 1) | 4 |
departure_city | Yes | "Hangzhou" |
budget | No | "60,000 CNY" |
Example: "4个人4月27号从杭州去意大利和法国,5月4号回,预算6万"
Extract destination cities, travel dates, number of travelers, departure city, and budget from the user's input.
Mandatory validation — do NOT proceed to Step 2 until all required fields are confirmed:
| Field | Required | How to resolve if missing |
|---|---|---|
| Destination (目的地) | Yes | Ask user |
| Departure city (出发城市) | Yes | Ask user |
| Departure date (出发日期) | Yes | Ask user |
| Trip duration (行程区间) | Yes — need either return date OR number of days | Ask user: "请问返回日期或出行天数?" |
| Travelers (出行人数) | No — default 1 | Use default |
| Budget (预算) | No | Skip |
If the user provides "玩7天" or "一共8天", calculate the return date from departure date + days. If only a return date is given, calculate trip days from the two dates. Either form is acceptable — the goal is to determine the full date range.
Stop and ask the user if any of the 4 required fields cannot be determined from their input. Do not guess or assume.
Once all fields are confirmed, plan a realistic day-by-day city routing. For multi-country trips, determine the city sequence. Example for Italy + France:
date +%Y-%m-%d
You MUST run this command and use the output as the reference date. Do NOT assume today's date from your training data or system prompt — those can be wrong. This is the only reliable source of truth for date calculations. Use it to resolve relative dates ("next month", "this Friday", etc.).
Retry rule (applies to all flyai calls in Step 3, 4, and 5): If a flyai command returns empty results (null or empty itemList) or errors out, wait 3 seconds and retry once. If still failed, handle per the Error Handling table and continue to the next step.
Search for all flight segments. Flight search works with both Chinese and English city names, but prefer Chinese for consistency.
International departure:
flyai search-flight --origin "{出发城市}" --destination "{第一个目的城市}" --dep-date "{start_date}" --sort-type 3
Inter-city flights (if applicable):
flyai search-flight --origin "{城市A}" --destination "{城市B}" --dep-date "{date}" --sort-type 3
Return flight:
flyai search-flight --origin "{最后一个城市}" --destination "{出发城市}" --dep-date "{end_date}" --sort-type 3
From each result, extract: marketingTransportName, marketingTransportNo, depDateTime, arrDateTime, depStationName, arrStationName, adultPrice, jumpUrl.
If no flight found for a segment: note it as "Train" or "To be confirmed" — do NOT hallucinate a flight number.
For each city in the itinerary, search hotels. You MUST include dates for overseas cities — without dates, overseas cities return wrong results from unrelated cities (not empty, but wrong data — more dangerous).
CRITICAL: Always use Chinese city names for hotel search. English names cause flyai to fuzzy-match wrong cities (e.g., "Tokyo" → Cape Town, "Nice" → Dubai, "Osaka" → null). This is not a fallback — Chinese is the only reliable option for overseas cities.
flyai search-hotels --dest-name "{城市中文名}" --check-in-date "{checkin}" --check-out-date "{checkout}" --sort rate_desc
From each result, extract: name, address, price, score, detailUrl.
Verify the hotel is actually in the target city. Check the address field — if it contains a different country or city, discard that result.
Pick the top-rated hotel for each city — this is a hard rule, not a suggestion. Do NOT substitute a cheaper or "better value" hotel to fit the user's budget. Budget handling is done separately in the booking links output (see Error Handling: "Budget exceeded"). Your job here is to pick the highest-rated valid hotel, period. If no valid results, mark "Hotel to be confirmed" in the itinerary.
For each city, search top attractions. You MUST use Chinese city names — English names return empty results.
Universal rule: Regardless of user input language, translate ALL city names to Chinese before calling any flyai command (hotels, attractions, flights with non-Chinese city names). Do not rely on a fixed mapping table — the agent is responsible for accurate translation.
flyai search-poi --city-name "{城市中文名}"
From results, extract: name, address, freePoiStatus, ticketInfo.price, jumpUrl.
Select 2-4 attractions per city to fill the daily itinerary. Distribute realistically — no more than 3 major attractions per day.
Only execute this step for multi-country Schengen trips. For single-country trips or non-Schengen destinations (e.g., Japan, South Korea, Southeast Asia), skip this step entirely.
Schengen 90/180 Day Check:
Main Application Country:
You MUST produce TWO outputs:
Generate a full English single-page travel plan table. This is the visa itinerary — keep it clean and simple, no extra sections.
# Travel Plan
| Country | Day | Date | City | Touring Spots | Accommodation | Transportation |
|---------|-----|------|------|---------------|---------------|----------------|
| CHINA | 1 | {YYYY/MM/DD} ({Day}) | {origin}→{first_city} | — | {hotel_name} ({full_address}) | Flight {airline} {flight_no}: {origin}→{dest} {dep_time} |
| {COUNTRY} | 2 | {YYYY/MM/DD} ({Day}) | {city} | {Spot 1}, {Spot 2}, {Spot 3} | {hotel_name} | Public transport and walking |
| ... | ... | ... | ... | ... | ... | ... |
| CHINA | {n} | {YYYY/MM/DD} ({Day}) | {last_city}→{origin} | — | — | Flight {airline} {flight_no}: {dep_city}→{origin} {dep_time} |
Critical rules:
Save the Markdown table to travel_plan.md in the working directory, then render it to PDF:
python3 <skill_dir>/scripts/render_pdf.py --md travel_plan.md --output My_Travel_Plan.pdf
For example, if this skill is installed at ~/.claude/skills/visa-itinerary-gen/, the command would be:
python3 ~/.claude/skills/visa-itinerary-gen/scripts/render_pdf.py --md travel_plan.md --output My_Travel_Plan.pdf
The script converts Markdown to a styled A4 PDF internally (Times New Roman, black & white, single-page table layout). Deliver both files to the user:
travel_plan.md — editable source, user can modify and re-renderMy_Travel_Plan.pdf — print-ready for consulate submissionWrite a data.json file to the working directory containing all flyai data for booking links. Then run the render script to produce the HTML files.
JSON schema — every field is required unless marked optional:
{
"title": {
"destination_cn": "意大利 & 法国",
"destination_en": "Italy & France",
"dates": "2026-04-27 ~ 2026-05-04"
},
"flights": [
{
"route_cn": "杭州 → 米兰",
"route_en": "Hangzhou → Milan",
"airline_cn": "南航 CZ8790 → 阿提哈德 EY889+EY081",
"airline_en": "China Southern CZ8790 → Etihad EY889+EY081",
"price": 4580,
"url": "https://a.feizhu.com/..."
}
],
"hotels": [
{
"city_cn": "米兰",
"city_en": "Milan",
"name_cn": "米兰大教堂广场酒店",
"name_en": "Hotel Piazza Duomo Milano",
"price": 1280,
"star": "高档型",
"recommendation_cn": "近米兰大教堂",
"recommendation_en": "Near Duomo di Milano",
"url": "https://a.feizhu.com/..."
}
],
"attractions": [
{
"city_cn": "米兰",
"city_en": "Milan",
"name_cn": "米兰大教堂",
"name_en": "Duomo di Milano",
"category_cn": "宗教场所 · 米兰地标",
"category_en": "Landmark · Milan's iconic cathedral",
"url": "https://a.feizhu.com/..."
}
]
}
Data filling rules:
Flights — format airline_cn and airline_en fields:
airline_cn uses marketingTransportName as-is from flyai (e.g. "南航", "阿提哈德"). airline_en uses the airline's official English name (e.g. "China Southern", "Etihad").{airline} {flight_no} — e.g. 海南航空 HU7937+ — e.g. 阿提哈德 EY156+EY888→ — e.g. 南航 CZ8790 → 阿提哈德 EY889+EY081price: per-person price as a number (not string), from flyai resulturl: Fliggy jumpUrl from flyai. If no link available, set to empty string ""Hotels:
name_en: use the hotel's own English name, not a translation of the Chinese transliterationprice: per-night price from flyai price field as a number. Do not calculate total cost or multiply by nights/guestsstar: tier label from flyai (e.g. "豪华型", "高档型", "舒适型")recommendation_cn/en: interestsPoi or notable features (e.g. "近圣马可广场" / "Near St. Mark's Square")Attractions:
name_en: use the internationally recognized English name (e.g. "布拉格城堡" → "Prague Castle")category_cn/en: from flyai category. Discard obviously wrong labels (e.g. "山湖田园" for a city observation deck)After writing data.json, render the HTML files:
python3 <skill_dir>/scripts/render_booking.py --data data.json --output-dir .
For example, if this skill is installed at ~/.claude/skills/visa-itinerary-gen/, the command would be:
python3 ~/.claude/skills/visa-itinerary-gen/scripts/render_booking.py --data data.json --output-dir .
The script produces two files:
booking_links_cn.html — Chinese versionbooking_links_en.html — English versionAfter generating both outputs, calculate the estimated total: sum all flight prices × number of travelers, plus all hotel prices × number of nights per hotel. If the total exceeds the user's stated budget:
flyai search-hotels for that city without the --sort parameter and pick the top-rated hotel from the lower tier.budget_warning field in data.json's title object (e.g. "budget_warning": "Estimated total exceeds stated budget").If no budget is specified, skip this check entirely.
Before delivering to the user, review each output as if you were the person who will submit it to a consulate. This step catches data errors that ruin the document's credibility. Do NOT skip it because "the output looks fine" — that is exactly when errors slip through.
The goal is to deliver something that can be used directly — not a draft that needs manual checking.
Review Output 1 (Travel Plan PDF) — the visa officer will read this:
Review Output 2 (data.json) — the user will use this to book:
airline_cn and airline_en with flight numbers (not just airline name)url (if flyai returned a jumpUrl)urlstar and recommendation_cn/en populatedcategory_cn/en populated; discard obviously wrong labels_en fields contain correct English translations:
When something fails review:
| Situation | Action |
|---|---|
| Flight not found | Write "To be confirmed — please check alternative routes" in Destination column |
| Hotel not found | Write "To be confirmed — please book a hotel with free cancellation" in Hotel column |
| Attraction data sparse | Use fliggy-fast-search --query "{city} tourist attractions" as fallback |
| Schengen stay > 90 days | Add prominent warning: "⚠ WARNING: Total Schengen stay exceeds 90 days" |
| flyai not installed | Print installation instructions and stop |
| Budget exceeded | Mention in booking links output that estimated total exceeds stated budget |