pinbook
v1.2.0
Published
YAML-first CLI for building Google My Maps-ready KML
Maintainers
Readme
Pinbook
Pinbook is a YAML-first CLI for building Google My Maps-ready KML.
It is designed for travel planning workflows where you want a map format that is readable by humans, easy to generate with AI, and safe to keep in git.
Plan the trip in plain YAML first, then turn it into a visual map.
Example
Pinbook can describe a real multi-city trip, not just a tiny demo config.
The canonical example in this repository covers Tokyo, Kyoto, Osaka, Nara, and Hiroshima as a multi-file travel map:
- Source config: example/index.yaml
- Live Google My Maps example: Japan Example Map
What It Does
- Scaffolds a new map project with
pinbook create - Reads an
index.yamlmap config - Geocodes addresses during build when coordinates are missing
- Writes a
.pinbook/map.kmlfile you can import into Google My Maps - Offers an optional Pinbook skill install for AI agents
Quick Start
npx pinbook create my-map
cd my-map
# Edit index.yaml and add at least one pin
# Optional: authorize Google Drive uploads
# when `photo` points to local files
# pnpm exec pinbook drive-auth
pnpm install
pnpm buildThen import .pinbook/map.kml into Google My Maps.
Optional AI skill:
npx skills add azat-io/pinbookWorkflow
- Create a new project with
pinbook create. - Edit
index.yaml. - Run
pnpm build. - Import
.pinbook/map.kmlinto Google My Maps. - Repeat as the trip plan changes.
Multi-File Maps
Pinbook also supports root-level imports so one map can be split across
multiple YAML files.
This works well for trips organized by city:
index.yaml
cities/
tokyo/
food.yaml
sights.yaml
kyoto.yaml
osaka.yamlTwo common conventions:
- layers for categories — group by
food,sights,entertainment(shown below) - layers for geography — group by city such as
tokyo,kyoto,osaka(used in the canonical example)
Both work well. Pick whichever makes the map easier to read at a glance.
Example root config with category-based layers:
map:
title: Japan Trip
layers:
- id: food
title: Food
- id: sights
title: Sights
imports:
- ./cities/tokyo/*.yaml
- ./cities/kyoto.yaml
- ./cities/osaka.yaml
pins: []Imported files may contain only pins:
pins:
- id: onibus-coffee-nakameguro
title: Onibus Coffee Nakameguro
address: Onibus Coffee Nakameguro, Tokyo
layer: foodMinimal Example
map:
title: Tokyo First Week
description: >
A first-pass map for a week in Tokyo.
layers:
- id: sights
title: Sights
pins:
- id: senso-ji
title: Senso-ji
address: Senso-ji Temple, Asakusa, Tokyo
layer: sights
color: red-500
icon: places-viewpoint
description: >
Good early-morning stop before the street gets crowded.
photo: https://example.com/photos/senso-ji.jpgConfig Shape
Pinbook maps use four top-level keys:
mapfor the map title and optional descriptionlayersfor optional logical groups such asfood,stay, orsightsimportsfor optional relative YAML paths or glob patternspinsfor the actual places that should appear on the map
Each pin should describe a real place using either:
addresswhen a clear human-readable address existscoordswhen no reliable address is available
Pins can also define color, icon, description, layer, and photo.
imports is root-only. Imported files may contain only pins.
Schema Stability
Pinbook treats the current YAML shape as the public map format.
For the future 1.x line:
- all current root keys and documented fields are considered stable
- new fields may be added in minor releases
- removing, renaming, or changing the meaning of an existing field requires a major release
Build Behavior
- If a pin includes
coords, Pinbook uses them as the final coordinates and does not geocode the pin. - If a pin includes
addressand does not includecoords, Pinbook may call the Google Geocoding API during build. - If a pin includes both
addressandcoords,coordsare authoritative and are used as the final coordinates. - If
importsis present, Pinbook expands the imported YAML files before final validation and build. - Resolved addresses are stored in the local resolution cache so later builds stay faster and more repeatable.
Photos
photo is supported as either:
- a single full public
http://orhttps://image URL - a single local image path such as
./photos/senso-ji.jpg - a list that mixes public URLs and local paths
During build, Pinbook includes those images in the generated placemark description so they can appear in Google My Maps after import.
When photo points to a local file, Pinbook uploads it to Google Drive during
build and rewrites it to a public URL before generating KML.
Before upload, Pinbook automatically normalizes local photos by rotating them when the image says it should be shown differently, cropping from the center to a fixed 3:2 frame, resizing them to a consistent size, and converting them to WebP.
By default, Pinbook creates or reuses this folder structure in Google Drive:
Pinbook/
<Map title>/For example, a map with map.title: Japan Trip uploads local photos into:
Pinbook/Japan TripIf GOOGLE_DRIVE_FOLDER_ID is set, Pinbook uses that folder as the parent and
creates or reuses:
<Configured folder>/<Map title>For imported YAML files, local photo paths are resolved relative to the file that declared them.
To authorize local photo uploads:
pnpm exec pinbook drive-authThat flow stores the Google Drive OAuth client ID, client secret, and refresh
token in the local project .env file.
Geocoding
If a pin has an address but no coords, Pinbook geocodes the address during
build.
To do that, set GOOGLE_MAPS_API_KEY in your environment or in a local .env
file inside the map project. In an interactive terminal, Pinbook can also ask
for the key and save it for you.
Resolved addresses are cached locally at
node_modules/.cache/pinbook/cache.json so repeated builds stay fast and
stable.
Google Drive Auth
Pinbook uses two separate Google integrations:
GOOGLE_MAPS_API_KEYfor address geocoding- Google Drive OAuth credentials for uploading local photo files
Google Drive uploads do not use a Drive API key.
Run pinbook drive-auth to save a refresh token locally, or provide these
variables in the local .env file:
GOOGLE_DRIVE_CLIENT_ID=your-google-oauth-client-id
GOOGLE_DRIVE_CLIENT_SECRET=your-google-oauth-client-secret
GOOGLE_DRIVE_REFRESH_TOKEN=your-google-drive-refresh-token
# Optional parent folder. Pinbook will then upload into
# <that folder>/<Map title> instead of Pinbook/<Map title>.
GOOGLE_DRIVE_FOLDER_ID=your-google-drive-folder-idUploaded photo metadata is cached locally at
node_modules/.cache/pinbook/photo-cache.json so unchanged files are not
uploaded again on every build. When a local photo changes, Pinbook uploads the
new version and removes the stale Google Drive file when its cached file id is
available.
pinbook drive-auth stores these values in the local project .env file next
to your YAML config and ensures that .env is ignored by Git.
Google Drive Setup
Use this once per Pinbook map project when you want to reference local photo
paths such as ./photos/senso-ji.jpg.
- Create or choose a Google Cloud project.
- Enable the Google Drive API for that project.
- Open
Google Auth Platform. - Complete the initial app setup in
Branding. For personal use, a simple app name and support email are enough. - Open
Audience. If you are using a personal Google account, chooseExternal. - Decide whether the app should stay in
Testingor move toProduction.Testingis fine for quick experiments, but Google limits it to test users and test-user authorizations expire after 7 days.Productionis better for long-lived personal use. - Open
Clientsand create an OAuth client with application typeDesktop app. - In your Pinbook project, run:
pnpm exec pinbook drive-auth- Paste the
Client IDandClient Secret. - Open the Google URL printed by Pinbook and finish the sign-in flow.
- Wait for the terminal message:
Google Drive auth saved to the local .env file.After that, pnpm build can upload local photos automatically.
Google Drive Notes
pinbook drive-authmust run in a local interactive terminal with access to a browser. Google explicitly documents desktop OAuth as a local flow.- Pinbook uses the
https://www.googleapis.com/auth/drive.filescope. Google classifies it asRecommended / Non-sensitive. - The OAuth exchange can take a little while after the browser says
Pinbook authorization complete. The refresh token is not saved until the terminal prints the success message.
Google Drive Troubleshooting
access_deniedafter sign-in usually means the app is still inTestingand your Google account was not added as a test user inAudience.- If local photos are uploaded into Drive root,
GOOGLE_DRIVE_FOLDER_IDis not set and the map title folder has not been created yet. Pinbook now createsPinbook/<Map title>automatically on the next build. - If unchanged photos are already in
node_modules/.cache/pinbook/photo-cache.json, Pinbook reuses their public URLs and skips re-uploading them. - If Pinbook reports that
photo-cache.jsonis invalid after an upgrade, delete it and run build again so the cache can be recreated in the new format.
Compatibility Target
Pinbook targets manual KML import into Google My Maps.
The generated KML is designed around that workflow, including Pinbook's color, icon, layer, and photo conventions.
Known Limitations
- Import behavior ultimately depends on Google My Maps.
- Rich import details such as folders, custom icon rendering, photos, and description HTML may vary with My Maps behavior.
- Large maps may hit Google My Maps import limits.
- Address-based builds depend on network access to Google Geocoding unless the required coordinates are already present in the local cache.
- Local photo uploads depend on Google Drive OAuth and public Drive download links.
- Imported files may contain only
pins; nested imports are not supported.
AI-Assisted Workflow
Pinbook publishes a repo-level skill that can be installed into coding agents with:
npx skills add azat-io/pinbookThat gives AI agents a reusable Pinbook reference for:
- the expected YAML shape
- supported fields
- color and icon conventions
- a consistent authoring style for map configs
Current Scope
Pinbook currently focuses on one job: building KML from YAML for Google My Maps.
That means:
- it exports KML for manual import into Google My Maps
- it does not sync directly with Google My Maps
- it can upload local pin photos to Google Drive during build
- it is optimized for travel-planning style maps with readable YAML configs
Contributing
See Contributing Guide.
License
MIT © Azat S.
