tripkit 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +14 -1
- package/agent/AGENT-SKILL.md +114 -68
- package/convert.js +84 -13
- package/examples/new-england-fall-2026.yaml +471 -0
- package/examples/nyc-long-weekend-2026.yaml +283 -0
- package/examples/oregon-spring-2026.yaml +2 -0
- package/examples/southwest-parks-2026.yaml +463 -0
- package/package.json +7 -2
- package/renderers/html/tripkit-renderer.html +90 -16
- package/schema/tripkit.schema.yaml +7 -0
- package/scripts/check-skill-coverage.js +66 -0
- package/validate.js +226 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# Southwest National Parks Loop 2026
|
|
3
|
+
# 5-day Utah / Arizona red-rock loop from Las Vegas
|
|
4
|
+
# Showcases: heavy hiking, desert palette, multi-park logistics
|
|
5
|
+
# ============================================================
|
|
6
|
+
|
|
7
|
+
trip:
|
|
8
|
+
title: "Southwest Parks Loop 2026"
|
|
9
|
+
subtitle: "Zion · Bryce · Page · Grand Canyon"
|
|
10
|
+
dates: "May 2–6, 2026"
|
|
11
|
+
total_days: 5
|
|
12
|
+
total_miles: "~900 mi"
|
|
13
|
+
total_stops: 13
|
|
14
|
+
travelers:
|
|
15
|
+
adults: 2
|
|
16
|
+
children: 0
|
|
17
|
+
origin: "Las Vegas, NV (fly in / fly out)"
|
|
18
|
+
origin_lat: 36.084
|
|
19
|
+
origin_lng: -115.154
|
|
20
|
+
vehicle: "Rental SUV"
|
|
21
|
+
|
|
22
|
+
days:
|
|
23
|
+
# ========== DAY 1 ==========
|
|
24
|
+
- number: 1
|
|
25
|
+
title: "Las Vegas → Zion"
|
|
26
|
+
date: "Saturday, May 2"
|
|
27
|
+
status: upcoming
|
|
28
|
+
color: "#c25e1f"
|
|
29
|
+
|
|
30
|
+
summary:
|
|
31
|
+
drive: "3 hrs"
|
|
32
|
+
hike: "1–2 hrs"
|
|
33
|
+
miles: "~165 mi"
|
|
34
|
+
|
|
35
|
+
weather:
|
|
36
|
+
high: "82°F"
|
|
37
|
+
low: "55°F"
|
|
38
|
+
sky: "Sunny, dry"
|
|
39
|
+
rain_chance: "0%"
|
|
40
|
+
note: "Bring a hat and 2L of water minimum — desert sun is no joke."
|
|
41
|
+
|
|
42
|
+
meals:
|
|
43
|
+
breakfast: "In-N-Out near LAS rental car return — quick fuel"
|
|
44
|
+
lunch: "Valley of Fire visitor center picnic area"
|
|
45
|
+
dinner: "Oscar's Cafe, Springdale — patio overlooks the canyon at sunset"
|
|
46
|
+
|
|
47
|
+
lodging:
|
|
48
|
+
name: "Best Western Plus Zion Canyon Inn & Suites"
|
|
49
|
+
location: "Springdale, Utah"
|
|
50
|
+
price_estimate: "~$220/night"
|
|
51
|
+
confirmation: "XXXXX1234"
|
|
52
|
+
booked: true
|
|
53
|
+
lat: 37.1924
|
|
54
|
+
lng: -112.9870
|
|
55
|
+
notes: "Walk to Zion shuttle stop. Pool, free breakfast, mountain views."
|
|
56
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=Best+Western+Plus+Zion+Canyon+Inn+Springdale+UT"
|
|
57
|
+
|
|
58
|
+
tips:
|
|
59
|
+
- "Park the rental in Springdale — Zion's canyon is shuttle-only Mar–Nov."
|
|
60
|
+
- "Watchman Trail is the perfect golden-hour hike — start at 5 PM, summit by sunset."
|
|
61
|
+
- "Refill water bottles at every visitor center; spigots are everywhere."
|
|
62
|
+
|
|
63
|
+
stops:
|
|
64
|
+
- name: "Valley of Fire State Park"
|
|
65
|
+
lat: 36.4307
|
|
66
|
+
lng: -114.5147
|
|
67
|
+
type: scenic
|
|
68
|
+
label: "Detour"
|
|
69
|
+
description: "60-min detour off I-15. Mars-red Aztec sandstone formations, petroglyphs, the Fire Wave. Drive the loop and stop at Mouse's Tank."
|
|
70
|
+
duration: "1.5–2 hrs"
|
|
71
|
+
parking_fee: "$15 NV non-resident"
|
|
72
|
+
kid_friendly: true
|
|
73
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=36.4307,-114.5147"
|
|
74
|
+
|
|
75
|
+
- name: "Zion Visitor Center"
|
|
76
|
+
lat: 37.2002
|
|
77
|
+
lng: -112.9871
|
|
78
|
+
type: scenic
|
|
79
|
+
label: "Stop"
|
|
80
|
+
description: "Pick up the shuttle here. Grab a Zion map, fill water bottles, check Angel's Landing permit status board."
|
|
81
|
+
duration: "20–30 min"
|
|
82
|
+
parking_fee: "$35 park fee (7-day) or America the Beautiful pass"
|
|
83
|
+
kid_friendly: true
|
|
84
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=37.2002,-112.9871"
|
|
85
|
+
|
|
86
|
+
- name: "Watchman Trail (sunset)"
|
|
87
|
+
lat: 37.1996
|
|
88
|
+
lng: -112.9871
|
|
89
|
+
type: hike
|
|
90
|
+
label: "Sunset"
|
|
91
|
+
description: "3.3-mile out-and-back from the visitor center. Modest 370 ft climb to a viewpoint over the West Temple and Watchman peak. Light up like fire at sunset."
|
|
92
|
+
duration: "2 hrs"
|
|
93
|
+
parking_fee: "Included with park fee"
|
|
94
|
+
kid_friendly: true
|
|
95
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=37.1996,-112.9871"
|
|
96
|
+
|
|
97
|
+
# ========== DAY 2 ==========
|
|
98
|
+
- number: 2
|
|
99
|
+
title: "Zion full day"
|
|
100
|
+
date: "Sunday, May 3"
|
|
101
|
+
status: upcoming
|
|
102
|
+
color: "#a04020"
|
|
103
|
+
|
|
104
|
+
summary:
|
|
105
|
+
drive: "Shuttle only"
|
|
106
|
+
hike: "5–7 hrs"
|
|
107
|
+
miles: "0"
|
|
108
|
+
|
|
109
|
+
weather:
|
|
110
|
+
high: "84°F"
|
|
111
|
+
low: "58°F"
|
|
112
|
+
sky: "Sunny, light wind"
|
|
113
|
+
rain_chance: "0%"
|
|
114
|
+
note: "Start hikes by 7 AM — the canyon walls trap heat by 11."
|
|
115
|
+
|
|
116
|
+
meals:
|
|
117
|
+
breakfast: "Hotel breakfast or Deep Creek Coffee, Springdale"
|
|
118
|
+
lunch: "Zion Lodge Red Rock Grill (sit on the lawn under cottonwoods)"
|
|
119
|
+
dinner: "Bit & Spur — Mexican, locally famous since the 80s"
|
|
120
|
+
|
|
121
|
+
lodging:
|
|
122
|
+
name: "Best Western Plus Zion Canyon Inn & Suites"
|
|
123
|
+
location: "Springdale, Utah"
|
|
124
|
+
price_estimate: "~$220/night"
|
|
125
|
+
confirmation: "XXXXX1234"
|
|
126
|
+
booked: true
|
|
127
|
+
lat: 37.1924
|
|
128
|
+
lng: -112.9870
|
|
129
|
+
notes: "Second night — keep the same room"
|
|
130
|
+
|
|
131
|
+
alerts:
|
|
132
|
+
- "🪨 Angel's Landing requires a permit (lottery + day-before). If you didn't get one, do Observation Point via East Mesa Trail instead — same view, no permit, less crowded."
|
|
133
|
+
|
|
134
|
+
tips:
|
|
135
|
+
- "Take the first shuttle (6 AM) to beat heat and crowds for Angel's Landing."
|
|
136
|
+
- "The Narrows from the bottom (Riverside Walk → Mystery Falls) is wadeable in May with rented neoprene boots from Zion Outfitter."
|
|
137
|
+
- "Emerald Pools loop is a perfect afternoon recovery hike — shaded, gentle, gorgeous."
|
|
138
|
+
|
|
139
|
+
stops:
|
|
140
|
+
- name: "Angel's Landing"
|
|
141
|
+
lat: 37.2581
|
|
142
|
+
lng: -112.9514
|
|
143
|
+
type: hike
|
|
144
|
+
label: "Big hike"
|
|
145
|
+
description: "5.4 miles, 1,488 ft climb. Walter's Wiggles switchbacks then the chain section ridge. Permit required. One of the most exposed hikes in any US park — not for vertigo."
|
|
146
|
+
duration: "4–5 hrs"
|
|
147
|
+
reservation_required: true
|
|
148
|
+
reservation_url: "https://www.recreation.gov/permits/angels-landing"
|
|
149
|
+
kid_friendly: false
|
|
150
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=37.2581,-112.9514"
|
|
151
|
+
|
|
152
|
+
- name: "Emerald Pools Loop"
|
|
153
|
+
lat: 37.2497
|
|
154
|
+
lng: -112.9588
|
|
155
|
+
type: hike
|
|
156
|
+
label: "Easy"
|
|
157
|
+
description: "Lower (paved, 1.2 mi) → Middle → Upper. Hanging gardens, seep springs, water curtain over an alcove. Best in the afternoon when sun hits the upper bowl."
|
|
158
|
+
duration: "1.5–2 hrs"
|
|
159
|
+
kid_friendly: true
|
|
160
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=37.2497,-112.9588"
|
|
161
|
+
|
|
162
|
+
- name: "Riverside Walk → Narrows mouth"
|
|
163
|
+
lat: 37.2856
|
|
164
|
+
lng: -112.9479
|
|
165
|
+
type: scenic
|
|
166
|
+
label: "Stroll"
|
|
167
|
+
description: "Paved 2-mile out-and-back to where the Virgin River disappears between 1,000 ft cliffs. Wade in 50 ft if you brought water shoes."
|
|
168
|
+
duration: "1–1.5 hrs"
|
|
169
|
+
kid_friendly: true
|
|
170
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=37.2856,-112.9479"
|
|
171
|
+
|
|
172
|
+
# ========== DAY 3 ==========
|
|
173
|
+
- number: 3
|
|
174
|
+
title: "Zion → Bryce → Page"
|
|
175
|
+
date: "Monday, May 4"
|
|
176
|
+
status: upcoming
|
|
177
|
+
color: "#d97a3c"
|
|
178
|
+
|
|
179
|
+
summary:
|
|
180
|
+
drive: "5 hrs"
|
|
181
|
+
hike: "2 hrs"
|
|
182
|
+
miles: "~270 mi"
|
|
183
|
+
|
|
184
|
+
weather:
|
|
185
|
+
high: "72°F (Bryce 8000 ft)"
|
|
186
|
+
low: "38°F"
|
|
187
|
+
sky: "Sunny, breezy at altitude"
|
|
188
|
+
rain_chance: "10%"
|
|
189
|
+
note: "Bryce is 4,000 ft higher than Zion — bring a fleece even in May."
|
|
190
|
+
|
|
191
|
+
meals:
|
|
192
|
+
breakfast: "Hotel breakfast"
|
|
193
|
+
lunch: "Bryce Canyon Lodge dining room — cafeteria + sit-down side"
|
|
194
|
+
dinner: "El Tapatio, Page — best Mexican in town, walk from hotel"
|
|
195
|
+
|
|
196
|
+
lodging:
|
|
197
|
+
name: "Best Western View of Lake Powell"
|
|
198
|
+
location: "Page, Arizona"
|
|
199
|
+
price_estimate: "~$165/night"
|
|
200
|
+
confirmation: "XXXXX5678"
|
|
201
|
+
booked: true
|
|
202
|
+
lat: 36.9097
|
|
203
|
+
lng: -111.4587
|
|
204
|
+
notes: "Hilltop with Lake Powell views. Pool, free breakfast, easy Antelope Canyon shuttle pickup."
|
|
205
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=Best+Western+View+of+Lake+Powell+Page+AZ"
|
|
206
|
+
|
|
207
|
+
tips:
|
|
208
|
+
- "Drive Hwy 12 — one of America's prettiest scenic byways. Slot in 30 min for the Hogsback ridge between Boulder and Escalante."
|
|
209
|
+
- "At Bryce, walk the rim from Sunrise to Sunset Point (1 mi) for the best free view of the amphitheater."
|
|
210
|
+
- "Time the Page arrival for sunset at Horseshoe Bend tomorrow morning — too crowded after 9 AM."
|
|
211
|
+
|
|
212
|
+
stops:
|
|
213
|
+
- name: "Bryce Canyon — Sunrise & Sunset Points"
|
|
214
|
+
lat: 37.6248
|
|
215
|
+
lng: -112.1620
|
|
216
|
+
type: scenic
|
|
217
|
+
label: "Rim walk"
|
|
218
|
+
description: "Two of the most-photographed viewpoints on the rim. 1-mile flat connector along the edge looks down on Thor's Hammer and the Silent City of hoodoos."
|
|
219
|
+
duration: "30–45 min"
|
|
220
|
+
kid_friendly: true
|
|
221
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=37.6248,-112.1620"
|
|
222
|
+
|
|
223
|
+
- name: "Navajo Loop / Queen's Garden"
|
|
224
|
+
lat: 37.6238
|
|
225
|
+
lng: -112.1640
|
|
226
|
+
type: hike
|
|
227
|
+
label: "Hoodoo hike"
|
|
228
|
+
description: "Best 3-mile hike in any US park, full stop. Drop down Wall Street switchbacks, weave through hoodoos, come back up Queen's Garden. Do it counter-clockwise."
|
|
229
|
+
duration: "2 hrs"
|
|
230
|
+
kid_friendly: true
|
|
231
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=37.6238,-112.1640"
|
|
232
|
+
|
|
233
|
+
# ========== DAY 4 ==========
|
|
234
|
+
- number: 4
|
|
235
|
+
title: "Page → Grand Canyon South Rim"
|
|
236
|
+
date: "Tuesday, May 5"
|
|
237
|
+
status: upcoming
|
|
238
|
+
color: "#7d3c1a"
|
|
239
|
+
|
|
240
|
+
summary:
|
|
241
|
+
drive: "3 hrs"
|
|
242
|
+
hike: "1.5 hrs"
|
|
243
|
+
miles: "~140 mi"
|
|
244
|
+
|
|
245
|
+
weather:
|
|
246
|
+
high: "75°F (rim)"
|
|
247
|
+
low: "42°F"
|
|
248
|
+
sky: "Clear"
|
|
249
|
+
rain_chance: "5%"
|
|
250
|
+
note: "Sunset at Hopi Point is the picture you've seen on every postcard."
|
|
251
|
+
|
|
252
|
+
meals:
|
|
253
|
+
breakfast: "Hotel breakfast — early start for Horseshoe Bend"
|
|
254
|
+
lunch: "Cameron Trading Post (en route) — Navajo tacos in a 1916 stone post"
|
|
255
|
+
dinner: "El Tovar Dining Room (book ahead) or Bright Angel Restaurant"
|
|
256
|
+
|
|
257
|
+
lodging:
|
|
258
|
+
name: "Best Western Premier Grand Canyon Squire Inn"
|
|
259
|
+
location: "Tusayan, Arizona"
|
|
260
|
+
price_estimate: "~$280/night"
|
|
261
|
+
confirmation: "XXXXX9012"
|
|
262
|
+
booked: true
|
|
263
|
+
lat: 35.9684
|
|
264
|
+
lng: -112.1218
|
|
265
|
+
notes: "1 mile south of the park entrance. Bowling alley, two pools, free shuttle into the park."
|
|
266
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=Best+Western+Premier+Grand+Canyon+Squire+Inn+Tusayan+AZ"
|
|
267
|
+
|
|
268
|
+
alerts:
|
|
269
|
+
- "📅 Antelope Canyon entry is by Navajo guide only — book the Lower Canyon tour 4–6 weeks ahead through Ken's Tours or Dixie Ellis'."
|
|
270
|
+
|
|
271
|
+
tips:
|
|
272
|
+
- "Horseshoe Bend opens at sunrise — be there by 6:30 AM to avoid tour bus crush."
|
|
273
|
+
- "Stop at the Watchtower (Desert View) on the way in — eastern entrance to GCNP saves 30 min vs going through Tusayan first."
|
|
274
|
+
- "Sunset shuttle from Bright Angel Lodge to Hopi Point runs every 15 min, free."
|
|
275
|
+
|
|
276
|
+
stops:
|
|
277
|
+
- name: "Horseshoe Bend"
|
|
278
|
+
lat: 36.8625
|
|
279
|
+
lng: -111.5103
|
|
280
|
+
type: scenic
|
|
281
|
+
label: "Sunrise"
|
|
282
|
+
description: "1.5-mile round trip from the parking lot to the overlook. The Colorado River carves a 270° loop around a sandstone fin 1,000 ft below. Bring a wide-angle lens."
|
|
283
|
+
duration: "1 hr"
|
|
284
|
+
parking_fee: "$10 City of Page"
|
|
285
|
+
kid_friendly: true
|
|
286
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=36.8625,-111.5103"
|
|
287
|
+
|
|
288
|
+
- name: "Lower Antelope Canyon"
|
|
289
|
+
lat: 36.8619
|
|
290
|
+
lng: -111.4133
|
|
291
|
+
type: scenic
|
|
292
|
+
label: "Slot canyon"
|
|
293
|
+
description: "Guided tour through a slot canyon carved by flash floods. Wavy walls glow orange and purple in shafts of sunlight. Easier than Upper for photos (less crowded, cheaper)."
|
|
294
|
+
duration: "1.5 hrs"
|
|
295
|
+
reservation_required: true
|
|
296
|
+
reservation_url: "https://lowerantelope.com"
|
|
297
|
+
parking_fee: "Included in tour"
|
|
298
|
+
kid_friendly: true
|
|
299
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=36.8619,-111.4133"
|
|
300
|
+
|
|
301
|
+
- name: "Mather Point (sunset)"
|
|
302
|
+
lat: 36.0613
|
|
303
|
+
lng: -112.1080
|
|
304
|
+
type: scenic
|
|
305
|
+
label: "Sunset"
|
|
306
|
+
description: "First view of the Grand Canyon for most arriving visitors, and still the best for impact. 5-min walk from the visitor center."
|
|
307
|
+
duration: "30–45 min"
|
|
308
|
+
kid_friendly: true
|
|
309
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=36.0613,-112.1080"
|
|
310
|
+
|
|
311
|
+
# ========== DAY 5 ==========
|
|
312
|
+
- number: 5
|
|
313
|
+
title: "Grand Canyon → Las Vegas"
|
|
314
|
+
date: "Wednesday, May 6"
|
|
315
|
+
status: upcoming
|
|
316
|
+
color: "#5c2a14"
|
|
317
|
+
|
|
318
|
+
summary:
|
|
319
|
+
drive: "4.5 hrs"
|
|
320
|
+
hike: "1.5–2 hrs"
|
|
321
|
+
miles: "~280 mi"
|
|
322
|
+
|
|
323
|
+
weather:
|
|
324
|
+
high: "78°F"
|
|
325
|
+
low: "44°F"
|
|
326
|
+
sky: "Clear, gusty PM"
|
|
327
|
+
rain_chance: "0%"
|
|
328
|
+
note: "Last day — pace it for the LAS evening flight."
|
|
329
|
+
|
|
330
|
+
meals:
|
|
331
|
+
breakfast: "Bright Angel Lodge — sunrise window seat if you can get one"
|
|
332
|
+
lunch: "Hoover Dam visitor center cafeteria, or push through to Boulder City for In-N-Out"
|
|
333
|
+
dinner: "Vegas — depends on flight time"
|
|
334
|
+
|
|
335
|
+
lodging:
|
|
336
|
+
name: "Home"
|
|
337
|
+
location: "Returning home"
|
|
338
|
+
booked: true
|
|
339
|
+
lat: 36.0840
|
|
340
|
+
lng: -115.1537
|
|
341
|
+
notes: "Flight out of LAS — return rental by 7 PM"
|
|
342
|
+
|
|
343
|
+
tips:
|
|
344
|
+
- "Bright Angel Trail down to 1.5-mile Resthouse and back is a perfect taste of below-the-rim without committing to Plateau Point."
|
|
345
|
+
- "Hoover Dam adds 20 min off Hwy 93 — totally worth it. Don't bother with the dam tour, just walk across the bridge."
|
|
346
|
+
- "Drop the rental 2 hrs before your flight — LAS rental return shuttle runs slow."
|
|
347
|
+
|
|
348
|
+
stops:
|
|
349
|
+
- name: "Bright Angel Trail (1.5-mile Resthouse)"
|
|
350
|
+
lat: 36.0578
|
|
351
|
+
lng: -112.1438
|
|
352
|
+
type: hike
|
|
353
|
+
label: "Half-day"
|
|
354
|
+
description: "Out-and-back to the 1.5-mile Resthouse — 3 miles, 1,100 ft of elevation (mostly down on the way in, all up on the way out). Mules pass you. Real canyon experience."
|
|
355
|
+
duration: "2 hrs"
|
|
356
|
+
kid_friendly: true
|
|
357
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=36.0578,-112.1438"
|
|
358
|
+
|
|
359
|
+
- name: "Hoover Dam"
|
|
360
|
+
lat: 36.0162
|
|
361
|
+
lng: -114.7377
|
|
362
|
+
type: scenic
|
|
363
|
+
label: "Engineering"
|
|
364
|
+
description: "Walk across the Mike O'Callaghan–Pat Tillman Memorial Bridge for the best photo of the dam. Free, 15 min, blows minds."
|
|
365
|
+
duration: "30–45 min"
|
|
366
|
+
parking_fee: "$10 dam parking, or free at the bridge lot"
|
|
367
|
+
kid_friendly: true
|
|
368
|
+
navigate_url: "https://www.google.com/maps/dir/?api=1&destination=36.0162,-114.7377"
|
|
369
|
+
|
|
370
|
+
# ============================================================
|
|
371
|
+
# ROUTES — explicit polylines that follow real road geometry
|
|
372
|
+
# (I-15, US-89, UT-9, UT-12, US-93). Auto-generation works fine
|
|
373
|
+
# without this block, but hand-curated waypoints look smoother.
|
|
374
|
+
# ============================================================
|
|
375
|
+
routes:
|
|
376
|
+
# Day 1: Vegas → Valley of Fire → I-15 → St. George → Springdale
|
|
377
|
+
- day: 1
|
|
378
|
+
color: "#c25e1f"
|
|
379
|
+
width: 4
|
|
380
|
+
points:
|
|
381
|
+
- [36.084, -115.154]
|
|
382
|
+
- [36.430, -114.515]
|
|
383
|
+
- [37.097, -113.568]
|
|
384
|
+
- [37.175, -113.283]
|
|
385
|
+
- [37.192, -112.987]
|
|
386
|
+
|
|
387
|
+
# Day 2: Zion canyon shuttle loop
|
|
388
|
+
- day: 2
|
|
389
|
+
color: "#a04020"
|
|
390
|
+
width: 4
|
|
391
|
+
points:
|
|
392
|
+
- [37.192, -112.987]
|
|
393
|
+
- [37.258, -112.951]
|
|
394
|
+
- [37.250, -112.959]
|
|
395
|
+
- [37.286, -112.948]
|
|
396
|
+
- [37.192, -112.987]
|
|
397
|
+
|
|
398
|
+
# Day 3: Springdale → Bryce → Kanab → Page
|
|
399
|
+
- day: 3
|
|
400
|
+
color: "#d97a3c"
|
|
401
|
+
width: 4
|
|
402
|
+
points:
|
|
403
|
+
- [37.192, -112.987]
|
|
404
|
+
- [37.342, -112.642]
|
|
405
|
+
- [37.524, -112.227]
|
|
406
|
+
- [37.624, -112.162]
|
|
407
|
+
- [37.524, -112.227]
|
|
408
|
+
- [37.045, -112.527]
|
|
409
|
+
- [36.910, -111.459]
|
|
410
|
+
|
|
411
|
+
# Day 4: Page → Horseshoe → Antelope → Cameron → Grand Canyon → Tusayan
|
|
412
|
+
- day: 4
|
|
413
|
+
color: "#7d3c1a"
|
|
414
|
+
width: 4
|
|
415
|
+
points:
|
|
416
|
+
- [36.910, -111.459]
|
|
417
|
+
- [36.862, -111.510]
|
|
418
|
+
- [36.862, -111.413]
|
|
419
|
+
- [35.875, -111.413]
|
|
420
|
+
- [36.061, -112.108]
|
|
421
|
+
- [35.968, -112.122]
|
|
422
|
+
|
|
423
|
+
# Day 5: Tusayan → Bright Angel → Williams → Kingman → Hoover → Vegas
|
|
424
|
+
- day: 5
|
|
425
|
+
color: "#5c2a14"
|
|
426
|
+
width: 4
|
|
427
|
+
points:
|
|
428
|
+
- [35.968, -112.122]
|
|
429
|
+
- [36.058, -112.144]
|
|
430
|
+
- [35.250, -112.190]
|
|
431
|
+
- [35.189, -114.053]
|
|
432
|
+
- [36.016, -114.738]
|
|
433
|
+
- [36.084, -115.154]
|
|
434
|
+
|
|
435
|
+
theme:
|
|
436
|
+
font_family: "DM Sans, sans-serif"
|
|
437
|
+
accent_color: "#c25e1f"
|
|
438
|
+
map_style: "terrain"
|
|
439
|
+
|
|
440
|
+
agent_context:
|
|
441
|
+
preferences:
|
|
442
|
+
pace: "moderate"
|
|
443
|
+
budget: "mid-range"
|
|
444
|
+
accommodation_chain: "Best Western"
|
|
445
|
+
interests:
|
|
446
|
+
- "national parks"
|
|
447
|
+
- "hiking"
|
|
448
|
+
- "geology"
|
|
449
|
+
- "photography"
|
|
450
|
+
constraints:
|
|
451
|
+
max_drive_per_day: "5 hours"
|
|
452
|
+
must_see:
|
|
453
|
+
- "Angel's Landing or Observation Point"
|
|
454
|
+
- "Bryce hoodoos"
|
|
455
|
+
- "Horseshoe Bend at sunrise"
|
|
456
|
+
- "Grand Canyon South Rim sunset"
|
|
457
|
+
iteration_log:
|
|
458
|
+
- date: "2026-04-15"
|
|
459
|
+
change: "Original plan included North Rim — dropped because it doesn't open until May 15."
|
|
460
|
+
- date: "2026-04-18"
|
|
461
|
+
change: "Swapped Upper Antelope for Lower Antelope — half the price, better photos, easier ladders."
|
|
462
|
+
- date: "2026-04-22"
|
|
463
|
+
change: "Added Hoover Dam to Day 5 — only adds 20 min and makes the LAS approach more interesting."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tripkit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Open-source framework for AI-assisted trip planning with beautiful interactive visualizers",
|
|
5
5
|
"main": "convert.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,9 +8,14 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"convert.js",
|
|
11
|
+
"validate.js",
|
|
12
|
+
"scripts/check-skill-coverage.js",
|
|
11
13
|
"renderers/html/tripkit-renderer.html",
|
|
12
14
|
"schema/tripkit.schema.yaml",
|
|
13
15
|
"examples/oregon-spring-2026.yaml",
|
|
16
|
+
"examples/southwest-parks-2026.yaml",
|
|
17
|
+
"examples/nyc-long-weekend-2026.yaml",
|
|
18
|
+
"examples/new-england-fall-2026.yaml",
|
|
14
19
|
"agent/questionnaire.yaml",
|
|
15
20
|
"agent/AGENT-SKILL.md",
|
|
16
21
|
"README.md",
|
|
@@ -20,7 +25,7 @@
|
|
|
20
25
|
"scripts": {
|
|
21
26
|
"convert": "node convert.js",
|
|
22
27
|
"example": "node convert.js examples/oregon-spring-2026.yaml examples/oregon-spring-2026.html",
|
|
23
|
-
"test": "node convert.js examples/oregon-spring-2026.yaml /tmp/tripkit-test.html && test -s /tmp/tripkit-test.html && echo 'OK'",
|
|
28
|
+
"test": "node convert.js examples/oregon-spring-2026.yaml /tmp/tripkit-test.html && test -s /tmp/tripkit-test.html && node scripts/check-skill-coverage.js && echo 'OK'",
|
|
24
29
|
"prepublishOnly": "npm test"
|
|
25
30
|
},
|
|
26
31
|
"keywords": [
|
|
@@ -18,6 +18,7 @@ html,body{height:100%;overflow:hidden}
|
|
|
18
18
|
body{font-family:var(--font);background:var(--bg);color:var(--text)}
|
|
19
19
|
.app{display:grid;grid-template-columns:460px 1fr;height:100vh}
|
|
20
20
|
@media(max-width:960px){.app{grid-template-columns:1fr;grid-template-rows:55vh 45vh}}
|
|
21
|
+
.legend-toggle{display:none}
|
|
21
22
|
.sidebar{display:flex;flex-direction:column;background:var(--card);border-right:1px solid var(--border);overflow:hidden}
|
|
22
23
|
.sidebar-scroll{overflow-y:auto;flex:1;scroll-behavior:smooth}
|
|
23
24
|
.sidebar-scroll::-webkit-scrollbar{width:5px}
|
|
@@ -93,6 +94,22 @@ body{font-family:var(--font);background:var(--bg);color:var(--text)}
|
|
|
93
94
|
border:2px solid #1b5e3b;font-family:var(--font);cursor:pointer;transition:transform .12s;letter-spacing:-.02em}
|
|
94
95
|
.hm:hover{transform:scale(1.15)}
|
|
95
96
|
.pp-conf{font-size:11px;font-weight:600;color:var(--accent);background:var(--accent-soft);padding:4px 8px;border-radius:4px;display:inline-block;margin-top:6px;letter-spacing:.02em}
|
|
97
|
+
.endpin{display:flex;align-items:center;justify-content:center;width:28px;height:34px;font-size:11px;font-weight:700;color:#fff;font-family:var(--font);border-radius:14px 14px 14px 2px;transform:rotate(-45deg);box-shadow:0 2px 6px rgba(0,0,0,.35);border:2px solid #fff;cursor:pointer}
|
|
98
|
+
.endpin span{transform:rotate(45deg);letter-spacing:.02em}
|
|
99
|
+
.endpin.start{background:#1b5e3b}
|
|
100
|
+
.endpin.end{background:#7a1f1f}
|
|
101
|
+
@media(max-width:480px){
|
|
102
|
+
.hero{height:140px}.hero-text{padding:12px 14px}.hero-text h1{font-size:20px}
|
|
103
|
+
.day-nav{padding:8px 10px;gap:1px}.day-btn{padding:5px 8px;font-size:10px}
|
|
104
|
+
.day-panel{padding:12px 14px 32px}
|
|
105
|
+
.legend{bottom:auto;top:52px;left:8px;right:auto;padding:6px 10px;font-size:9px;line-height:1.6;max-width:60%}
|
|
106
|
+
.legend.collapsed .lr,.legend.collapsed > div:not(.legend-toggle){display:none}
|
|
107
|
+
.legend-toggle{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:10px;font-weight:600;user-select:none;color:var(--text)}
|
|
108
|
+
.legend-toggle::after{content:'▾';font-size:9px;transition:transform .15s;color:var(--muted)}
|
|
109
|
+
.legend.collapsed .legend-toggle::after{transform:rotate(-90deg)}
|
|
110
|
+
.map-ctrl{top:8px;right:8px}.mbtn{padding:5px 8px;font-size:9px}
|
|
111
|
+
.fullbtn{bottom:10px;right:8px;padding:5px 10px;font-size:10px}
|
|
112
|
+
}
|
|
96
113
|
</style>
|
|
97
114
|
</head>
|
|
98
115
|
<body>
|
|
@@ -316,15 +333,25 @@ const TRIP_DATA = {};
|
|
|
316
333
|
L.polyline(r.points, { color: r.color || '#666', weight: r.width || 4, opacity: 0.8 }).addTo(map);
|
|
317
334
|
});
|
|
318
335
|
} else {
|
|
319
|
-
// Auto-generate routes
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
336
|
+
// Auto-generate routes. For each day, the polyline runs:
|
|
337
|
+
// prev day's lodging (if any) → today's stops → today's lodging (if any)
|
|
338
|
+
// This stitches consecutive days together so the map reads as one continuous trip.
|
|
339
|
+
const isAnchorLodging = (l) => l && l.lat && l.lng && l.name !== 'Home';
|
|
340
|
+
days.forEach((d, di) => {
|
|
341
|
+
const pts = [];
|
|
342
|
+
const prevLodging = di > 0 ? days[di - 1].lodging : null;
|
|
343
|
+
if (isAnchorLodging(prevLodging)) pts.push([prevLodging.lat, prevLodging.lng]);
|
|
344
|
+
(d.stops || []).forEach(s => { if (s.lat && s.lng) pts.push([s.lat, s.lng]); });
|
|
345
|
+
if (isAnchorLodging(d.lodging)) {
|
|
346
|
+
const last = pts[pts.length - 1];
|
|
347
|
+
if (!last || last[0] !== d.lodging.lat || last[1] !== d.lodging.lng) {
|
|
348
|
+
pts.push([d.lodging.lat, d.lodging.lng]);
|
|
326
349
|
}
|
|
327
350
|
}
|
|
351
|
+
if (pts.length > 1) {
|
|
352
|
+
L.polyline(pts, { color: '#fff', weight: 7, opacity: 0.4 }).addTo(map);
|
|
353
|
+
L.polyline(pts, { color: d.color || '#666', weight: 4, opacity: 0.8 }).addTo(map);
|
|
354
|
+
}
|
|
328
355
|
});
|
|
329
356
|
}
|
|
330
357
|
|
|
@@ -356,8 +383,34 @@ const TRIP_DATA = {};
|
|
|
356
383
|
markers.push(dm);
|
|
357
384
|
});
|
|
358
385
|
|
|
386
|
+
// --- Start / End pins (origin & destination) ---
|
|
387
|
+
function endpoint(lat, lng, kind, name) {
|
|
388
|
+
const labelTxt = kind === 'start' ? 'A' : 'B';
|
|
389
|
+
const titleTxt = kind === 'start' ? 'Start' : 'End';
|
|
390
|
+
const icon = L.divIcon({
|
|
391
|
+
className: '',
|
|
392
|
+
html: `<div class="endpin ${kind}"><span>${labelTxt}</span></div>`,
|
|
393
|
+
iconSize: [28, 34], iconAnchor: [4, 32]
|
|
394
|
+
});
|
|
395
|
+
const m = L.marker([lat, lng], { icon, zIndexOffset: 800 }).addTo(map);
|
|
396
|
+
m.bindPopup(`<div class="pp-body" style="padding:14px">
|
|
397
|
+
<div class="pp-day">${titleTxt}</div>
|
|
398
|
+
<div class="pp-name">${name || titleTxt}</div>
|
|
399
|
+
</div>`, { maxWidth: 240 });
|
|
400
|
+
}
|
|
401
|
+
if (Number.isFinite(trip.origin_lat) && Number.isFinite(trip.origin_lng)) {
|
|
402
|
+
endpoint(trip.origin_lat, trip.origin_lng, 'start', trip.origin);
|
|
403
|
+
const destLat = Number.isFinite(trip.destination_lat) ? trip.destination_lat : trip.origin_lat;
|
|
404
|
+
const destLng = Number.isFinite(trip.destination_lng) ? trip.destination_lng : trip.origin_lng;
|
|
405
|
+
// Suppress the "End" pin if it's at the same coords as the Start (round trip).
|
|
406
|
+
if (Math.abs(destLat - trip.origin_lat) > 0.001 || Math.abs(destLng - trip.origin_lng) > 0.001) {
|
|
407
|
+
endpoint(destLat, destLng, 'end', trip.destination || trip.origin);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
359
411
|
// --- Hotel markers ---
|
|
360
|
-
|
|
412
|
+
// Build per-night list, then dedupe by lat/lng so multi-night stays render as one marker.
|
|
413
|
+
const nights = days
|
|
361
414
|
.filter(d => d.lodging && d.lodging.lat && d.lodging.lng && d.lodging.name !== 'Home')
|
|
362
415
|
.map((d, i) => ({
|
|
363
416
|
name: d.lodging.name,
|
|
@@ -371,11 +424,24 @@ const TRIP_DATA = {};
|
|
|
371
424
|
navigate_url: d.lodging.navigate_url
|
|
372
425
|
}));
|
|
373
426
|
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
427
|
+
const hotelGroups = new Map();
|
|
428
|
+
nights.forEach(n => {
|
|
429
|
+
const key = n.lat.toFixed(4) + ',' + n.lng.toFixed(4);
|
|
430
|
+
if (!hotelGroups.has(key)) hotelGroups.set(key, []);
|
|
431
|
+
hotelGroups.get(key).push(n);
|
|
432
|
+
});
|
|
433
|
+
const hotels = Array.from(hotelGroups.values());
|
|
434
|
+
|
|
435
|
+
const hotelLabel = theme.hotel_label
|
|
436
|
+
|| (data.agent_context?.preferences?.accommodation_chain
|
|
437
|
+
? data.agent_context.preferences.accommodation_chain.split(' ')[0].substring(0, 3).toUpperCase()
|
|
438
|
+
: '🏨');
|
|
439
|
+
|
|
440
|
+
hotels.forEach(group => {
|
|
441
|
+
const h = group[0];
|
|
442
|
+
const ns = group.map(g => g.night);
|
|
443
|
+
const nightLabel = ns.length === 1 ? `Night ${ns[0]}` : `Nights ${ns[0]}–${ns[ns.length - 1]}`;
|
|
444
|
+
const dateLabel = group.length === 1 ? h.day : `${h.day} – ${group[group.length - 1].day}`;
|
|
379
445
|
const icon = L.divIcon({
|
|
380
446
|
className: '',
|
|
381
447
|
html: `<div class="hm">${hotelLabel}</div>`,
|
|
@@ -383,7 +449,7 @@ const TRIP_DATA = {};
|
|
|
383
449
|
});
|
|
384
450
|
const m = L.marker([h.lat, h.lng], { icon, zIndexOffset: 1000 }).addTo(map);
|
|
385
451
|
let popupHTML = `<div class="pp-body" style="padding:16px">
|
|
386
|
-
<div class="pp-day"
|
|
452
|
+
<div class="pp-day">${nightLabel} · ${dateLabel}</div>
|
|
387
453
|
<div class="pp-name">${h.name}</div>
|
|
388
454
|
<div style="font-size:12px;color:var(--muted);margin-bottom:6px">${h.loc}</div>`;
|
|
389
455
|
if (h.booked && h.conf) popupHTML += `<div class="pp-conf">Conf# ${h.conf}</div>`;
|
|
@@ -394,7 +460,7 @@ const TRIP_DATA = {};
|
|
|
394
460
|
|
|
395
461
|
// --- Legend ---
|
|
396
462
|
const legendEl = document.getElementById('legend');
|
|
397
|
-
let legendHTML = '<
|
|
463
|
+
let legendHTML = '<div class="legend-toggle"><span>Route segments</span></div>';
|
|
398
464
|
days.forEach(d => {
|
|
399
465
|
legendHTML += `<div class="lr"><i style="background:${d.color || '#666'}"></i>Day ${d.number} · ${d.title.split('→')[0].replace('✅ ','').trim()}</div>`;
|
|
400
466
|
});
|
|
@@ -404,9 +470,17 @@ const TRIP_DATA = {};
|
|
|
404
470
|
legendHTML += `Booked hotels (${hotels.length})</div></div>`;
|
|
405
471
|
}
|
|
406
472
|
legendEl.innerHTML = legendHTML;
|
|
473
|
+
if (window.matchMedia('(max-width: 480px)').matches) legendEl.classList.add('collapsed');
|
|
474
|
+
legendEl.querySelector('.legend-toggle')?.addEventListener('click', () => legendEl.classList.toggle('collapsed'));
|
|
407
475
|
|
|
408
476
|
// --- Fit bounds ---
|
|
409
|
-
|
|
477
|
+
// Include stops, hotels, and origin/destination pins so the initial fit covers everything.
|
|
478
|
+
const allPts = [
|
|
479
|
+
...days.flatMap(d => (d.stops || []).filter(s => s.lat && s.lng).map(s => [s.lat, s.lng])),
|
|
480
|
+
...days.flatMap(d => (d.lodging && d.lodging.lat && d.lodging.lng && d.lodging.name !== 'Home') ? [[d.lodging.lat, d.lodging.lng]] : []),
|
|
481
|
+
];
|
|
482
|
+
if (Number.isFinite(trip.origin_lat) && Number.isFinite(trip.origin_lng)) allPts.push([trip.origin_lat, trip.origin_lng]);
|
|
483
|
+
if (Number.isFinite(trip.destination_lat) && Number.isFinite(trip.destination_lng)) allPts.push([trip.destination_lat, trip.destination_lng]);
|
|
410
484
|
const fullBounds = L.latLngBounds(allPts).pad(0.06);
|
|
411
485
|
map.fitBounds(fullBounds);
|
|
412
486
|
|
|
@@ -18,6 +18,10 @@ trip:
|
|
|
18
18
|
children: integer
|
|
19
19
|
ages: string # e.g. "Kids: 14, 17"
|
|
20
20
|
origin: string # e.g. "Folsom, CA"
|
|
21
|
+
origin_lat: number # Optional — if set with origin_lng, renders a "Start" pin on the map
|
|
22
|
+
origin_lng: number
|
|
23
|
+
destination_lat: number # Optional — if absent, defaults to origin coords (round trip)
|
|
24
|
+
destination_lng: number # Set explicitly only if the trip ends somewhere different from where it started
|
|
21
25
|
vehicle: string # e.g. "SUV", "Sedan", "RV"
|
|
22
26
|
|
|
23
27
|
# ============================================================
|
|
@@ -102,6 +106,9 @@ theme:
|
|
|
102
106
|
map_style: string # "terrain" | "satellite" | "topo" | "street"
|
|
103
107
|
dark_mode: boolean # Default: false
|
|
104
108
|
hero_style: string # "photo" | "gradient" | "minimal"
|
|
109
|
+
hotel_label: string # Optional 1–4 char label for hotel markers (e.g. "MAR", "BW", "INN").
|
|
110
|
+
# Falls back to first 3 chars of agent_context.preferences.accommodation_chain,
|
|
111
|
+
# then 🏨 emoji.
|
|
105
112
|
|
|
106
113
|
# ============================================================
|
|
107
114
|
# AGENT CONTEXT — metadata for the planning agent
|