visa-itinerary-gen — Visa Itinerary Generator
一句话说明: 输入"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.
Execution Contract — Read This First
You are a strict executor of this skill, not a co-designer. Follow every step in the exact order written. Do NOT:
- - Skip steps you consider unnecessary. Every step exists for a reason. If it says "run this command", run it. If it says "review this output", review it.
- Reinterpret instructions. "Pick the top-rated hotel" means pick the top-rated hotel. Do not substitute a cheaper hotel for budget reasons, do not pick a "better value" alternative, do not apply your own judgment to override an explicit rule.
- Optimize on behalf of the user. This skill already handles edge cases (budget exceeded, data missing, etc.) in specific steps. If a situation is not covered by the instructions, ask the user — do not invent a workaround.
- Combine or reorder steps. Step numbers are execution order. Do not merge Step 3+4+5 into a single batch, do not skip Step 2 because "the date is obvious", do not skip Step 8 because "the output looks fine".
When in doubt, follow the literal instruction. When instructions conflict with your judgment, the instruction wins.
Step 0: Dependency Check — MANDATORY, DO NOT SKIP
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.
CODEBLOCK0
If anything is missing, ask the user for permission before installing. Do NOT install silently — always confirm first.
- - node missing → tell user: install Node.js from https://nodejs.org/ (cannot be auto-installed)
- flyai-cli missing → ask user: "flyai-cli is not installed. It's a free CLI tool (no API key needed) for searching flights, hotels, and attractions on Fliggy. Shall I install it? (
npm i -g @fly-ai/flyai-cli)" → if user agrees, run the install command - python3 missing → tell user: install Python 3 from https://python.org/ (cannot be auto-installed)
- playwright missing → ask user: "playwright is not installed. It's needed for PDF generation. Shall I install it? (
pip3 install playwright && python3 -m playwright install chromium)" → if user agrees, run the install commands
After all dependencies are present, verify flyai actually works:
CODEBLOCK1
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.
When to Use This Skill
Activate when the user wants to:
- - Generate a travel itinerary for any visa application (Schengen, Japan, South Korea, Southeast Asia, etc.)
- Create a travel plan document for consulate/embassy submission
- Prepare visa application documents (specifically the itinerary)
Input
The user provides a natural language description of their trip. Extract these parameters:
| Parameter | Required | Example |
|---|
| INLINECODE2 | Yes | "Italy and France" |
| INLINECODE3 |
Yes | "Apr 27 - May 4" |
|
travelers | Yes (default: 1) | 4 |
|
departure_city | Yes | "Hangzhou" |
|
budget | No | "60,000 CNY" |
Example: INLINECODE7
Execution Steps
Step 1: Parse Input & Validate
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:
- - Milan → Venice → Florence → Rome → Nice → Paris
Step 2: Get Current Date — MANDATORY, DO NOT SKIP
CODEBLOCK2
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.).
Step 3: Call flyai — Flights
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:
CODEBLOCK3
Inter-city flights (if applicable):
CODEBLOCK4
Return flight:
CODEBLOCK5
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.
Step 4: Call flyai — Hotels
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.
CODEBLOCK6
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.
Step 5: Call flyai — Attractions
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.
CODEBLOCK7
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.
Step 6: Internal Logic
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:
- - Count total days inside Schengen zone
- If > 90 days, add a warning at the top of the document
Main Application Country:
- - Count days spent in each country
- The country with the most days = main application country
- If tied, the first country visited is the main application country
Step 7: Generate Output
You MUST produce TWO outputs:
Output 1: Travel Plan Table (Markdown → PDF)
Generate a full English single-page travel plan table. This is the visa itinerary — keep it clean and simple, no extra sections.
CODEBLOCK8
Critical rules:
- - ALL text in English
- Every day must have specific touring spots — never write "Free day" or "Rest"
- Touring Spots: just list attraction names, no descriptions (e.g. "Duomo di Milano, Galleria V.E. II, The Last Supper")
- Accommodation: first time a hotel appears → full name + address; subsequent nights at same hotel → name only
- Transportation: flight rows → "Flight {airline} {flightno}: {route} {time}"; train rows → "Train {citya}→{city_b}"; sightseeing days → "Public transport and walking"
- Keep it concise — the whole table should fit on a single A4 page
- No Declaration, no Financial Summary, no Notes for Visa Officer — just the itinerary table. Real visa itineraries that get approved are plain tables. Adding extra sections deviates from standard consulate submission format.
Generate PDF from the table
Save the Markdown table to travel_plan.md in the working directory, then render it to PDF:
CODEBLOCK9
For example, if this skill is installed at ~/.claude/skills/visa-itinerary-gen/, the command would be:
CODEBLOCK10
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-render - INLINECODE30 — print-ready for consulate submission
Output 2: Booking Links HTML (Chinese + English)
Generate TWO HTML files. Reference the visual style, CSS, and HTML structure of templates/ as design examples, but dynamically generate all content from flyai data. The templates contain hardcoded demo data (Italy & France), not fillable placeholders — do NOT attempt find-and-replace. Instead, replicate the same layout and styling with real data.
- -
booking_links_cn.html — Chinese version with Chinese hotel/attraction names and recommendations - INLINECODE33 — English version with English recommendations
Each HTML file contains three tables (Flights / Hotels / Attractions) with:
- - Every row has a copy button (click to copy Fliggy link to clipboard) + clickable link
- Recommendations from flyai data: hotels show
star + interestsPoi (e.g. "高档型 · 近圣马可广场"), attractions show category (e.g. "博物馆 · 达芬奇名作") - Fonts: Georgia (cross-platform) + Playfair Display (EN title only, via Google Fonts)
- Style: memo briefing aesthetic (warm paper background, accent red dividers, black table headers)
Data to fill into templates:
Flights table: route (CN/EN city names), airline + flight number, price per person, Fliggy jumpUrl.
Flight display rules — extract marketingTransportName + marketingTransportNo from each segment in flyai results, then format as follows:
- - Airline name: use
marketingTransportName as-is from flyai (e.g. "南航", "阿提哈德"). For the English version, translate to the airline's official English name (e.g. "China Southern", "Etihad"). - Flight numbers are mandatory. Never omit the flight number or write only the airline name.
- Direct flight:
{airline} {flight_no} — e.g. INLINECODE42 - Same-airline connecting flights: combine flight numbers with
+ — e.g. INLINECODE44 - Multi-airline connecting flights: separate airlines with
→, following the actual boarding sequence — e.g. INLINECODE46 - Symbol rules:
+ joins consecutive same-airline segments; → joins different airlines. Do not merge non-consecutive same-airline segments. - No transfer descriptions. Do not write "经多哈中转", "via Abu Dhabi", "直飞" etc. in the flight column. The flight numbers and airline names already convey this information.
Hotels table: city, hotel name (EN) / flyai Chinese name, price (per night — use the price field from flyai as-is, do not calculate total cost or multiply by nights/guests), star + interestsPoi as recommendation, Fliggy detailUrl. Column header: "每晚" (CN) / "Price/night" (EN).
Attractions table: city, attraction name (EN) / flyai Chinese name, category as recommendation, Fliggy INLINECODE57
Budget Check (only when budget is specified)
After 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:
- 1. Identify the most expensive hotel (highest per-night price)
- From the Step 4 flyai results for that city, pick the next-best hotel one tier down (e.g., 豪华型 → 高档型 → 舒适型). If the original results have no lower-tier options, re-run
flyai search-hotels for that city without the --sort parameter and pick the top-rated hotel from the lower tier. - Recalculate total. Still over budget? Repeat for the next most expensive hotel.
- Update both Output 1 (travel_plan.md + re-render PDF) and Output 2 (HTML files) with the new hotel selections.
- If all hotels are already at 舒适型 and total still exceeds budget, keep the current selection and add a note in the booking links HTML: "Estimated total exceeds stated budget."
If no budget is specified, skip this check entirely.
Step 8: Delivery Review — MANDATORY, DO NOT SKIP
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:
- - Language check: scan the entire travel_plan.md for any non-ASCII characters (Chinese, Japanese, Arabic, etc.). The travel plan must be pure English + numbers + standard punctuation. If any non-ASCII text is found (most commonly: Chinese airline names copied from flyai, Chinese hotel names, or Chinese city names), translate it to the correct English equivalent and re-render the PDF. This is a hard gate — do not proceed to delivery until the table is 100% English.
- Every hotel address is in the correct city (not a different country or region)
- Every flight row has a specific flight number + departure time (not just the airline name)
- Each day's touring spots are only in that day's city (no mixing cities on transfer days)
- Transfer day timing is realistic (early morning flight = no sightseeing that day)
- Every piece of data (flight numbers, hotel names, touring spot names) must trace back to a flyai call. If a touring spot was not returned by flyai, it must be removed — use "Local exploration / neighborhood walk" instead. This skill's promise is zero hallucination from real data.
Review Output 2 (Booking Links HTML) — the user will use this to book:
- - CN version: every flight has airline + flight number (e.g., "春秋航空 9C8515", not just "春秋航空")
- EN version: every flight has airline + flight number (e.g., "Spring Airlines 9C8515")
- Attraction categories/descriptions make sense (discard or replace obviously wrong labels like "山湖田园" for a city observation deck)
- Every hotel and flight has a Fliggy link; if data exists but link is missing, note "请在飞猪搜索该航线"
- EN version: verify all English translations are correct. flyai returns Chinese data — the agent translates airline names, hotel names, and attraction names to English. Check each translation against the entity's official English name:
- Airline names: use IATA official English name, not literal translation (e.g. "南航" → "China Southern Airlines", not "Southern Airlines")
- Hotel names: use the hotel's own English name from the address or booking site, not a translation of the Chinese transliteration (e.g. "阿勒尔霍特尔安德雷西登斯布拉格" is a transliteration of "Hotel Residence Agnes", not a Chinese name to be translated back)
- Attraction names: use the internationally recognized English name (e.g. "布拉格城堡" → "Prague Castle")
- If unsure of the correct English name, keep the flyai Chinese name rather than guessing
When something fails review:
- - Fix it if you can (re-search, substitute data, remove bad labels)
- If unfixable (flyai has no data), mark it clearly in the output and tell the user what needs their attention
- Default is: user receives a ready-to-use document, not a TODO list
Error Handling
| 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 |
Important Notes
- - Never hallucinate data. Every flight number, hotel name, and address must come from flyai results. If flyai returns no data, mark it as "To be confirmed" — do NOT make up information. Visa officers can and will verify.
- Always include booking links. In Output 2, every hotel and flight must have a Fliggy booking link from the flyai response.
- Keep the travel plan clean. Output 1 is just a table — no extra sections, no branding. Follow standard consulate submission format.
- Brand mention. Only in Output 2 (booking links), include "Based on fly.ai real-time results".
visa-itinerary-gen — 签证行程生成器
一句话说明: 输入4个人4月27号从杭州去意大利和法国,5月4号回,一键生成领馆级签证行程计划书(PDF)+ 飞猪预订链接。省 ¥30-110 代做费,省 3-5 小时手工排版。
一条命令生成符合领馆要求的签证行程文件。数据来自飞猪,零幻觉。
执行契约 — 请先阅读
你是此技能的严格执行者,而非共同设计者。 严格按照以下顺序执行每一步。不得:
- - 跳过你认为不必要的步骤。 每一步都有其存在的理由。如果指示说运行此命令,就运行它。如果指示说检查此输出,就检查它。
- 重新解释指令。 选择评分最高的酒店意味着选择评分最高的酒店。不要为了预算原因替换更便宜的酒店,不要选择性价比更高的替代品,不要运用你自己的判断来覆盖明确的规则。
- 代表用户进行优化。 此技能已在特定步骤中处理了边缘情况(预算超支、数据缺失等)。如果指令未涵盖某种情况,请询问用户——不要自行发明解决方案。
- 合并或重新排序步骤。 步骤编号即执行顺序。不要将步骤3+4+5合并为一批处理,不要因为日期显而易见而跳过步骤2,不要因为输出看起来没问题而跳过步骤8。
如有疑问,请遵循字面指示。当指示与你的判断冲突时,以指示为准。
步骤0:依赖检查 — 强制,不得跳过
当此技能被激活时,在执行任何其他操作之前,首先运行以下检查。此步骤可及早发现环境问题——跳过它会导致执行中途静默失败,且更难调试。
bash
1. 检查 node(flyai-cli 所需)
which node > /dev/null 2>&1 || echo MISSING: node
2. 检查 flyai-cli 二进制文件
which flyai > /dev/null 2>&1 || echo MISSING: flyai-cli
3. 检查 python3
which python3 > /dev/null 2>&1 || echo MISSING: python3
4. 检查 playwright(用于 PDF 生成)
python3 -c import playwright 2>/dev/null || echo MISSING: playwright
如果缺少任何依赖,在安装前征求用户许可。不要静默安装——务必先确认。
- - 缺少 node → 告知用户:从 https://nodejs.org/ 安装 Node.js(无法自动安装)
- 缺少 flyai-cli → 询问用户:flyai-cli 未安装。它是一个免费的 CLI 工具(无需 API 密钥),用于在飞猪上搜索航班、酒店和景点。是否安装?(npm i -g @fly-ai/flyai-cli) → 如果用户同意,运行安装命令
- 缺少 python3 → 告知用户:从 https://python.org/ 安装 Python 3(无法自动安装)
- 缺少 playwright → 询问用户:playwright 未安装。PDF 生成需要它。是否安装?(pip3 install playwright && python3 -m playwright install chromium) → 如果用户同意,运行安装命令
所有依赖就绪后,验证 flyai 是否正常工作:
bash
flyai fliggy-fast-search --query test > /dev/null 2>&1 && echo flyai OK || echo flyai ERROR
如果 flyai 返回错误,警告用户但不要停止——它可能仍能处理特定查询。
只有在所有依赖确认就绪后,才继续执行步骤1。
何时使用此技能
当用户希望以下情况时激活:
- - 为任何签证申请生成旅行行程(申根、日本、韩国、东南亚等)
- 创建用于领事馆/大使馆提交的旅行计划文件
- 准备签证申请文件(特别是行程单)
输入
用户提供其旅行的自然语言描述。提取以下参数:
是 | 4月27日 - 5月4日 |
| 旅行人数 | 是(默认:1) | 4 |
| 出发城市 | 是 | 杭州 |
| 预算 | 否 | 6万人民币 |
示例:4个人4月27号从杭州去意大利和法国,5月4号回,预算6万
执行步骤
步骤1:解析输入并验证
从用户输入中提取目的地城市、旅行日期、旅行人数、出发城市和预算。
强制验证——在所有必填字段确认之前,不得进入步骤2:
是 | 询问用户 |
| 出发日期 | 是 | 询问用户 |
| 行程区间 | 是——需要返回日期或出行天数 | 询问用户:请问返回日期或出行天数? |
| 出行人数 | 否——默认1 | 使用默认值 |
| 预算 | 否 | 跳过 |
如果用户提供玩7天或一共8天,根据出发日期+天数计算返回日期。如果只提供了返回日期,根据两个日期计算行程天数。两种形式均可接受——目标是确定完整的日期范围。
如果无法从用户输入中确定4个必填字段中的任何一个,请停下来询问用户。 不要猜测或假设。
一旦所有字段确认,规划一个现实的城市逐日路线。对于多国旅行,确定城市顺序。意大利+法国示例:
- - 米兰 → 威尼斯 → 佛罗伦萨 → 罗马 → 尼斯 → 巴黎
步骤2:获取当前日期 — 强制,不得跳过
bash
date +%Y-%m-%d
你必须运行此命令并将输出用作参考日期。不要从你的训练数据或系统提示中假设今天的日期——这些可能错误。这是日期计算的唯一可靠真实来源。用它来解析相对日期(下个月、这周五等)。
步骤3:调用 flyai — 航班
重试规则(适用于步骤3、4和5中的所有 flyai 调用): 如果 flyai 命令返回空结果(null 或空的 itemList)或出错,等待3秒并重试一次。如果仍然失败,根据错误处理表处理并继续下一步。
搜索所有航段。航班搜索支持中英文城市名称,但为了一致性,优先使用中文。
国际出发:
bash
flyai search-flight --origin {出发城市} --destination {第一个目的城市} --dep-date {start_date} --sort-type 3
城市间航班(如适用):
bash
flyai search-flight --origin {城市A} --destination {城市B} --dep-date {date} --sort-type 3
返程航班:
bash
flyai search-flight --origin {最后一个城市} --destination {出发城市} --dep-date {end_date} --sort-type 3
从每个结果中提取:marketingTransportName、marketingTransportNo、depDateTime、arrDateTime、depStationName、arrStationName、adultPrice、jumpUrl。
如果某段没有找到航班: 标注为火车或待确认——不要虚构航班号。
步骤4:调用 flyai — 酒店
为行程中的每个城市搜索酒店。海外城市必须包含日期——没有日期,海外城市会返回来自无关城市的错误结果(不是空,而是错误数据——更危险)。
关键:酒店搜索始终使用中文城市名称。 英文名称会导致 flyai 模糊匹配到错误城市(例如,Tokyo → 开普敦,Nice → 迪拜,Osaka → null)。这不是备选方案——中文是海外城市唯一可靠的选择。
bash
flyai search-hotels --dest-name {城市中文名} --check-in-date {checkin} --check-out-date {checkout} --sort rate_desc
从每个结果中提取:name、address、price、score、detailUrl。
验证酒店确实在目标城市。 检查 address 字段——如果包含不同的国家或城市,丢弃该结果。
为每个城市选择评分最高的酒店——这是硬性规则,不是建议。 不要为了符合用户预算而替换更便宜或性价比更高的酒店。预算处理在预订链接输出中单独进行(参见错误处理:预算超支)。你的工作就是选择评分最高的有效酒店,仅此而已。如果没有有效结果,在行程中标注酒店待确认。
步骤5:调用 flyai — 景点
为每个城市搜索热门景点。必须使用中文城市名称——英文名称返回空结果。
通用规则