Ask the user for their Google saved places list, look up each place with goplaces, and recommend the single best one to visit today based on their preferences and visit history.
Help the user pick one place to visit from their saved Google Places list by fetching live details (rating, hours, reviews) via the goplaces CLI, incorporating the user's own notes, their stated cuisine and location preferences, and their visit history — then making a clear, opinionated recommendation.
1. Click "Deselect all", then check only "Saved"
2. Click "Next step" → "Export once" → "Create export"
3. Download the zip and find the CSV at Takeout/Saved/Saved Places.csv
4. Give the agent the file path — it will import and remember it automatically
- A local data file at skills/goplaces-togo/goplaces-visits.json persists the imported list and visit history (created automatically on first use).
Data file schema
INLINECODE5 is the single source of truth for all persistent state:
CODEBLOCK0
- savedList — the user's saved places, persisted so they don't have to paste it again.
INLINECODE7 — normalised city name resolved via the Places API; used to filter by city before scoring.
INLINECODE8 — keyed by place_id, holds visit history for recording and recency scoring.
Steps
1. Load or ask for the saved list
Read skills/goplaces-togo/goplaces-visits.json. Check whether savedList exists and has at least one entry.
If a saved list exists, show it to the user and ask:
I have your saved list from last time:
1. —
...
Use this list, or provide a new CSV to replace it? (Press Enter to use the existing list.)
- If the user presses Enter or says "use it" / "yes" / similar affirmation → keep savedList as-is and proceed.
If the user provides a new CSV file path or pastes CSV content → replace savedList with the newly parsed entries (see Step 2), write to disk, then proceed.
If the user says "add" or "update" followed by a new CSV → merge: add new entries, keep existing ones that are not duplicates (match on name case-insensitively or mapsUrl). Write merged list to disk.
If no saved list exists, say exactly:
I need your Google saved places list. Here's how to get it:
1. Go to https://takeout.google.com
Click "Deselect all", then scroll down and check only "Saved"
Download the zip, open it, and find the CSV file inside the Saved/ folder (e.g. Saved Places.csv)
Share the file path or paste its contents here
Wait for the user to provide the CSV before proceeding to Step 2.
2. Parse the CSV and extract user comments
The CSV exported from Google Takeout has this header and format:
CODEBLOCK1
For each non-empty data row (skip the header and blank rows):
- Title → INLINECODE18
INLINECODE19 → userComment (use null if empty)
INLINECODE22 → mapsUrl — store as-is for reference; do not attempt to extract a place ID from the URL path, as the hex values in data=!4m2!3m1!1s<hex> are Google's internal CID format, not Places API IDs
INLINECODE25 , Comment → ignore for now
Set addedAt to today's date
Store INLINECODE28
After parsing, write the resulting array to savedList in skills/goplaces-togo/goplaces-visits.json immediately, before doing any API calls. This ensures the list is never lost even if the session ends early.
3. Classify each place by city
For every entry in savedList where city is null, resolve the place name to get its city:
CODEBLOCK2
From the result, extract the city using this priority order:
1. candidates[0].addressComponents — find the component with types containing "locality" → use its INLINECODE37
Fall back to the component with types containing "administrative_area_level_2" → use its INLINECODE40
Fall back to parsing the city token from candidates[0].formattedAddress (typically the second comma-separated segment, e.g. "Gochi, Cupertino, CA 95014" → "Cupertino")
If all fail, set city to null and note the entry as unclassified
Normalise city names to title case (e.g. "cupertino" → "Cupertino"). Store placeId from candidates[0].place_id at the same time — no need to re-resolve in Step 4.
After classifying all entries, write the updated savedList (with city and placeId filled in) back to disk.
Which city are you in today? (or press Enter to search all cities)
- If the user names a city → filter savedList to entries where city matches (case-insensitive). Use only these for scoring. If the city has zero matches, say so and ask again or offer to search all.
If the user presses Enter or says "all" / "anywhere" → use the full savedList.
Store the chosen city as currentCity (or null for all) in memory for this session.
5. Ask for cuisine and location preferences
Say exactly:
What cuisine or type of food are you in the mood for today? And do you have a preferred neighbourhood or area? (Press Enter to skip either.)
Wait for the user's response. Parse two optional values:
- cuisinePreference — e.g. "Japanese", "Italian", "anything spicy", null if skipped.
INLINECODE60 — e.g. "Shibuya", "within 2km of Shinjuku Station", null if skipped.
If the user skips both, proceed without preference filtering.
6. Resolve any remaining unresolved place IDs
Step 3 already resolved and stored placeId for newly imported entries. Only re-run resolve for entries that still have placeId: null (e.g. manually added entries):
CODEBLOCK3
Parse the JSON. Take candidates[0].place_id. Also backfill city if it is still null. If the result is empty, mark the entry as unresolvable and skip it (report at the end).
7. Fetch details for each place ID
CODEBLOCK4
Collect the following fields for each place:
- displayName.text — human name
INLINECODE68 — is it open right now?
INLINECODE69 — overall rating (0–5)
INLINECODE70 — number of reviews
INLINECODE71 — 0 (free) to 4 (very expensive)
INLINECODE72 or types[] — cuisine/category tags
INLINECODE74 — { latitude, longitude } for distance scoring
INLINECODE76 — top review snippet (first 150 chars)
INLINECODE77 — one-line editorial blurb if present
8. Load visit history
The places section of skills/goplaces-togo/goplaces-visits.json was already read in Step 1. For each place in the working list, look up its place_id in places and derive:
- visitCount — length of the visits array (0 if the key is absent).
INLINECODE84 — days between today and visits[last].date (null if never visited).
9. Score and rank
Compute a score for each resolved place. All bonus terms are additive.
CODEBLOCK5
Rank places by score descending. Exclude unresolvable entries from the ranking but list them at the end.
10. Recommend exactly one place
Present the top-ranked place as your recommendation using this format:
Recommended:
- Open now: Yes / No
Rating: X.X / 5 (N reviews)
Price: $ / $$ / $$$ / $$$$ (omit if unavailable)
Your note: "" (omit if null)
Visits: N times (last: YYYY-MM-DD) / Never visited
Why:
CODEBLOCK6 (Run the above to see full hours, phone, and website.)
Then list the remaining resolved places as a ranked table:
Rank
Place
Rating
Open
Visits
Score
2
...
Finish with any unresolvable entries: "Could not look up: X, Y — please check the spelling or paste Google Maps URLs."
If only one place was provided, still confirm it looks good (or flag if it is closed, poorly rated, or visited very recently).
11. Confirm selection and record the visit
After showing the recommendation, ask:
Are you going to ? Say "yes" to log this visit, or tell me which place from the list you picked instead.
Wait for the user's response.
- If the user confirms a place (by saying yes or naming one), record the visit:
1. Read skills/goplaces-togo/goplaces-visits.json (already loaded; use in-memory copy).
2. Ensure places["<place_id>"] exists; create it with { name, visits: [] } if not.
3. Append a new visit entry:
Use today's date and the current local time (24-hour format). Do not touch savedList.
4. Write the full updated object (both savedList and places) back to skills/goplaces-togo/goplaces-visits.json.
5. Confirm: "Logged your visit to on at
goplaces-togo
目标
通过 goplaces CLI 获取实时详情(评分、营业时间、评论),结合用户自己的笔记、用户指定的菜系和位置偏好以及访问历史,帮助用户从其保存的 Google 地点列表中选择一个地点进行访问,然后给出清晰、有主见的推荐。
下载 zip 文件,打开它,在 Saved/ 文件夹中找到 CSV 文件(例如 Saved Places.csv)
分享文件路径或在此处粘贴其内容
等待用户提供 CSV 后再进入步骤 2。
2. 解析 CSV 并提取用户评论
从 Google Takeout 导出的 CSV 具有以下标题和格式:
Title,Note,URL,Tags,Comment
Gochi Cupertino,,https://www.google.com/maps/place/Gochi+Cupertino/data=!4m2!3m1!1s0x808fb5c78e1841e7:0xf8efac3fb5ce0b40,,
Kunjip Tofu,Fine and good for date,https://www.google.com/maps/place/Kunjip+Tofu/data=!4m2!3m1!1s0x808fb10003a9a597:0xe41f61b5ba1b2f19,,
对于每个非空数据行(跳过标题行和空行):
- Title → name
Note → userComment(如果为空则使用 null)
URL → mapsUrl — 按原样存储以供参考;不要尝试从 URL 路径中提取地点 ID,因为 data=!4m2!3m1!1s 中的十六进制值是 Google 的内部 CID 格式,不是 Places API ID
**Adds city classification and city-based filtering to recommendations.**
- Each saved place is now classified by city using the Places API; city names are stored and normalized.
- On launch, shows a summary of saved places grouped by city, and asks which city the user is in today (with option to search all).
- Recommendations filter by selected city before applying cuisine/location preferences.
- The data schema is updated: both `savedList` and `places` entries can contain a `city` field.
- Place ID resolution now fills in both `city` and `placeId` for new entries to reduce duplicate lookups.