sbb-mcp 0.4.3 → 0.5.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.
Files changed (83) hide show
  1. package/LICENSE +50 -57
  2. package/README.md +25 -214
  3. package/dist/index.js +47 -19
  4. package/package.json +10 -33
  5. package/dist/auth.d.ts +0 -2
  6. package/dist/auth.js +0 -44
  7. package/dist/auth.js.map +0 -1
  8. package/dist/cache.d.ts +0 -14
  9. package/dist/cache.js +0 -62
  10. package/dist/cache.js.map +0 -1
  11. package/dist/client.d.ts +0 -17
  12. package/dist/client.js +0 -70
  13. package/dist/client.js.map +0 -1
  14. package/dist/formatters.d.ts +0 -35
  15. package/dist/formatters.js +0 -285
  16. package/dist/formatters.js.map +0 -1
  17. package/dist/http.d.ts +0 -2
  18. package/dist/http.js +0 -117
  19. package/dist/http.js.map +0 -1
  20. package/dist/i18n.d.ts +0 -22
  21. package/dist/i18n.js +0 -36
  22. package/dist/i18n.js.map +0 -1
  23. package/dist/index.d.ts +0 -2
  24. package/dist/index.js.map +0 -1
  25. package/dist/journey.d.ts +0 -5
  26. package/dist/journey.js +0 -67
  27. package/dist/journey.js.map +0 -1
  28. package/dist/look2book.d.ts +0 -98
  29. package/dist/look2book.js +0 -212
  30. package/dist/look2book.js.map +0 -1
  31. package/dist/prices.d.ts +0 -3
  32. package/dist/prices.js +0 -51
  33. package/dist/prices.js.map +0 -1
  34. package/dist/profile.d.ts +0 -16
  35. package/dist/profile.js +0 -84
  36. package/dist/profile.js.map +0 -1
  37. package/dist/rate-limit.d.ts +0 -5
  38. package/dist/rate-limit.js +0 -44
  39. package/dist/rate-limit.js.map +0 -1
  40. package/dist/shortlink.d.ts +0 -60
  41. package/dist/shortlink.js +0 -122
  42. package/dist/shortlink.js.map +0 -1
  43. package/dist/structured.d.ts +0 -125
  44. package/dist/structured.js +0 -134
  45. package/dist/structured.js.map +0 -1
  46. package/dist/swisstrip.d.ts +0 -41
  47. package/dist/swisstrip.js +0 -135
  48. package/dist/swisstrip.js.map +0 -1
  49. package/dist/tools.d.ts +0 -40
  50. package/dist/tools.js +0 -509
  51. package/dist/tools.js.map +0 -1
  52. package/dist/transport/index.d.ts +0 -10
  53. package/dist/transport/index.js +0 -13
  54. package/dist/transport/index.js.map +0 -1
  55. package/dist/transport/setup.d.ts +0 -1
  56. package/dist/transport/setup.js +0 -59
  57. package/dist/transport/setup.js.map +0 -1
  58. package/dist/transport/smapi-auth.d.ts +0 -14
  59. package/dist/transport/smapi-auth.js +0 -89
  60. package/dist/transport/smapi-auth.js.map +0 -1
  61. package/dist/transport/smapi-client.d.ts +0 -46
  62. package/dist/transport/smapi-client.js +0 -186
  63. package/dist/transport/smapi-client.js.map +0 -1
  64. package/dist/transport/smapi-journey.d.ts +0 -29
  65. package/dist/transport/smapi-journey.js +0 -91
  66. package/dist/transport/smapi-journey.js.map +0 -1
  67. package/dist/transport/smapi-mock.d.ts +0 -9
  68. package/dist/transport/smapi-mock.js +0 -151
  69. package/dist/transport/smapi-mock.js.map +0 -1
  70. package/dist/transport/smapi-prices.d.ts +0 -48
  71. package/dist/transport/smapi-prices.js +0 -144
  72. package/dist/transport/smapi-prices.js.map +0 -1
  73. package/dist/transport/smapi-types.d.ts +0 -181
  74. package/dist/transport/smapi-types.js +0 -2
  75. package/dist/transport/smapi-types.js.map +0 -1
  76. package/dist/types.d.ts +0 -139
  77. package/dist/types.js +0 -3
  78. package/dist/types.js.map +0 -1
  79. package/dist/widgets.d.ts +0 -60
  80. package/dist/widgets.js +0 -184
  81. package/dist/widgets.js.map +0 -1
  82. package/web/dist/widgets.css +0 -1
  83. package/web/dist/widgets.js +0 -1
package/dist/widgets.js DELETED
@@ -1,184 +0,0 @@
1
- /**
2
- * Registers ChatGPT Apps SDK widget resources on an McpServer.
3
- *
4
- * Each tool that returns structured data links to a widget via
5
- * `_meta["openai/outputTemplate"] = "ui://widget/<name>.html"`.
6
- *
7
- * The HTML shell for every widget embeds the same JS bundle (dist/widgets.js)
8
- * produced by `packages/sbb-mcp/web`. Routing to the right widget component
9
- * happens in the bundle via `data-widget` on the root element.
10
- */
11
- import { readFileSync, existsSync } from 'node:fs';
12
- import { dirname, resolve } from 'node:path';
13
- import { fileURLToPath } from 'node:url';
14
- import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
16
- /** Widgets ship under packages/sbb-mcp/web/dist, i.e. two levels above dist/*.js at runtime. */
17
- const WEB_DIST = resolve(__dirname, '..', 'web', 'dist');
18
- /** Registered widget IDs (mirrored in web/src/main.tsx). */
19
- export const WIDGETS = {
20
- STATIONS_LIST: 'stations-list',
21
- CONNECTION_LIST: 'connection-list',
22
- TRIP_DETAILS: 'trip-details',
23
- PRICES_TABLE: 'prices-table',
24
- TICKET_CARD: 'ticket-card',
25
- };
26
- export function widgetUri(id) {
27
- return `ui://widget/${id}.html`;
28
- }
29
- /**
30
- * True when the connected MCP client renders our widget iframe (currently
31
- * ChatGPT via the Apps SDK). Used by tool handlers to decide between a full
32
- * markdown response (non-widget clients need it to display anything useful)
33
- * and a terse one-line summary (widget clients already show the rich UI, and
34
- * the detailed prose just gets paraphrased by the LLM into a duplicate
35
- * narration next to the iframe — see https://…/issues/connections-shown-twice).
36
- *
37
- * Detection is based on `clientInfo.name` from the MCP `initialize` handshake.
38
- * ChatGPT self-identifies as "ChatGPT" / "ChatGPT-Atlas" / similar — we match
39
- * case-insensitively on "chatgpt" or "openai" so future naming tweaks from
40
- * OpenAI don't silently revert us to the noisy path.
41
- *
42
- * Falls back to `false` (full markdown) when no clientInfo has been received
43
- * yet or when the name doesn't match — the safe default for unknown clients
44
- * that might not render widgets.
45
- */
46
- export function isWidgetRenderingClient(server) {
47
- const info = server.server.getClientVersion();
48
- const name = info?.name ?? '';
49
- return /chatgpt|openai/i.test(name);
50
- }
51
- /**
52
- * Descriptor `_meta` (on the tool and resource registrations).
53
- * This is what tells ChatGPT to render a UI widget for the tool.
54
- */
55
- export function widgetToolMeta(id, invoking, invoked) {
56
- return {
57
- 'openai/outputTemplate': widgetUri(id),
58
- 'openai/toolInvocation/invoking': invoking,
59
- 'openai/toolInvocation/invoked': invoked,
60
- 'openai/widgetAccessible': true,
61
- };
62
- }
63
- /**
64
- * Invocation `_meta` (on each CallTool response).
65
- *
66
- * IMPORTANT: matches OpenAI's Pizzaz reference server exactly — ONLY the
67
- * invoking/invoked strings. Historically we also set `openai/outputTemplate`
68
- * and `openai/widgetAccessible` here, but those belong on the tool descriptor
69
- * + resource registration (see `widgetToolMeta`). Repeating them on the
70
- * response appears to confuse ChatGPT's widget resolver: the iframe mounts
71
- * but `window.openai.toolOutput` never populates — leaving every widget
72
- * stuck on the "Loading…" fallback.
73
- *
74
- * Ref: https://github.com/openai/openai-apps-sdk-examples/blob/main/pizzaz_server_node/src/server.ts
75
- */
76
- export function widgetResponseMeta(_id, invoking, invoked) {
77
- return {
78
- 'openai/toolInvocation/invoking': invoking,
79
- 'openai/toolInvocation/invoked': invoked,
80
- };
81
- }
82
- let cachedBundles = null;
83
- function loadBundles() {
84
- if (cachedBundles)
85
- return cachedBundles;
86
- const jsPath = resolve(WEB_DIST, 'widgets.js');
87
- const cssPath = resolve(WEB_DIST, 'widgets.css');
88
- if (!existsSync(jsPath)) {
89
- throw new Error(`Widget bundle not found at ${jsPath}. Run 'npm run build' in packages/sbb-mcp (which runs the web build first).`);
90
- }
91
- const js = readFileSync(jsPath, 'utf8');
92
- const css = existsSync(cssPath) ? readFileSync(cssPath, 'utf8') : '';
93
- cachedBundles = { js, css };
94
- return cachedBundles;
95
- }
96
- function renderWidgetHtml(id) {
97
- const { js, css } = loadBundles();
98
- // Full HTML document shape matching OpenAI's Pizzaz reference server.
99
- //
100
- // We settled on a full `<!doctype html>` document (rather than a naked
101
- // fragment) because ChatGPT's widget iframe resolver wraps text/html+
102
- // skybridge resources as-is — no implicit <html>/<body> — and Preact's
103
- // hydration was silently failing when the bundle executed before the
104
- // document's body was parsed.
105
- //
106
- // Ref: https://github.com/openai/openai-apps-sdk-examples
107
- return `<!doctype html>
108
- <html>
109
- <head>
110
- <meta charset="utf-8">
111
- <style>${css}</style>
112
- </head>
113
- <body>
114
- <div id="sbb-widget-root" data-widget="${id}"></div>
115
- <script>${js}</script>
116
- </body>
117
- </html>`;
118
- }
119
- const DEFINITIONS = [
120
- {
121
- id: WIDGETS.STATIONS_LIST,
122
- title: 'Stations',
123
- description: 'Card view of Swiss train station search results.',
124
- },
125
- {
126
- id: WIDGETS.CONNECTION_LIST,
127
- title: 'Train connections',
128
- description: 'Card list of Swiss train connections with times, duration, and transfers.',
129
- },
130
- {
131
- id: WIDGETS.TRIP_DETAILS,
132
- title: 'Trip details',
133
- description: 'Timeline view of a single train journey with stops, platforms, and occupancy.',
134
- },
135
- {
136
- id: WIDGETS.PRICES_TABLE,
137
- title: 'Ticket prices',
138
- description: 'Table view of 1st- and 2nd-class ticket prices for selected trips.',
139
- },
140
- {
141
- id: WIDGETS.TICKET_CARD,
142
- title: 'Buy ticket',
143
- description: 'SBB ticket purchase card with deep link that opens SBB.ch or the SBB mobile app.',
144
- },
145
- ];
146
- /** Registers all widget resources on the given MCP server. Call once per server instance. */
147
- export function registerWidgets(server) {
148
- for (const def of DEFINITIONS) {
149
- const uri = widgetUri(def.id);
150
- const meta = widgetToolMeta(def.id, 'Loading…', 'Done');
151
- const config = {
152
- title: def.title,
153
- description: def.description,
154
- mimeType: 'text/html+skybridge',
155
- _meta: meta,
156
- };
157
- const read = async () => ({
158
- contents: [
159
- {
160
- uri,
161
- mimeType: 'text/html+skybridge',
162
- text: renderWidgetHtml(def.id),
163
- _meta: meta,
164
- },
165
- ],
166
- });
167
- // Static registration — surfaces in resources/list + enables plain
168
- // resources/read by URI. Matches Pizzaz's ListResourcesRequest shape.
169
- server.registerResource(`widget-${def.id}`, uri, config, read);
170
- // Template registration — surfaces in resources/templates/list, which
171
- // is what ChatGPT's Apps SDK widget resolver actually queries to
172
- // discover mountable widgets. Before this dual registration, ChatGPT
173
- // saw an empty template list, failed with "Error loading app — Failed
174
- // to fetch template", and never mounted the iframe.
175
- //
176
- // `list: undefined` matches the ResourceTemplate signature and tells
177
- // the SDK there is no dynamic enumeration needed (our template URI
178
- // has no {variables} — it's a plain URI, like Pizzaz).
179
- //
180
- // Ref: https://github.com/openai/openai-apps-sdk-examples/blob/main/pizzaz_server_node/src/server.ts
181
- server.registerResource(`widget-${def.id}-template`, new ResourceTemplate(uri, { list: undefined }), config, read);
182
- }
183
- }
184
- //# sourceMappingURL=widgets.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"widgets.js","sourceRoot":"","sources":["../src/widgets.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- :host,.sbb-root{--bg: #ffffff;--bg-raised: #f7f7f8;--border: #e5e5e7;--text: #0f172a;--text-muted: #6b7280;--accent: #eb0000;--accent-fg: #ffffff;--accent-muted: #fff0f0;--success: #0a9447;--warn: #b45309;--radius: 12px;--radius-sm: 8px;--gap: 12px;--gap-sm: 8px;color:var(--text);background:var(--bg);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;line-height:1.45;-webkit-font-smoothing:antialiased}.sbb-root[data-theme=dark]{--bg: #0f1115;--bg-raised: #1a1d23;--border: #2a2e37;--text: #e5e7eb;--text-muted: #9ca3af;--accent: #ff4444;--accent-fg: #ffffff;--accent-muted: #2a1515;--success: #10b981;--warn: #f59e0b}.sbb-root{padding:0;margin:0;max-width:560px}.sbb-header{display:flex;align-items:baseline;justify-content:space-between;gap:var(--gap-sm);margin:0 0 var(--gap) 0;padding:0}.sbb-header__title{font-size:15px;font-weight:600;margin:0}.sbb-header__meta{color:var(--text-muted);font-size:12px}.sbb-card{background:var(--bg-raised);border:1px solid var(--border);border-radius:var(--radius);padding:var(--gap);display:flex;flex-direction:column;gap:var(--gap-sm)}.sbb-list{display:flex;flex-direction:column;gap:var(--gap-sm)}.sbb-connection{display:grid;grid-template-columns:1fr auto;gap:var(--gap-sm);align-items:center;padding:var(--gap-sm) var(--gap);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg)}.sbb-connection__times{display:flex;align-items:baseline;gap:8px;font-variant-numeric:tabular-nums}.sbb-connection__time{font-weight:600;font-size:16px}.sbb-connection__time--link{color:var(--accent);text-decoration:none}.sbb-connection__time--link:hover{text-decoration:underline}.sbb-connection__arrow{color:var(--text-muted)}.sbb-connection__meta{color:var(--text-muted);font-size:12px;margin-top:2px;display:flex;flex-wrap:wrap;gap:6px}.sbb-connection__route{font-size:12px;color:var(--text-muted);display:flex;flex-wrap:wrap;gap:4px;align-items:center}.sbb-connection__actions{display:flex;gap:6px;justify-self:end}.sbb-badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;background:var(--bg-raised);border:1px solid var(--border);font-size:11px;color:var(--text);white-space:nowrap}.sbb-badge--accent{background:var(--accent-muted);border-color:transparent;color:var(--accent);font-weight:600}.sbb-button{appearance:none;border:1px solid var(--border);background:var(--bg);color:var(--text);padding:6px 10px;font-size:12px;font-weight:500;border-radius:var(--radius-sm);cursor:pointer;display:inline-flex;align-items:center;gap:4px;text-decoration:none;font-family:inherit}.sbb-button:hover:not(:disabled){background:var(--bg-raised)}.sbb-button:disabled{opacity:.5;cursor:not-allowed}.sbb-button--primary{background:var(--accent);color:var(--accent-fg);border-color:var(--accent);padding:8px 14px;font-size:13px}.sbb-button--primary:hover:not(:disabled){background:var(--accent);filter:brightness(.95)}.sbb-footer{margin-top:var(--gap);padding:var(--gap-sm) 0;display:flex;gap:var(--gap-sm);flex-wrap:wrap}.sbb-table{width:100%;border-collapse:collapse;font-size:13px}.sbb-table th,.sbb-table td{padding:6px 10px;text-align:left;border-bottom:1px solid var(--border)}.sbb-table th{font-weight:600;color:var(--text-muted);font-size:12px;text-transform:uppercase;letter-spacing:.03em}.sbb-table td{font-variant-numeric:tabular-nums}.sbb-table tr:last-child td{border-bottom:0}.sbb-ticket-card{padding:16px;background:linear-gradient(135deg,var(--accent-muted),var(--bg-raised));border:1px solid var(--border);border-radius:var(--radius);display:flex;flex-direction:column;gap:var(--gap)}.sbb-ticket-card__route{font-size:16px;font-weight:600}.sbb-ticket-card__times{color:var(--text-muted);font-size:13px}.sbb-weather{padding:8px var(--gap);background:var(--bg-raised);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text-muted);font-size:12px;margin-top:var(--gap-sm)}.sbb-timeline{display:flex;flex-direction:column;gap:4px;position:relative;padding-left:16px}.sbb-timeline:before{content:"";position:absolute;left:4px;top:6px;bottom:6px;width:2px;background:var(--border)}.sbb-timeline__stop{position:relative;display:flex;justify-content:space-between;padding:2px 0;font-size:13px}.sbb-timeline__stop:before{content:"";position:absolute;left:-16px;top:8px;width:10px;height:10px;border-radius:50%;background:var(--bg);border:2px solid var(--accent)}.sbb-timeline__stop--intermediate:before{background:var(--border);border-color:var(--border);width:6px;height:6px;left:-14px;top:10px}.sbb-timeline__stop--intermediate{color:var(--text-muted);font-size:12px}.sbb-timeline__time{font-variant-numeric:tabular-nums;color:var(--text-muted);margin-right:8px}.sbb-empty{color:var(--text-muted);padding:var(--gap);text-align:center;font-size:13px}.sbb-debug{margin-top:var(--gap);padding:8px;border:1px dashed var(--border);border-radius:var(--radius-sm);font-size:11px;color:var(--text-muted);text-align:left}.sbb-debug summary{cursor:pointer;font-weight:600}.sbb-debug code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;word-break:break-all}.sbb-traveler-bar{margin:0 0 var(--gap-sm) 0;padding:8px var(--gap);background:var(--bg-raised);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:12px}.sbb-traveler-bar__summary{display:flex;align-items:center;justify-content:space-between;gap:var(--gap-sm)}.sbb-traveler-bar__text{color:var(--text-muted)}.sbb-traveler-bar__text strong{color:var(--text);font-weight:600}.sbb-traveler-bar__form{margin-top:var(--gap-sm);padding-top:var(--gap-sm);border-top:1px solid var(--border);display:flex;flex-direction:column;gap:var(--gap-sm)}.sbb-traveler-bar__group{display:flex;align-items:center;gap:var(--gap-sm);flex-wrap:wrap}.sbb-traveler-bar__label{color:var(--text-muted);font-size:11px;text-transform:uppercase;letter-spacing:.04em;min-width:110px}.sbb-traveler-bar__options{display:flex;gap:4px;flex-wrap:wrap}.sbb-traveler-bar__hint{margin:4px 0 0;color:var(--text-muted);font-size:11px;font-style:italic}.sbb-chip{appearance:none;border:1px solid var(--border);background:var(--bg);color:var(--text);padding:4px 10px;font-size:12px;border-radius:999px;cursor:pointer;font-family:inherit}.sbb-chip:hover{background:var(--bg-raised)}.sbb-chip--active{background:var(--accent-muted);border-color:var(--accent);color:var(--accent);font-weight:600}.sbb-button--ghost{background:transparent;border-color:transparent;color:var(--accent);padding:4px 8px}.sbb-button--ghost:hover:not(:disabled){background:var(--accent-muted)}
@@ -1 +0,0 @@
1
- (function(){"use strict";var H,b,le,L,ce,ae,de,V,B,A,ue,J,K,Q,E={},F=[],Ue=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,U=Array.isArray;function C(e,t){for(var n in t)e[n]=t[n];return e}function X(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Me(e,t,n){var i,o,s,c={};for(s in t)s=="key"?i=t[s]:s=="ref"?o=t[s]:c[s]=t[s];if(arguments.length>2&&(c.children=arguments.length>3?H.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(s in e.defaultProps)c[s]===void 0&&(c[s]=e.defaultProps[s]);return M(e,c,i,o,null)}function M(e,t,n,i,o){var s={type:e,props:t,key:n,ref:i,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++le,__i:-1,__u:0};return o==null&&b.vnode!=null&&b.vnode(s),s}function O(e){return e.children}function R(e,t){this.props=e,this.context=t}function x(e,t){if(t==null)return e.__?x(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?x(e):null}function Oe(e){if(e.__P&&e.__d){var t=e.__v,n=t.__e,i=[],o=[],s=C({},t);s.__v=t.__v+1,b.vnode&&b.vnode(s),Y(e.__P,s,t,e.__n,e.__P.namespaceURI,32&t.__u?[n]:null,i,n??x(t),!!(32&t.__u),o),s.__v=t.__v,s.__.__k[s.__i]=s,ye(i,s,o),t.__e=t.__=null,s.__e!=n&&he(s)}}function he(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),he(e)}function pe(e){(!e.__d&&(e.__d=!0)&&L.push(e)&&!W.__r++||ce!=b.debounceRendering)&&((ce=b.debounceRendering)||ae)(W)}function W(){try{for(var e,t=1;L.length;)L.length>t&&L.sort(de),e=L.shift(),t=L.length,Oe(e)}finally{L.length=W.__r=0}}function be(e,t,n,i,o,s,c,a,d,l,h){var _,p,u,g,w,k,f,m=i&&i.__k||F,$=t.length;for(d=Re(n,t,m,d,$),_=0;_<$;_++)(u=n.__k[_])!=null&&(p=u.__i!=-1&&m[u.__i]||E,u.__i=_,k=Y(e,u,p,o,s,c,a,d,l,h),g=u.__e,u.ref&&p.ref!=u.ref&&(p.ref&&te(p.ref,null,u),h.push(u.ref,u.__c||g,u)),w==null&&g!=null&&(w=g),(f=!!(4&u.__u))||p.__k===u.__k?(d=fe(u,d,e,f),f&&p.__e&&(p.__e=null)):typeof u.type=="function"&&k!==void 0?d=k:g&&(d=g.nextSibling),u.__u&=-7);return n.__e=w,d}function Re(e,t,n,i,o){var s,c,a,d,l,h=n.length,_=h,p=0;for(e.__k=new Array(o),s=0;s<o;s++)(c=t[s])!=null&&typeof c!="boolean"&&typeof c!="function"?(typeof c=="string"||typeof c=="number"||typeof c=="bigint"||c.constructor==String?c=e.__k[s]=M(null,c,null,null,null):U(c)?c=e.__k[s]=M(O,{children:c},null,null,null):c.constructor===void 0&&c.__b>0?c=e.__k[s]=M(c.type,c.props,c.key,c.ref?c.ref:null,c.__v):e.__k[s]=c,d=s+p,c.__=e,c.__b=e.__b+1,a=null,(l=c.__i=We(c,n,d,_))!=-1&&(_--,(a=n[l])&&(a.__u|=2)),a==null||a.__v==null?(l==-1&&(o>h?p--:o<h&&p++),typeof c.type!="function"&&(c.__u|=4)):l!=d&&(l==d-1?p--:l==d+1?p++:(l>d?p--:p++,c.__u|=4))):e.__k[s]=null;if(_)for(s=0;s<h;s++)(a=n[s])!=null&&!(2&a.__u)&&(a.__e==i&&(i=x(a)),ke(a,a));return i}function fe(e,t,n,i){var o,s;if(typeof e.type=="function"){for(o=e.__k,s=0;o&&s<o.length;s++)o[s]&&(o[s].__=e,t=fe(o[s],t,n,i));return t}e.__e!=t&&(i&&(t&&e.type&&!t.parentNode&&(t=x(e)),n.insertBefore(e.__e,t||null)),t=e.__e);do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function We(e,t,n,i){var o,s,c,a=e.key,d=e.type,l=t[n],h=l!=null&&(2&l.__u)==0;if(l===null&&a==null||h&&a==l.key&&d==l.type)return n;if(i>(h?1:0)){for(o=n-1,s=n+1;o>=0||s<t.length;)if((l=t[c=o>=0?o--:s++])!=null&&!(2&l.__u)&&a==l.key&&d==l.type)return c}return-1}function me(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||Ue.test(t)?n:n+"px"}function Z(e,t,n,i,o){var s,c;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof i=="string"&&(e.style.cssText=i=""),i)for(t in i)n&&t in n||me(e.style,t,"");if(n)for(t in n)i&&n[t]==i[t]||me(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")s=t!=(t=t.replace(ue,"$1")),c=t.toLowerCase(),t=c in e||t=="onFocusOut"||t=="onFocusIn"?c.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+s]=n,n?i?n[A]=i[A]:(n[A]=J,e.addEventListener(t,s?Q:K,s)):e.removeEventListener(t,s?Q:K,s);else{if(o=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function ve(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t[B]==null)t[B]=J++;else if(t[B]<n[A])return;return n(b.event?b.event(t):t)}}}function Y(e,t,n,i,o,s,c,a,d,l){var h,_,p,u,g,w,k,f,m,$,P,D,Fe,z,_e,T=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(d=!!(32&n.__u),s=[a=t.__e=n.__e]),(h=b.__b)&&h(t);e:if(typeof T=="function")try{if(f=t.props,m=T.prototype&&T.prototype.render,$=(h=T.contextType)&&i[h.__c],P=h?$?$.props.value:h.__:i,n.__c?k=(_=t.__c=n.__c).__=_.__E:(m?t.__c=_=new T(f,P):(t.__c=_=new R(f,P),_.constructor=T,_.render=je),$&&$.sub(_),_.state||(_.state={}),_.__n=i,p=_.__d=!0,_.__h=[],_._sb=[]),m&&_.__s==null&&(_.__s=_.state),m&&T.getDerivedStateFromProps!=null&&(_.__s==_.state&&(_.__s=C({},_.__s)),C(_.__s,T.getDerivedStateFromProps(f,_.__s))),u=_.props,g=_.state,_.__v=t,p)m&&T.getDerivedStateFromProps==null&&_.componentWillMount!=null&&_.componentWillMount(),m&&_.componentDidMount!=null&&_.__h.push(_.componentDidMount);else{if(m&&T.getDerivedStateFromProps==null&&f!==u&&_.componentWillReceiveProps!=null&&_.componentWillReceiveProps(f,P),t.__v==n.__v||!_.__e&&_.shouldComponentUpdate!=null&&_.shouldComponentUpdate(f,_.__s,P)===!1){t.__v!=n.__v&&(_.props=f,_.state=_.__s,_.__d=!1),t.__e=n.__e,t.__k=n.__k,t.__k.some(function(I){I&&(I.__=t)}),F.push.apply(_.__h,_._sb),_._sb=[],_.__h.length&&c.push(_);break e}_.componentWillUpdate!=null&&_.componentWillUpdate(f,_.__s,P),m&&_.componentDidUpdate!=null&&_.__h.push(function(){_.componentDidUpdate(u,g,w)})}if(_.context=P,_.props=f,_.__P=e,_.__e=!1,D=b.__r,Fe=0,m)_.state=_.__s,_.__d=!1,D&&D(t),h=_.render(_.props,_.state,_.context),F.push.apply(_.__h,_._sb),_._sb=[];else do _.__d=!1,D&&D(t),h=_.render(_.props,_.state,_.context),_.state=_.__s;while(_.__d&&++Fe<25);_.state=_.__s,_.getChildContext!=null&&(i=C(C({},i),_.getChildContext())),m&&!p&&_.getSnapshotBeforeUpdate!=null&&(w=_.getSnapshotBeforeUpdate(u,g)),z=h!=null&&h.type===O&&h.key==null?ge(h.props.children):h,a=be(e,U(z)?z:[z],t,n,i,o,s,c,a,d,l),_.base=t.__e,t.__u&=-161,_.__h.length&&c.push(_),k&&(_.__E=_.__=null)}catch(I){if(t.__v=null,d||s!=null)if(I.then){for(t.__u|=d?160:128;a&&a.nodeType==8&&a.nextSibling;)a=a.nextSibling;s[s.indexOf(a)]=null,t.__e=a}else{for(_e=s.length;_e--;)X(s[_e]);ee(t)}else t.__e=n.__e,t.__k=n.__k,I.then||ee(t);b.__e(I,t,n)}else s==null&&t.__v==n.__v?(t.__k=n.__k,t.__e=n.__e):a=t.__e=Ze(n.__e,t,n,i,o,s,c,d,l);return(h=b.diffed)&&h(t),128&t.__u?void 0:a}function ee(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(ee))}function ye(e,t,n){for(var i=0;i<n.length;i++)te(n[i],n[++i],n[++i]);b.__c&&b.__c(t,e),e.some(function(o){try{e=o.__h,o.__h=[],e.some(function(s){s.call(o)})}catch(s){b.__e(s,o.__v)}})}function ge(e){return typeof e!="object"||e==null||e.__b>0?e:U(e)?e.map(ge):C({},e)}function Ze(e,t,n,i,o,s,c,a,d){var l,h,_,p,u,g,w,k=n.props||E,f=t.props,m=t.type;if(m=="svg"?o="http://www.w3.org/2000/svg":m=="math"?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),s!=null){for(l=0;l<s.length;l++)if((u=s[l])&&"setAttribute"in u==!!m&&(m?u.localName==m:u.nodeType==3)){e=u,s[l]=null;break}}if(e==null){if(m==null)return document.createTextNode(f);e=document.createElementNS(o,m,f.is&&f),a&&(b.__m&&b.__m(t,s),a=!1),s=null}if(m==null)k===f||a&&e.data==f||(e.data=f);else{if(s=s&&H.call(e.childNodes),!a&&s!=null)for(k={},l=0;l<e.attributes.length;l++)k[(u=e.attributes[l]).name]=u.value;for(l in k)u=k[l],l=="dangerouslySetInnerHTML"?_=u:l=="children"||l in f||l=="value"&&"defaultValue"in f||l=="checked"&&"defaultChecked"in f||Z(e,l,null,u,o);for(l in f)u=f[l],l=="children"?p=u:l=="dangerouslySetInnerHTML"?h=u:l=="value"?g=u:l=="checked"?w=u:a&&typeof u!="function"||k[l]===u||Z(e,l,u,k[l],o);if(h)a||_&&(h.__html==_.__html||h.__html==e.innerHTML)||(e.innerHTML=h.__html),t.__k=[];else if(_&&(e.innerHTML=""),be(t.type=="template"?e.content:e,U(p)?p:[p],t,n,i,m=="foreignObject"?"http://www.w3.org/1999/xhtml":o,s,c,s?s[0]:n.__k&&x(n,0),a,d),s!=null)for(l=s.length;l--;)X(s[l]);a||(l="value",m=="progress"&&g==null?e.removeAttribute("value"):g!=null&&(g!==e[l]||m=="progress"&&!g||m=="option"&&g!=k[l])&&Z(e,l,g,k[l],o),l="checked",w!=null&&w!=e[l]&&Z(e,l,w,k[l],o))}return e}function te(e,t,n){try{if(typeof e=="function"){var i=typeof e.__u=="function";i&&e.__u(),i&&t==null||(e.__u=e(t))}else e.current=t}catch(o){b.__e(o,n)}}function ke(e,t,n){var i,o;if(b.unmount&&b.unmount(e),(i=e.ref)&&(i.current&&i.current!=e.__e||te(i,null,t)),(i=e.__c)!=null){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(s){b.__e(s,t)}i.base=i.__P=null}if(i=e.__k)for(o=0;o<i.length;o++)i[o]&&ke(i[o],t,n||typeof e.type!="function");n||X(e.__e),e.__c=e.__=e.__e=void 0}function je(e,t,n){return this.constructor(e,n)}function qe(e,t,n){var i,o,s,c;t==document&&(t=document.documentElement),b.__&&b.__(e,t),o=(i=!1)?null:t.__k,s=[],c=[],Y(t,e=t.__k=Me(O,null,[e]),o||E,E,t.namespaceURI,o?null:t.firstChild?H.call(t.childNodes):null,s,o?o.__e:t.firstChild,i,c),ye(s,e,c)}H=F.slice,b={__e:function(e,t,n,i){for(var o,s,c;t=t.__;)if((o=t.__c)&&!o.__)try{if((s=o.constructor)&&s.getDerivedStateFromError!=null&&(o.setState(s.getDerivedStateFromError(e)),c=o.__d),o.componentDidCatch!=null&&(o.componentDidCatch(e,i||{}),c=o.__d),c)return o.__E=o}catch(a){e=a}throw e}},le=0,R.prototype.setState=function(e,t){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=C({},this.state),typeof e=="function"&&(e=e(C({},n),this.props)),e&&C(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),pe(this))},R.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),pe(this))},R.prototype.render=O,L=[],ae=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,de=function(e,t){return e.__v.__b-t.__v.__b},W.__r=0,V=Math.random().toString(8),B="__d"+V,A="__a"+V,ue=/(PointerCapture)$|Capture$/i,J=0,K=ve(!1),Q=ve(!0);var Ge=0;function r(e,t,n,i,o,s){t||(t={});var c,a,d=t;if("ref"in d)for(a in d={},t)a=="ref"?c=t[a]:d[a]=t[a];var l={type:e,props:d,key:n,ref:c,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--Ge,__i:-1,__u:0,__source:o,__self:s};if(typeof e=="function"&&(c=e.defaultProps))for(a in c)d[a]===void 0&&(d[a]=c[a]);return b.vnode&&b.vnode(l),l}function N(){return window.openai??{}}function ze({data:e}){return e.stations.length?r("div",{children:[r("header",{class:"sbb-header",children:[r("h2",{class:"sbb-header__title",children:['Stations matching "',e.query,'"']}),r("span",{class:"sbb-header__meta",children:[e.stations.length," result",e.stations.length===1?"":"s"]})]}),r("div",{class:"sbb-list",children:e.stations.map(t=>r("div",{class:"sbb-connection",children:r("div",{children:[r("div",{style:{fontWeight:600},children:t.name}),r("div",{class:"sbb-connection__meta",children:[r("span",{class:"sbb-badge",children:["ID ",t.id]}),typeof t.lat=="number"&&typeof t.lon=="number"&&r("span",{children:[t.lat.toFixed(3),", ",t.lon.toFixed(3)]})]})]})},t.id))})]}):r("div",{class:"sbb-empty",children:['No stations matched "',e.query,'".']})}var ne,v,re,we,ie=0,Te=[],y=b,Ce=y.__b,Se=y.__r,Le=y.diffed,Ne=y.__c,$e=y.unmount,Pe=y.__;function Ve(e,t){y.__h&&y.__h(v,e,ie||t),ie=0;var n=v.__H||(v.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function Je(e){return ie=1,Ke(Ie,e)}function Ke(e,t,n){var i=Ve(ne++,2);if(i.t=e,!i.__c&&(i.__=[Ie(void 0,t),function(a){var d=i.__N?i.__N[0]:i.__[0],l=i.t(d,a);d!==l&&(i.__N=[l,i.__[1]],i.__c.setState({}))}],i.__c=v,!v.__f)){var o=function(a,d,l){if(!i.__c.__H)return!0;var h=i.__c.__H.__.filter(function(p){return p.__c});if(h.every(function(p){return!p.__N}))return!s||s.call(this,a,d,l);var _=i.__c.props!==a;return h.some(function(p){if(p.__N){var u=p.__[0];p.__=p.__N,p.__N=void 0,u!==p.__[0]&&(_=!0)}}),s&&s.call(this,a,d,l)||_};v.__f=!0;var s=v.shouldComponentUpdate,c=v.componentWillUpdate;v.componentWillUpdate=function(a,d,l){if(this.__e){var h=s;s=void 0,o(a,d,l),s=h}c&&c.call(this,a,d,l)},v.shouldComponentUpdate=o}return i.__N||i.__}function Qe(){for(var e;e=Te.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(j),t.__h.some(se),t.__h=[]}catch(n){t.__h=[],y.__e(n,e.__v)}}}y.__b=function(e){v=null,Ce&&Ce(e)},y.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),Pe&&Pe(e,t)},y.__r=function(e){Se&&Se(e),ne=0;var t=(v=e.__c).__H;t&&(re===v?(t.__h=[],v.__h=[],t.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(t.__h.some(j),t.__h.some(se),t.__h=[],ne=0)),re=v},y.diffed=function(e){Le&&Le(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(Te.push(t)!==1&&we===y.requestAnimationFrame||((we=y.requestAnimationFrame)||Xe)(Qe)),t.__H.__.some(function(n){n.u&&(n.__H=n.u),n.u=void 0})),re=v=null},y.__c=function(e,t){t.some(function(n){try{n.__h.some(j),n.__h=n.__h.filter(function(i){return!i.__||se(i)})}catch(i){t.some(function(o){o.__h&&(o.__h=[])}),t=[],y.__e(i,n.__v)}}),Ne&&Ne(e,t)},y.unmount=function(e){$e&&$e(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(i){try{j(i)}catch(o){t=o}}),n.__H=void 0,t&&y.__e(t,n.__v))};var xe=typeof requestAnimationFrame=="function";function Xe(e){var t,n=function(){clearTimeout(i),xe&&cancelAnimationFrame(t),setTimeout(e)},i=setTimeout(n,35);xe&&(t=requestAnimationFrame(n))}function j(e){var t=v,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),v=t}function se(e){var t=v;e.__c=e.__(),v=t}function Ie(e,t){return typeof t=="function"?t(e):t}function S(e){try{return new Date(e).toLocaleTimeString("de-CH",{hour:"2-digit",minute:"2-digit",timeZone:"Europe/Zurich"})}catch{return e}}function oe(e){try{return new Date(e).toLocaleDateString("en-CH",{weekday:"short",day:"2-digit",month:"short",timeZone:"Europe/Zurich"})}catch{return e}}function q(e){if(!Number.isFinite(e)||e<0)return"—";const t=Math.floor(e/60),n=e%60;return t===0?`${n}m`:n===0?`${t}h`:`${t}h ${n}m`}function Ae(e,t="CHF"){return`${t} ${e.toFixed(2)}`}const Ye="https://mcp.swisstrip.app",et=1;function tt(e){return btoa(unescape(encodeURIComponent(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function nt(e){const t=new Date(e),n=t.toLocaleDateString("en-CA",{timeZone:"Europe/Zurich"}),i=t.toLocaleTimeString("en-GB",{timeZone:"Europe/Zurich",hour:"2-digit",minute:"2-digit"});return{date:n,time:i}}function rt(e){const{date:t,time:n}=nt(e.departureIso),i={v:et,tid:e.tripId,o:e.fromId,on:e.fromName,d:e.toId,dn:e.toName,dt:t,t:n,l:e.lang??"en"},o=tt(JSON.stringify(i));return`${Ye}/r/${o}`}const it={reductionCard:"HALF_FARE",travelerType:"ADULT"};function De(e){switch(e){case"HALF_FARE":return"Halbtax";case"GA":return"GA";case"NONE":return"No card"}}function st(e){return e==="CHILD"?"child":"adult"}function ot({prefs:e,onChange:t}){const[n,i]=Je(!1);return r("div",{class:"sbb-traveler-bar",children:[r("div",{class:"sbb-traveler-bar__summary",children:[r("span",{class:"sbb-traveler-bar__text",children:["Pricing for: ",r("strong",{children:["1 ",st(e.travelerType)]})," · ",r("strong",{children:De(e.reductionCard)})]}),r("button",{type:"button",class:"sbb-button sbb-button--ghost",onClick:()=>i(!n),children:n?"Done":"Change"})]}),n&&r("div",{class:"sbb-traveler-bar__form",children:[r("div",{class:"sbb-traveler-bar__group",children:[r("span",{class:"sbb-traveler-bar__label",children:"Reduction card"}),r("div",{class:"sbb-traveler-bar__options",children:["HALF_FARE","GA","NONE"].map(o=>r("button",{type:"button",class:`sbb-chip ${e.reductionCard===o?"sbb-chip--active":""}`,onClick:()=>t({...e,reductionCard:o}),children:De(o)},o))})]}),r("div",{class:"sbb-traveler-bar__group",children:[r("span",{class:"sbb-traveler-bar__label",children:"Traveler"}),r("div",{class:"sbb-traveler-bar__options",children:["ADULT","CHILD"].map(o=>r("button",{type:"button",class:`sbb-chip ${e.travelerType===o?"sbb-chip--active":""}`,onClick:()=>t({...e,travelerType:o}),children:o==="ADULT"?"Adult":"Child (6–16)"},o))})]}),r("p",{class:"sbb-traveler-bar__hint",children:'For multiple travelers or family tickets, ask in chat — e.g. "2 adults + 1 kid age 8".'})]})]})}function _t({legs:e}){const t=e.filter(n=>n.type==="train");return t.length===0?null:r("span",{class:"sbb-connection__route",children:t.map((n,i)=>r("span",{children:[i>0&&r("span",{style:{color:"var(--text-muted)",margin:"0 2px"},children:"›"}),r("span",{class:"sbb-badge",children:[n.line??"Train",n.platform?` · Pl. ${n.platform}`:""]})]},i))})}function lt(e,t,n,i){return rt({tripId:e.tripId,fromId:t.id,fromName:t.name,toId:n.id,toName:n.name,departureIso:e.departureTime,lang:i})}function ct({c:e,origin:t,destination:n,lang:i,onPrice:o}){const s=lt(e,t,n,i);return r("div",{class:"sbb-connection",children:[r("div",{children:[r("div",{class:"sbb-connection__times",children:[r("a",{class:"sbb-connection__time sbb-connection__time--link",href:s,target:"_blank",rel:"noopener noreferrer",title:"Open on SBB.ch",children:S(e.departureTime)}),r("span",{class:"sbb-connection__arrow",children:"→"}),r("span",{class:"sbb-connection__time",children:S(e.arrivalTime)})]}),r("div",{class:"sbb-connection__meta",children:[r("span",{class:"sbb-badge",children:q(e.durationMinutes)}),r("span",{class:"sbb-badge",children:[e.transfers," transfer",e.transfers===1?"":"s"]}),r(_t,{legs:e.legs})]})]}),r("div",{class:"sbb-connection__actions",children:[r("button",{type:"button",class:"sbb-button",onClick:o,children:"Price"}),r("a",{class:"sbb-button sbb-button--primary",href:s,target:"_blank",rel:"noopener noreferrer",children:"Book"})]})]})}function at({data:e,theme:t}){const n=N(),i=n.widgetState?.travelerPrefs??it,o=l=>{n.setWidgetState?.({...n.widgetState??{},travelerPrefs:l})},s=()=>({traveler_type:i.travelerType,reduction_card:i.reductionCard}),c=l=>{n.callTool?.("get_prices",{trip_ids:[l],...s()})},a=l=>{n.callTool?.("get_more_connections",{collection_id:e.collectionId,direction:l})},d=()=>{const l=e.connections.slice(0,5).map(h=>h.tripId);l.length&&n.callTool?.("get_prices",{trip_ids:l,...s()})};return e.connections.length?r("div",{children:[r("header",{class:"sbb-header",children:[r("h2",{class:"sbb-header__title",children:[e.origin.name," → ",e.destination.name]}),r("span",{class:"sbb-header__meta",children:oe(e.date)})]}),r(ot,{prefs:i,onChange:o}),r("div",{class:"sbb-list",children:e.connections.map(l=>r(ct,{c:l,origin:e.origin,destination:e.destination,lang:e.lang,onPrice:()=>c(l.tripId)},l.tripId))}),e.weather?.summary&&r("div",{class:"sbb-weather",children:["☁ ",e.destination.name,": ",e.weather.summary]}),r("div",{class:"sbb-footer",children:[r("button",{type:"button",class:"sbb-button",onClick:()=>a("previous"),children:"← Earlier"}),r("button",{type:"button",class:"sbb-button",onClick:()=>a("next"),children:"Later →"}),r("button",{type:"button",class:"sbb-button sbb-button--primary",onClick:d,children:"See all prices"})]})]}):r("div",{class:"sbb-empty",children:"No connections found."})}function dt({leg:e,index:t}){return e.type==="walk"?r("div",{class:"sbb-card",style:{background:"var(--bg)"},children:r("div",{style:{fontWeight:600},children:["Transfer · walk ",q(e.durationMinutes)]})}):r("div",{class:"sbb-card",children:[r("div",{style:{display:"flex",alignItems:"baseline",justifyContent:"space-between",gap:8},children:[r("div",{children:[r("span",{class:"sbb-badge sbb-badge--accent",children:["Leg ",t+1]}),r("span",{style:{marginLeft:8,fontWeight:600},children:[e.line??"Train",e.operator?r("span",{class:"sbb-header__meta",children:[" · ",e.operator]}):null]})]}),r("span",{class:"sbb-header__meta",children:q(e.durationMinutes)})]}),r("div",{class:"sbb-timeline",children:[e.from&&r("div",{class:"sbb-timeline__stop",children:[r("span",{children:[r("strong",{children:e.from.name}),e.from.platform?r("span",{class:"sbb-header__meta",children:[" · Pl. ",e.from.platform]}):null]}),r("span",{class:"sbb-timeline__time",children:S(e.from.time)})]}),e.intermediateStops?.map((n,i)=>r("div",{class:"sbb-timeline__stop sbb-timeline__stop--intermediate",children:[r("span",{children:n.name}),r("span",{class:"sbb-timeline__time",children:n.arrivalTime?S(n.arrivalTime):""})]},i)),e.to&&r("div",{class:"sbb-timeline__stop",children:[r("span",{children:[r("strong",{children:e.to.name}),e.to.platform?r("span",{class:"sbb-header__meta",children:[" · Pl. ",e.to.platform]}):null]}),r("span",{class:"sbb-timeline__time",children:S(e.to.time)})]})]}),e.occupancy&&r("div",{class:"sbb-connection__meta",children:[e.occupancy.firstClass&&r("span",{class:"sbb-badge",children:["1st: ",e.occupancy.firstClass]}),e.occupancy.secondClass&&r("span",{class:"sbb-badge",children:["2nd: ",e.occupancy.secondClass]})]})]})}function ut({data:e}){const t=N();return r("div",{children:[r("header",{class:"sbb-header",children:[r("h2",{class:"sbb-header__title",children:[e.origin.name," → ",e.destination.name]}),r("span",{class:"sbb-header__meta",children:oe(e.departureTime)})]}),r("div",{class:"sbb-card",style:{marginBottom:"var(--gap)"},children:r("div",{style:{display:"flex",justifyContent:"space-between"},children:[r("div",{class:"sbb-connection__times",children:[r("span",{class:"sbb-connection__time",children:S(e.departureTime)}),r("span",{class:"sbb-connection__arrow",children:"→"}),r("span",{class:"sbb-connection__time",children:S(e.arrivalTime)})]}),r("div",{class:"sbb-connection__meta",children:[r("span",{class:"sbb-badge",children:q(e.durationMinutes)}),r("span",{class:"sbb-badge",children:[e.transfers," transfer",e.transfers===1?"":"s"]}),r("span",{class:"sbb-badge",children:e.status})]})]})}),r("div",{class:"sbb-list",children:e.legs.map((n,i)=>r(dt,{leg:n,index:i},i))}),r("div",{class:"sbb-footer",children:r("button",{type:"button",class:"sbb-button sbb-button--primary",onClick:()=>t.callTool?.("get_prices",{trip_ids:[e.tripId]}),children:"See price"})})]})}function ht({data:e}){return e.prices.length?r("div",{children:[r("header",{class:"sbb-header",children:[r("h2",{class:"sbb-header__title",children:"Ticket prices"}),r("span",{class:"sbb-header__meta",children:[e.prices.length," trip",e.prices.length===1?"":"s"]})]}),r("div",{class:"sbb-card",children:r("table",{class:"sbb-table",children:[r("thead",{children:r("tr",{children:[r("th",{children:"Trip"}),r("th",{children:"2nd class"}),r("th",{children:"1st class"})]})}),r("tbody",{children:e.prices.map(t=>r("tr",{children:[r("td",{style:{fontFamily:"ui-monospace, monospace",fontSize:11},children:[t.tripId.slice(0,12),"…"]}),r("td",{children:t.secondClass?Ae(t.secondClass.amount,t.secondClass.currency):"—"}),r("td",{children:t.firstClass?Ae(t.firstClass.amount,t.firstClass.currency):"—"})]},t.tripId))})]})}),r("div",{class:"sbb-header__meta",style:{marginTop:8},children:"Prices are estimates. Ask for the ticket link to see the final price on SBB.ch."})]}):r("div",{class:"sbb-empty",children:"No price information available."})}function He(e){return`https://swisstrip.app/go?url=${encodeURIComponent(e)}`}function pt({data:e}){const t=e.affiliateLink??e.primaryLink;return r("div",{class:"sbb-ticket-card",children:[r("header",{class:"sbb-header",children:[r("h2",{class:"sbb-header__title",children:"Buy ticket on SBB"}),r("span",{class:"sbb-header__meta",children:oe(e.departureTime)})]}),r("div",{children:[r("div",{class:"sbb-ticket-card__route",children:[e.origin.name," → ",e.destination.name]}),r("div",{class:"sbb-ticket-card__times",children:[S(e.departureTime)," – ",S(e.arrivalTime)]})]}),r("div",{style:{display:"flex",gap:8,flexWrap:"wrap"},children:[r("a",{class:"sbb-button sbb-button--primary",href:He(t),target:"_blank",rel:"noopener noreferrer",children:"Buy on SBB.ch →"}),e.affiliateLink&&e.affiliateLink!==e.primaryLink&&r("a",{class:"sbb-button",href:He(e.primaryLink),target:"_blank",rel:"noopener noreferrer",children:"Direct SBB link"})]}),r("div",{class:"sbb-header__meta",style:{fontSize:11},children:"Opens SBB.ch with this connection pre-filled. On mobile, the SBB app opens directly with your Halbtax/GA applied."})]})}function bt(){const t=document.getElementById("sbb-widget-root")?.getAttribute("data-widget");if(t)return t;const i=N().toolName;return i?{search_stations:"stations-list",search_connections:"connection-list",get_more_connections:"connection-list",get_trip_details:"trip-details",get_prices:"prices-table",get_ticket_link:"ticket-card"}[i]??null:null}let G=0;function Be({tick:e}){const t=N(),n=typeof window<"u"&&window.openai?Object.keys(window.openai).join(", ")||"(none)":"(no window.openai)";return r("details",{class:"sbb-debug",children:[r("summary",{children:["Debug · tick ",e]}),r("div",{children:["window.openai keys: ",r("code",{children:n})]}),r("div",{children:["toolName: ",r("code",{children:String(t.toolName??"undefined")})]}),r("div",{children:["toolOutput: ",r("code",{children:t.toolOutput?"present":"missing"})]})]})}function ft(){const e=bt(),t=N(),n=t.toolOutput,i=t.theme==="dark"?"dark":"light";if(!e)return r("div",{class:"sbb-empty",children:["Widget not initialised.",r(Be,{tick:G})]});if(!n)return r("div",{class:"sbb-empty",children:["Loading…",r(Be,{tick:G})]});switch(e){case"stations-list":return r(ze,{data:n});case"connection-list":return r(at,{data:n,theme:i});case"trip-details":return r(ut,{data:n});case"prices-table":return r(ht,{data:n});case"ticket-card":return r(pt,{data:n})}}function mt(e){const t=N().theme==="dark"?"dark":"light";e.setAttribute("data-theme",t)}function Ee(){const e=document.getElementById("sbb-widget-root");if(!e){console.warn("[sbb-widgets] #sbb-widget-root not found");return}e.classList.add("sbb-root");const t=o=>{G++,console.debug("[sbb-widgets] rerender",{reason:o,tick:G,hasToolOutput:!!N().toolOutput}),mt(e),qe(r(ft,{}),e)};t("initial"),window.addEventListener("openai:set_globals",()=>t("set_globals"));let n=0;const i=window.setInterval(()=>{n++,N().toolOutput?(t("poll"),window.clearInterval(i)):n>=50&&window.clearInterval(i)},100)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Ee):Ee()})();