sbb-mcp 0.4.1 → 0.4.3
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/LICENSE +57 -57
- package/README.md +258 -314
- package/dist/cache.d.ts +2 -0
- package/dist/cache.js +4 -0
- package/dist/cache.js.map +1 -1
- package/dist/formatters.d.ts +13 -0
- package/dist/formatters.js +94 -19
- package/dist/formatters.js.map +1 -1
- package/dist/http.js +59 -3
- package/dist/http.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/look2book.d.ts +98 -0
- package/dist/look2book.js +212 -0
- package/dist/look2book.js.map +1 -0
- package/dist/profile.js +15 -8
- package/dist/profile.js.map +1 -1
- package/dist/shortlink.d.ts +60 -0
- package/dist/shortlink.js +122 -0
- package/dist/shortlink.js.map +1 -0
- package/dist/structured.d.ts +7 -1
- package/dist/structured.js +2 -1
- package/dist/structured.js.map +1 -1
- package/dist/swisstrip.js +21 -5
- package/dist/swisstrip.js.map +1 -1
- package/dist/tools.d.ts +38 -0
- package/dist/tools.js +122 -77
- package/dist/tools.js.map +1 -1
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +4 -0
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/setup.d.ts +1 -0
- package/dist/transport/setup.js +59 -0
- package/dist/transport/setup.js.map +1 -0
- package/dist/transport/smapi-auth.d.ts +14 -2
- package/dist/transport/smapi-auth.js +67 -17
- package/dist/transport/smapi-auth.js.map +1 -1
- package/dist/transport/smapi-client.d.ts +25 -1
- package/dist/transport/smapi-client.js +112 -9
- package/dist/transport/smapi-client.js.map +1 -1
- package/dist/transport/smapi-journey.d.ts +11 -5
- package/dist/transport/smapi-journey.js +16 -7
- package/dist/transport/smapi-journey.js.map +1 -1
- package/dist/transport/smapi-mock.js.map +1 -1
- package/dist/transport/smapi-prices.d.ts +43 -9
- package/dist/transport/smapi-prices.js +112 -41
- package/dist/transport/smapi-prices.js.map +1 -1
- package/dist/transport/smapi-types.d.ts +28 -0
- package/dist/widgets.d.ts +30 -3
- package/dist/widgets.js +78 -14
- package/dist/widgets.js.map +1 -1
- package/package.json +79 -73
- package/web/dist/widgets.css +1 -1
- package/web/dist/widgets.js +1 -1
package/dist/formatters.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getWeatherSummary } from 'swiss-weather-mcp';
|
|
2
2
|
import { toLocale, toSbbLinkLang, t } from './i18n.js';
|
|
3
|
+
import { buildShortLink } from './shortlink.js';
|
|
3
4
|
function formatTime(iso, lang) {
|
|
4
5
|
const d = new Date(iso);
|
|
5
6
|
return d.toLocaleTimeString(toLocale(lang), {
|
|
@@ -34,6 +35,55 @@ function transfersLabel(n, lang) {
|
|
|
34
35
|
return tr.oneChange;
|
|
35
36
|
return tr.nChanges(n);
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Terse one-line summary used when the client renders the Apps SDK widget
|
|
40
|
+
* (see `isWidgetRenderingClient`). Widget clients already show a full card
|
|
41
|
+
* list — returning the long markdown table here just causes the LLM to
|
|
42
|
+
* paraphrase it into a redundant narration right next to the iframe. The
|
|
43
|
+
* structured content still carries all the data the model needs to answer
|
|
44
|
+
* follow-up questions; this string is purely for the chat bubble above the
|
|
45
|
+
* widget.
|
|
46
|
+
*/
|
|
47
|
+
export function formatStationsTerse(stations, _lang = 'en') {
|
|
48
|
+
if (stations.length === 0) {
|
|
49
|
+
return 'No stations found.';
|
|
50
|
+
}
|
|
51
|
+
const names = stations.slice(0, 3).map(s => s.name).join(', ');
|
|
52
|
+
const more = stations.length > 3 ? ` (+${stations.length - 3} more)` : '';
|
|
53
|
+
return `Found ${stations.length} station${stations.length === 1 ? '' : 's'}: ${names}${more}.`;
|
|
54
|
+
}
|
|
55
|
+
export function formatConnectionsTerse(collection, lang = 'en') {
|
|
56
|
+
const trips = collection.trips;
|
|
57
|
+
const tr = t(lang);
|
|
58
|
+
if (trips.length === 0)
|
|
59
|
+
return tr.noConnections;
|
|
60
|
+
const first = trips[0];
|
|
61
|
+
const date = formatDate(first.startTime, lang);
|
|
62
|
+
const dep = formatTime(first.startTime, lang);
|
|
63
|
+
const arr = formatTime(first.endTime, lang);
|
|
64
|
+
const dur = parseDuration(first.duration);
|
|
65
|
+
const changes = transfersLabel(first.transfers, lang);
|
|
66
|
+
return `${trips.length} connection${trips.length === 1 ? '' : 's'} ${first.origin.name} → ${first.destination.name} · ${date}. Next: ${dep} → ${arr} (${dur}, ${changes}).`;
|
|
67
|
+
}
|
|
68
|
+
export function formatTripDetailsTerse(trip, lang = 'en') {
|
|
69
|
+
const date = formatDate(trip.startTime, lang);
|
|
70
|
+
const dep = formatTime(trip.startTime, lang);
|
|
71
|
+
const arr = formatTime(trip.endTime, lang);
|
|
72
|
+
const dur = parseDuration(trip.duration);
|
|
73
|
+
const changes = transfersLabel(trip.transfers, lang);
|
|
74
|
+
return `${trip.origin.name} → ${trip.destination.name} · ${date} · ${dep}–${arr} (${dur}, ${changes}).`;
|
|
75
|
+
}
|
|
76
|
+
export function formatPricesTerse(results, lang = 'en') {
|
|
77
|
+
const tr = t(lang);
|
|
78
|
+
if (results.length === 0)
|
|
79
|
+
return 'No price information available.';
|
|
80
|
+
const cheapest = results
|
|
81
|
+
.map(r => r.prices.find(p => p.class === '2')?.amount)
|
|
82
|
+
.filter((a) => typeof a === 'number')
|
|
83
|
+
.sort((a, b) => a - b)[0];
|
|
84
|
+
const from = typeof cheapest === 'number' ? ` from CHF ${cheapest.toFixed(2)}` : '';
|
|
85
|
+
return `${tr.prices} for ${results.length} trip${results.length === 1 ? '' : 's'}${from}.`;
|
|
86
|
+
}
|
|
37
87
|
export function formatStations(stations, lang = 'en') {
|
|
38
88
|
if (stations.length === 0) {
|
|
39
89
|
return 'No stations found.';
|
|
@@ -49,18 +99,20 @@ export function formatStations(stations, lang = 'en') {
|
|
|
49
99
|
}
|
|
50
100
|
return lines.join('\n');
|
|
51
101
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Convert an ISO timestamp into Europe/Zurich-local YYYY-MM-DD + HH:MM pair.
|
|
104
|
+
* Matches `toZurichDateTime` in structured.ts. Used to build SBB deep links
|
|
105
|
+
* in the markdown text path.
|
|
106
|
+
*/
|
|
107
|
+
function toZurichDateTime(iso) {
|
|
108
|
+
const d = new Date(iso);
|
|
109
|
+
const date = d.toLocaleDateString('en-CA', { timeZone: 'Europe/Zurich' });
|
|
110
|
+
const time = d.toLocaleTimeString('en-GB', {
|
|
111
|
+
timeZone: 'Europe/Zurich',
|
|
112
|
+
hour: '2-digit',
|
|
113
|
+
minute: '2-digit',
|
|
114
|
+
});
|
|
115
|
+
return { date, time };
|
|
64
116
|
}
|
|
65
117
|
export function formatConnections(collection, lang = 'en') {
|
|
66
118
|
const trips = collection.trips;
|
|
@@ -70,22 +122,45 @@ export function formatConnections(collection, lang = 'en') {
|
|
|
70
122
|
}
|
|
71
123
|
const first = trips[0];
|
|
72
124
|
const date = formatDate(first.startTime, lang);
|
|
125
|
+
//
|
|
126
|
+
// Markdown table format.
|
|
127
|
+
//
|
|
128
|
+
// We tried a prose bullet list first (expecting ChatGPT's rewrite to keep
|
|
129
|
+
// the links intact) but ChatGPT ignores the text entirely when it has
|
|
130
|
+
// structuredContent — it paraphrases from the JSON and picks just one
|
|
131
|
+
// connection to feature. The table format is here as the canonical text
|
|
132
|
+
// response for MCP clients that DO render markdown (Claude Desktop, the
|
|
133
|
+
// WhatsApp bot, our own web UI) — which want all 5 options side-by-side.
|
|
134
|
+
//
|
|
135
|
+
// The booking URL is intentionally hidden behind "Book here" in the last
|
|
136
|
+
// column (not exposed as a bare URL) so rendering is clean in every
|
|
137
|
+
// markdown-capable client.
|
|
138
|
+
//
|
|
73
139
|
const lines = [
|
|
74
|
-
`**${first.origin.name}
|
|
75
|
-
`Collection ID: \`${collection.id}\` (use with get_more_connections)`,
|
|
140
|
+
`**${first.origin.name} → ${first.destination.name}** · ${date}`,
|
|
76
141
|
'',
|
|
77
|
-
`| ${tr.dep} | ${tr.arr} | ${tr.dur} | ${tr.changes} |
|
|
78
|
-
'
|
|
142
|
+
`| ${tr.dep} | ${tr.arr} | ${tr.dur} | ${tr.changes} | |`,
|
|
143
|
+
'|---|---|---|---|---|',
|
|
79
144
|
];
|
|
80
145
|
for (const trip of trips) {
|
|
81
146
|
const dep = formatTime(trip.startTime, lang);
|
|
82
147
|
const arr = formatTime(trip.endTime, lang);
|
|
83
148
|
const dur = parseDuration(trip.duration);
|
|
84
|
-
const
|
|
85
|
-
|
|
149
|
+
const { date: d, time } = toZurichDateTime(trip.startTime);
|
|
150
|
+
const bookingUrl = buildShortLink({
|
|
151
|
+
tripId: trip.id,
|
|
152
|
+
fromId: trip.origin.id,
|
|
153
|
+
fromName: trip.origin.name,
|
|
154
|
+
toId: trip.destination.id,
|
|
155
|
+
toName: trip.destination.name,
|
|
156
|
+
date: d,
|
|
157
|
+
time,
|
|
158
|
+
lang,
|
|
159
|
+
});
|
|
160
|
+
lines.push(`| ${dep} | ${arr} | ${dur} | ${transfersLabel(trip.transfers, lang)} | [${tr.bookHere}](${bookingUrl}) |`);
|
|
86
161
|
}
|
|
87
162
|
lines.push('');
|
|
88
|
-
lines.push(`*
|
|
163
|
+
lines.push(`*Collection ID: \`${collection.id}\` — pass to \`get_more_connections\` for earlier/later trains.*`);
|
|
89
164
|
return lines.join('\n');
|
|
90
165
|
}
|
|
91
166
|
export function formatTripDetails(trip, lang = 'en') {
|
package/dist/formatters.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatters.js","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"formatters.js","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,WAAW,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,SAAS,UAAU,CAAC,GAAW,EAAE,IAAU;IACzC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;IACvB,OAAO,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAC1C,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;QACjB,QAAQ,EAAE,eAAe;KAC1B,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,GAAW,EAAE,IAAU;IACzC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;IACvB,OAAO,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAC1C,OAAO,EAAE,OAAO;QAChB,GAAG,EAAE,SAAS;QACd,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,eAAe;KAC1B,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAA;IACnD,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAA;IACtB,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IACxC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IACxC,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,IAAI,CAAA;AAC/C,CAAC;AAED,SAAS,cAAc,CAAC,CAAS,EAAE,IAAU;IAC3C,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAClB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,MAAM,CAAA;IAC7B,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,SAAS,CAAA;IAChC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;AACvB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAsB,EAAE,QAAc,IAAI;IAC5E,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,oBAAoB,CAAA;IAC7B,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC9D,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IACzE,OAAO,SAAS,QAAQ,CAAC,MAAM,WAAW,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,GAAG,IAAI,GAAG,CAAA;AAChG,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,UAAgC,EAAE,OAAa,IAAI;IACxF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAA;IAC9B,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAClB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,aAAa,CAAA;IAE/C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACtB,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC7C,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAC3C,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IACzC,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAErD,OAAO,GAAG,KAAK,CAAC,MAAM,cAAc,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,KAAK,CAAC,WAAW,CAAC,IAAI,MAAM,IAAI,WAAW,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,OAAO,IAAI,CAAA;AAC7K,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAe,EAAE,OAAa,IAAI;IACvE,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC7C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAC1C,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IACpD,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,OAAO,IAAI,CAAA;AACzG,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAA2B,EAAE,OAAa,IAAI;IAC9E,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAClB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,iCAAiC,CAAA;IAClE,MAAM,QAAQ,GAAG,OAAO;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,EAAE,MAAM,CAAC;SACrD,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;SACjD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3B,MAAM,IAAI,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACnF,OAAO,GAAG,EAAE,CAAC,MAAM,QAAQ,OAAO,CAAC,MAAM,QAAQ,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,GAAG,CAAA;AAC5F,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAsB,EAAE,OAAa,IAAI;IACtE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,oBAAoB,CAAA;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,SAAS,QAAQ,CAAC,MAAM,cAAc,EAAE,EAAE,CAAC,CAAA;IAC1D,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAA;IAC5C,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IAE3B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW;YAC1B,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YAC/E,CAAC,CAAC,GAAG,CAAA;QACP,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,QAAQ,MAAM,IAAI,CAAC,CAAA;IACvD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;IACvB,MAAM,IAAI,GAAG,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAA;IACzE,MAAM,IAAI,GAAG,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACzC,QAAQ,EAAE,eAAe;QACzB,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;KAClB,CAAC,CAAA;IACF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACvB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAgC,EAAE,OAAa,IAAI;IACnF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAA;IAC9B,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAClB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC,aAAa,CAAA;IACzB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACtB,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC9C,EAAE;IACF,yBAAyB;IACzB,EAAE;IACF,0EAA0E;IAC1E,sEAAsE;IACtE,sEAAsE;IACtE,wEAAwE;IACxE,wEAAwE;IACxE,yEAAyE;IACzE,EAAE;IACF,yEAAyE;IACzE,oEAAoE;IACpE,2BAA2B;IAC3B,EAAE;IACF,MAAM,KAAK,GAAa;QACtB,KAAK,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,KAAK,CAAC,WAAW,CAAC,IAAI,QAAQ,IAAI,EAAE;QAChE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,OAAO,MAAM;QACzD,uBAAuB;KACxB,CAAA;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QAC1C,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACxC,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC1D,MAAM,UAAU,GAAG,cAAc,CAAC;YAChC,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE;YACtB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;YAC1B,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE;YACzB,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI;YAC7B,IAAI,EAAE,CAAC;YACP,IAAI;YACJ,IAAI;SACL,CAAC,CAAA;QACF,KAAK,CAAC,IAAI,CACR,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,KAAK,UAAU,KAAK,CAC3G,CAAA;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACd,KAAK,CAAC,IAAI,CAAC,qBAAqB,UAAU,CAAC,EAAE,kEAAkE,CAAC,CAAA;IAEhH,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAe,EAAE,OAAa,IAAI;IAClE,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAClB,MAAM,KAAK,GAAG;QACZ,sBAAsB,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE;QACxD,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,MAAM,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE;QAC7H,WAAW,IAAI,CAAC,UAAU,EAAE;QAC5B,EAAE;KACH,CAAA;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAExB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACzB,MAAM,EAAE,GAAG,GAAoB,CAAA;YAC/B,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,iBAAiB,IAAI,OAAO,CAAA;YACpD,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAA;YAC9C,MAAM,aAAa,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACvF,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAE1F,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;YAC1D,KAAK,CAAC,IAAI,CAAC,OAAO,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,GAAG,aAAa,EAAE,CAAC,CAAA;YAE1G,IAAI,EAAE,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC;gBACjC,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,iBAAiB,EAAE,CAAC;oBACxC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;oBACvE,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAA;gBAClD,CAAC;YACH,CAAC;YAED,KAAK,CAAC,IAAI,CAAC,OAAO,UAAU,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,GAAG,cAAc,EAAE,CAAC,CAAA;YAE3G,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC;gBACjB,MAAM,GAAG,GAAG,EAAE,CAAA;gBACd,IAAI,EAAE,CAAC,SAAS,CAAC,UAAU;oBAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAA;gBACxE,IAAI,EAAE,CAAC,SAAS,CAAC,WAAW;oBAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAA;gBAC1E,IAAI,GAAG,CAAC,MAAM;oBAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAC9D,CAAC;YAED,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,KAAK,aAAa,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QAC1D,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,GAAuB,CAAA;YACpC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,UAAU,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACxE,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,sCAAsC,IAAI,CAAC,EAAE,2BAA2B,CAAC,CAAA;IAEpF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAA2B,EAAE,OAAa,IAAI;IACzE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,iCAAiC,CAAA;IAC1C,CAAC;IAED,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAClB,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,CAAA;IACtC,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAA;IACjD,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAA;IAEjD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAA;QAClD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAA;QACjD,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA;QAClE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA;QAC/D,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,QAAQ,SAAS,MAAM,QAAQ,IAAI,CAAC,CAAA;IAChE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACd,KAAK,CAAC,IAAI,CAAC,4FAA4F,CAAC,CAAA;IAExG,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAQhC;IACC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,CAAA;IAC7D,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAEhE,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;QAC3B,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE;QAC9C,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;KAC3C,CAAC,CAAA;IAEF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAA;IACrD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACpC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,IAAI,GAAG,CAAC,CAAA;IACzC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,IAAI,GAAG,CAAC,CAAA;IACzC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;IAC7C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;IAE1C,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAA;AACvB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,QAAiB,EACjB,OAAgB,EAChB,OAAa,IAAI;IAEjB,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAClB,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,SAAS,KAAK,OAAO,GAAG,CAAC,CAAA;IAC7C,CAAC;IACD,IAAI,QAAQ,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,uBAAuB,QAAQ,GAAG,CAAC,CAAA;IAChD,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,uCAAuC,MAAM,0DAA0D,CAAA;IAChH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,CAAC,SAAS,IAAI;QACrB,EAAE;QACF,WAAW,MAAM,IAAI;QACrB,EAAE;QACF,GAAG,KAAK;QACR,EAAE;QACF,IAAI,EAAE,CAAC,UAAU,GAAG;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC;AAED,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,SAAiB,EACjB,cAAsB,EACtB,cAAsB,EACtB,IAAY,EACZ,eAAuB;IAEvB,uGAAuG;IACvG,uEAAuE;IACvE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,cAAc,EAAE,cAAc,EAAE,IAAI,CAAC,CAAA;QAC7E,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAA;QAC9B,OAAO,SAAS,GAAG,SAAS,eAAe,eAAe,OAAO,EAAE,CAAA;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC"}
|
package/dist/http.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
3
|
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
|
|
4
4
|
import { isSmapiConfigured } from './transport/index.js';
|
|
5
|
+
import { installSbbTransportAdapters } from './transport/setup.js';
|
|
5
6
|
import { isSwissTripConfigured } from './swisstrip.js';
|
|
6
7
|
import { createSbbMcpServer } from './tools.js';
|
|
7
8
|
import { isRateLimited } from './rate-limit.js';
|
|
9
|
+
import { decodeShortlink, resolveTargetUrl } from './shortlink.js';
|
|
10
|
+
import { recordClick, getRatioSnapshot } from './look2book.js';
|
|
8
11
|
const useMock = !isSmapiConfigured();
|
|
9
|
-
const useCloud = isSwissTripConfigured();
|
|
10
12
|
async function main() {
|
|
13
|
+
installSbbTransportAdapters();
|
|
11
14
|
const port = parseInt(process.env.PORT || '3001');
|
|
12
15
|
const app = createMcpExpressApp({ host: '0.0.0.0' });
|
|
13
16
|
app.post('/mcp', async (req, res) => {
|
|
@@ -28,7 +31,58 @@ async function main() {
|
|
|
28
31
|
}
|
|
29
32
|
});
|
|
30
33
|
app.get('/health', (_req, res) => {
|
|
31
|
-
|
|
34
|
+
// Expose Look2Book state so readiness probes + dashboards can surface
|
|
35
|
+
// contractual ratio violations without parsing stdout logs.
|
|
36
|
+
res.json({
|
|
37
|
+
status: 'ok',
|
|
38
|
+
mode: useMock ? 'mock' : 'live',
|
|
39
|
+
look2book: getRatioSnapshot(),
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
// ─── Booking redirect ─────────────────────────────────────────────────
|
|
43
|
+
//
|
|
44
|
+
// Every booking URL in our tool responses points at this endpoint. We
|
|
45
|
+
// log the click (for SBB Look2Book telemetry + channel attribution),
|
|
46
|
+
// then 302 to the real sbb.ch deep link. Stateless: everything needed
|
|
47
|
+
// is encoded in `:token`.
|
|
48
|
+
//
|
|
49
|
+
// Log format is intentionally a single JSON line per click so Railway's
|
|
50
|
+
// log drain (or future log forwarder) can ingest it directly.
|
|
51
|
+
app.get('/r/:token', (req, res) => {
|
|
52
|
+
const { token } = req.params;
|
|
53
|
+
try {
|
|
54
|
+
const payload = decodeShortlink(token);
|
|
55
|
+
const target = resolveTargetUrl(payload);
|
|
56
|
+
const log = {
|
|
57
|
+
event: 'sbb_booking_click',
|
|
58
|
+
ts: new Date().toISOString(),
|
|
59
|
+
tripId: payload.tid,
|
|
60
|
+
origin: payload.o,
|
|
61
|
+
destination: payload.d,
|
|
62
|
+
date: payload.dt,
|
|
63
|
+
time: payload.t,
|
|
64
|
+
lang: payload.l,
|
|
65
|
+
ip: req.ip || req.socket.remoteAddress || 'unknown',
|
|
66
|
+
ua: req.get('user-agent') || 'unknown',
|
|
67
|
+
referer: req.get('referer') || '',
|
|
68
|
+
};
|
|
69
|
+
// One-line JSON for easy grep/ingest. Railway captures stdout.
|
|
70
|
+
console.log(JSON.stringify(log));
|
|
71
|
+
// Credit the Look2Book ratio — this represents real booking intent
|
|
72
|
+
// and offsets SMAPI calls in both the trips and offers windows.
|
|
73
|
+
recordClick({ tripId: payload.tid });
|
|
74
|
+
res.redirect(302, target);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
78
|
+
console.warn(JSON.stringify({
|
|
79
|
+
event: 'sbb_booking_click_invalid',
|
|
80
|
+
ts: new Date().toISOString(),
|
|
81
|
+
reason: msg,
|
|
82
|
+
token: token.slice(0, 32), // truncate in case it's garbage
|
|
83
|
+
}));
|
|
84
|
+
res.status(400).type('text/plain').send(`Invalid booking link: ${msg}`);
|
|
85
|
+
}
|
|
32
86
|
});
|
|
33
87
|
app.get('/.well-known/mcp/server-card.json', (_req, res) => {
|
|
34
88
|
res.json({
|
|
@@ -50,8 +104,10 @@ async function main() {
|
|
|
50
104
|
console.log(`[sbb-mcp] HTTP server listening on port ${port}`);
|
|
51
105
|
console.log(`[sbb-mcp] MCP endpoint: http://0.0.0.0:${port}/mcp`);
|
|
52
106
|
console.log(`[sbb-mcp] Mode: ${useMock ? 'MOCK' : 'LIVE'}`);
|
|
53
|
-
console.log(`[sbb-mcp] SwissTrip cloud: ${
|
|
107
|
+
console.log(`[sbb-mcp] SwissTrip cloud: ${isSwissTripConfigured() ? 'enabled' : 'disabled'}`);
|
|
54
108
|
console.log(`[sbb-mcp] Rate limit: 60 req/min per IP`);
|
|
109
|
+
console.log(`[sbb-mcp] Booking redirects: /r/:token (logs to stdout)`);
|
|
110
|
+
console.log(`[sbb-mcp] Look2Book telemetry: 1h window, caps trips=100:1 offers=50:1`);
|
|
55
111
|
});
|
|
56
112
|
}
|
|
57
113
|
main().catch((err) => {
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAA;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAA;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { isSmapiConfigured } from './transport/index.js';
|
|
4
|
+
import { installSbbTransportAdapters } from './transport/setup.js';
|
|
4
5
|
import { isSwissTripConfigured } from './swisstrip.js';
|
|
5
6
|
import { createSbbMcpServer } from './tools.js';
|
|
6
7
|
async function main() {
|
|
8
|
+
installSbbTransportAdapters();
|
|
7
9
|
const server = createSbbMcpServer();
|
|
8
10
|
const transport = new StdioServerTransport();
|
|
9
11
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAE/B,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;QACjF,OAAO,CAAC,KAAK,CAAC,+EAA+E,CAAC,CAAA;IAChG,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAA;IACnD,CAAC;IAED,IAAI,qBAAqB,EAAE,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;IACnF,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAA;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAA;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,KAAK,UAAU,IAAI;IACjB,2BAA2B,EAAE,CAAA;IAC7B,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAE/B,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;QACjF,OAAO,CAAC,KAAK,CAAC,+EAA+E,CAAC,CAAA;IAChG,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAA;IACnD,CAAC;IAED,IAAI,qBAAqB,EAAE,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;IACnF,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAA;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* look2book.ts — Look2Book ratio telemetry for SBB SMAPI calls.
|
|
3
|
+
*
|
|
4
|
+
* ## Why
|
|
5
|
+
* SBB's Distributionsvertrag caps the API-call-to-booking-click ratio:
|
|
6
|
+
* - Trips API (places, trips, trip-details, pagination): max 100 : 1
|
|
7
|
+
* - Offers API (prices, trip-offers): max 50 : 1
|
|
8
|
+
*
|
|
9
|
+
* Violating the ratio is a contractual issue, not a technical one — SBB
|
|
10
|
+
* audits quarterly. We need to: (1) observe the ratio in production so we
|
|
11
|
+
* can intervene before an audit finds us over, and (2) emit an operator
|
|
12
|
+
* warning when a running window crosses the threshold so the on-call can
|
|
13
|
+
* investigate (bot traffic? broken booking flow? missing CTAs?).
|
|
14
|
+
*
|
|
15
|
+
* ## What this module does — and doesn't do
|
|
16
|
+
* Does:
|
|
17
|
+
* - Count every SMAPI request (classified `trips` vs `offers` by base URL)
|
|
18
|
+
* and every booking-link click (from `/r/:token`) on a sliding 1-hour
|
|
19
|
+
* window.
|
|
20
|
+
* - Emit a JSON line per event to stdout so Railway's log drain (or any
|
|
21
|
+
* log forwarder) can ship it to long-term storage for trend analysis.
|
|
22
|
+
* - Log a `sbb_look2book_violation` warning JSON line once per 5-minute
|
|
23
|
+
* cooldown per family when the current window's ratio exceeds the cap.
|
|
24
|
+
* - Expose a synchronous `getRatioSnapshot()` so `/health` can surface the
|
|
25
|
+
* current state for readiness probes / dashboards.
|
|
26
|
+
*
|
|
27
|
+
* Does NOT:
|
|
28
|
+
* - Block or rate-limit violating requests. Look2Book is a business metric,
|
|
29
|
+
* not a request gate — gating tool calls based on a ratio would break UX
|
|
30
|
+
* catastrophically (e.g. a single user who hasn't clicked yet would see
|
|
31
|
+
* subsequent searches fail). See rate-limit.ts for the actual per-IP gate.
|
|
32
|
+
* - Persist counters. Railway restarts reset the window — acceptable since
|
|
33
|
+
* the contractual window is aggregate and real analysis happens offline
|
|
34
|
+
* via the JSON-line logs.
|
|
35
|
+
*
|
|
36
|
+
* ## Design: in-memory sliding window
|
|
37
|
+
* A single array of `{ts, kind}` events, pruned at every read/write so old
|
|
38
|
+
* entries fall off after `WINDOW_MS`. O(n) prune on each call, but n is
|
|
39
|
+
* bounded by the per-IP rate limit (60/min × 60min ≈ 3.6k events worst
|
|
40
|
+
* case) so this stays well under a millisecond.
|
|
41
|
+
*
|
|
42
|
+
* Events are timestamped with an injectable `now()` so tests can drive the
|
|
43
|
+
* window deterministically without `vi.useFakeTimers`.
|
|
44
|
+
*/
|
|
45
|
+
/** Tool family — matches the two SMAPI base URLs in smapi-client.ts. */
|
|
46
|
+
export type ApiFamily = 'trips' | 'offers';
|
|
47
|
+
/** Snapshot returned by getRatioSnapshot() — safe to JSON-serialize. */
|
|
48
|
+
export interface RatioSnapshot {
|
|
49
|
+
windowMs: number;
|
|
50
|
+
trips: {
|
|
51
|
+
calls: number;
|
|
52
|
+
clicks: number;
|
|
53
|
+
ratio: number | null;
|
|
54
|
+
cap: number;
|
|
55
|
+
violated: boolean;
|
|
56
|
+
};
|
|
57
|
+
offers: {
|
|
58
|
+
calls: number;
|
|
59
|
+
clicks: number;
|
|
60
|
+
ratio: number | null;
|
|
61
|
+
cap: number;
|
|
62
|
+
violated: boolean;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Record a successful SMAPI call. Call AFTER the request succeeds so failed
|
|
67
|
+
* requests (auth errors, network blips) don't pollute the ratio — only
|
|
68
|
+
* billable, data-returning calls count against the cap.
|
|
69
|
+
*
|
|
70
|
+
* The `endpoint` arg is logged for offline attribution (which tool is the
|
|
71
|
+
* worst offender?) but doesn't affect bucketing.
|
|
72
|
+
*/
|
|
73
|
+
export declare function recordApiCall(family: ApiFamily, endpoint: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Record a booking-link click (from /r/:token). The click "pays back" one
|
|
76
|
+
* unit of ratio for either family — SBB's contract counts all clicks toward
|
|
77
|
+
* both caps since a single click represents real user booking intent.
|
|
78
|
+
*/
|
|
79
|
+
export declare function recordClick(meta?: {
|
|
80
|
+
tripId?: string;
|
|
81
|
+
}): void;
|
|
82
|
+
/**
|
|
83
|
+
* Current window state. Cheap (bounded prune + single pass). Safe to call
|
|
84
|
+
* from a health endpoint handler without a cache.
|
|
85
|
+
*/
|
|
86
|
+
export declare function getRatioSnapshot(): RatioSnapshot;
|
|
87
|
+
/** Reset all state. Call between tests. */
|
|
88
|
+
export declare function __resetForTesting(): void;
|
|
89
|
+
/** Override the clock for deterministic window tests. */
|
|
90
|
+
export declare function __setClockForTesting(fn: () => number): void;
|
|
91
|
+
/** Expose tunables so tests can read them instead of duplicating the numbers. */
|
|
92
|
+
export declare const __internals: {
|
|
93
|
+
readonly WINDOW_MS: number;
|
|
94
|
+
readonly CAP_TRIPS: 100;
|
|
95
|
+
readonly CAP_OFFERS: 50;
|
|
96
|
+
readonly ALERT_COOLDOWN_MS: number;
|
|
97
|
+
readonly MIN_CALLS_BEFORE_ALERT: 10;
|
|
98
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* look2book.ts — Look2Book ratio telemetry for SBB SMAPI calls.
|
|
3
|
+
*
|
|
4
|
+
* ## Why
|
|
5
|
+
* SBB's Distributionsvertrag caps the API-call-to-booking-click ratio:
|
|
6
|
+
* - Trips API (places, trips, trip-details, pagination): max 100 : 1
|
|
7
|
+
* - Offers API (prices, trip-offers): max 50 : 1
|
|
8
|
+
*
|
|
9
|
+
* Violating the ratio is a contractual issue, not a technical one — SBB
|
|
10
|
+
* audits quarterly. We need to: (1) observe the ratio in production so we
|
|
11
|
+
* can intervene before an audit finds us over, and (2) emit an operator
|
|
12
|
+
* warning when a running window crosses the threshold so the on-call can
|
|
13
|
+
* investigate (bot traffic? broken booking flow? missing CTAs?).
|
|
14
|
+
*
|
|
15
|
+
* ## What this module does — and doesn't do
|
|
16
|
+
* Does:
|
|
17
|
+
* - Count every SMAPI request (classified `trips` vs `offers` by base URL)
|
|
18
|
+
* and every booking-link click (from `/r/:token`) on a sliding 1-hour
|
|
19
|
+
* window.
|
|
20
|
+
* - Emit a JSON line per event to stdout so Railway's log drain (or any
|
|
21
|
+
* log forwarder) can ship it to long-term storage for trend analysis.
|
|
22
|
+
* - Log a `sbb_look2book_violation` warning JSON line once per 5-minute
|
|
23
|
+
* cooldown per family when the current window's ratio exceeds the cap.
|
|
24
|
+
* - Expose a synchronous `getRatioSnapshot()` so `/health` can surface the
|
|
25
|
+
* current state for readiness probes / dashboards.
|
|
26
|
+
*
|
|
27
|
+
* Does NOT:
|
|
28
|
+
* - Block or rate-limit violating requests. Look2Book is a business metric,
|
|
29
|
+
* not a request gate — gating tool calls based on a ratio would break UX
|
|
30
|
+
* catastrophically (e.g. a single user who hasn't clicked yet would see
|
|
31
|
+
* subsequent searches fail). See rate-limit.ts for the actual per-IP gate.
|
|
32
|
+
* - Persist counters. Railway restarts reset the window — acceptable since
|
|
33
|
+
* the contractual window is aggregate and real analysis happens offline
|
|
34
|
+
* via the JSON-line logs.
|
|
35
|
+
*
|
|
36
|
+
* ## Design: in-memory sliding window
|
|
37
|
+
* A single array of `{ts, kind}` events, pruned at every read/write so old
|
|
38
|
+
* entries fall off after `WINDOW_MS`. O(n) prune on each call, but n is
|
|
39
|
+
* bounded by the per-IP rate limit (60/min × 60min ≈ 3.6k events worst
|
|
40
|
+
* case) so this stays well under a millisecond.
|
|
41
|
+
*
|
|
42
|
+
* Events are timestamped with an injectable `now()` so tests can drive the
|
|
43
|
+
* window deterministically without `vi.useFakeTimers`.
|
|
44
|
+
*/
|
|
45
|
+
// ─── Tunables ───────────────────────────────────────────────────────────────
|
|
46
|
+
/** Rolling window length. 1 hour balances responsiveness vs noise. */
|
|
47
|
+
const WINDOW_MS = 60 * 60 * 1000;
|
|
48
|
+
/** Maximum API-calls-to-clicks ratio per family — contractual caps. */
|
|
49
|
+
const CAP_TRIPS = 100;
|
|
50
|
+
const CAP_OFFERS = 50;
|
|
51
|
+
/** Minimum gap between duplicate violation warnings per family. */
|
|
52
|
+
const ALERT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
53
|
+
/**
|
|
54
|
+
* Don't warn on the first few calls of a fresh window — a single call with
|
|
55
|
+
* zero clicks gives infinite ratio, which is technically a violation but
|
|
56
|
+
* operationally meaningless. Wait until we have enough calls to reason about.
|
|
57
|
+
*/
|
|
58
|
+
const MIN_CALLS_BEFORE_ALERT = 10;
|
|
59
|
+
// ─── Mutable module state ───────────────────────────────────────────────────
|
|
60
|
+
const events = [];
|
|
61
|
+
const lastAlertTs = { trips: 0, offers: 0 };
|
|
62
|
+
/** Clock injector for deterministic tests. Defaults to Date.now. */
|
|
63
|
+
let nowFn = () => Date.now();
|
|
64
|
+
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
65
|
+
function pruneOld(now) {
|
|
66
|
+
const cutoff = now - WINDOW_MS;
|
|
67
|
+
// Array.shift is O(n) but events is bounded (~3.6k max); fine.
|
|
68
|
+
while (events.length > 0 && events[0].ts < cutoff) {
|
|
69
|
+
events.shift();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function countsFor(family) {
|
|
73
|
+
let calls = 0;
|
|
74
|
+
let clicks = 0;
|
|
75
|
+
for (const ev of events) {
|
|
76
|
+
if (ev.kind === family)
|
|
77
|
+
calls++;
|
|
78
|
+
else if (ev.kind === 'click')
|
|
79
|
+
clicks++;
|
|
80
|
+
}
|
|
81
|
+
return { calls, clicks };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Current ratio for a family. Returns `null` when no calls yet (undefined
|
|
85
|
+
* ratio). When calls > 0 and clicks == 0, returns `Infinity` — callers
|
|
86
|
+
* should treat this as "over the cap" but violation logic also checks
|
|
87
|
+
* MIN_CALLS_BEFORE_ALERT to avoid spurious warnings.
|
|
88
|
+
*/
|
|
89
|
+
function ratioFor(family) {
|
|
90
|
+
const { calls, clicks } = countsFor(family);
|
|
91
|
+
if (calls === 0)
|
|
92
|
+
return null;
|
|
93
|
+
if (clicks === 0)
|
|
94
|
+
return Infinity;
|
|
95
|
+
return calls / clicks;
|
|
96
|
+
}
|
|
97
|
+
function capFor(family) {
|
|
98
|
+
return family === 'trips' ? CAP_TRIPS : CAP_OFFERS;
|
|
99
|
+
}
|
|
100
|
+
function logJson(line) {
|
|
101
|
+
// Single JSON line per event — consistent with the /r/:token click log.
|
|
102
|
+
// Using stdout directly; Railway (and most log drains) capture it verbatim.
|
|
103
|
+
console.log(JSON.stringify(line));
|
|
104
|
+
}
|
|
105
|
+
function checkAndWarn(family, now) {
|
|
106
|
+
const { calls, clicks } = countsFor(family);
|
|
107
|
+
if (calls < MIN_CALLS_BEFORE_ALERT)
|
|
108
|
+
return;
|
|
109
|
+
const cap = capFor(family);
|
|
110
|
+
const ratio = ratioFor(family);
|
|
111
|
+
if (ratio === null || ratio <= cap)
|
|
112
|
+
return;
|
|
113
|
+
if (now - lastAlertTs[family] < ALERT_COOLDOWN_MS)
|
|
114
|
+
return;
|
|
115
|
+
lastAlertTs[family] = now;
|
|
116
|
+
console.warn(JSON.stringify({
|
|
117
|
+
event: 'sbb_look2book_violation',
|
|
118
|
+
ts: new Date(now).toISOString(),
|
|
119
|
+
family,
|
|
120
|
+
calls,
|
|
121
|
+
clicks,
|
|
122
|
+
ratio: ratio === Infinity ? 'infinity' : Number(ratio.toFixed(2)),
|
|
123
|
+
cap,
|
|
124
|
+
windowMs: WINDOW_MS,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
128
|
+
/**
|
|
129
|
+
* Record a successful SMAPI call. Call AFTER the request succeeds so failed
|
|
130
|
+
* requests (auth errors, network blips) don't pollute the ratio — only
|
|
131
|
+
* billable, data-returning calls count against the cap.
|
|
132
|
+
*
|
|
133
|
+
* The `endpoint` arg is logged for offline attribution (which tool is the
|
|
134
|
+
* worst offender?) but doesn't affect bucketing.
|
|
135
|
+
*/
|
|
136
|
+
export function recordApiCall(family, endpoint) {
|
|
137
|
+
const now = nowFn();
|
|
138
|
+
pruneOld(now);
|
|
139
|
+
events.push({ ts: now, kind: family, endpoint });
|
|
140
|
+
logJson({
|
|
141
|
+
event: 'sbb_smapi_call',
|
|
142
|
+
ts: new Date(now).toISOString(),
|
|
143
|
+
family,
|
|
144
|
+
endpoint,
|
|
145
|
+
});
|
|
146
|
+
checkAndWarn(family, now);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Record a booking-link click (from /r/:token). The click "pays back" one
|
|
150
|
+
* unit of ratio for either family — SBB's contract counts all clicks toward
|
|
151
|
+
* both caps since a single click represents real user booking intent.
|
|
152
|
+
*/
|
|
153
|
+
export function recordClick(meta = {}) {
|
|
154
|
+
const now = nowFn();
|
|
155
|
+
pruneOld(now);
|
|
156
|
+
events.push({ ts: now, kind: 'click' });
|
|
157
|
+
logJson({
|
|
158
|
+
event: 'sbb_look2book_click',
|
|
159
|
+
ts: new Date(now).toISOString(),
|
|
160
|
+
...(meta.tripId && { tripId: meta.tripId }),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Current window state. Cheap (bounded prune + single pass). Safe to call
|
|
165
|
+
* from a health endpoint handler without a cache.
|
|
166
|
+
*/
|
|
167
|
+
export function getRatioSnapshot() {
|
|
168
|
+
const now = nowFn();
|
|
169
|
+
pruneOld(now);
|
|
170
|
+
const t = countsFor('trips');
|
|
171
|
+
const o = countsFor('offers');
|
|
172
|
+
const tRatio = ratioFor('trips');
|
|
173
|
+
const oRatio = ratioFor('offers');
|
|
174
|
+
return {
|
|
175
|
+
windowMs: WINDOW_MS,
|
|
176
|
+
trips: {
|
|
177
|
+
calls: t.calls,
|
|
178
|
+
clicks: t.clicks,
|
|
179
|
+
ratio: tRatio === Infinity ? null : tRatio,
|
|
180
|
+
cap: CAP_TRIPS,
|
|
181
|
+
violated: tRatio !== null && tRatio > CAP_TRIPS,
|
|
182
|
+
},
|
|
183
|
+
offers: {
|
|
184
|
+
calls: o.calls,
|
|
185
|
+
clicks: o.clicks,
|
|
186
|
+
ratio: oRatio === Infinity ? null : oRatio,
|
|
187
|
+
cap: CAP_OFFERS,
|
|
188
|
+
violated: oRatio !== null && oRatio > CAP_OFFERS,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// ─── Test hooks ─────────────────────────────────────────────────────────────
|
|
193
|
+
/** Reset all state. Call between tests. */
|
|
194
|
+
export function __resetForTesting() {
|
|
195
|
+
events.length = 0;
|
|
196
|
+
lastAlertTs.trips = 0;
|
|
197
|
+
lastAlertTs.offers = 0;
|
|
198
|
+
nowFn = () => Date.now();
|
|
199
|
+
}
|
|
200
|
+
/** Override the clock for deterministic window tests. */
|
|
201
|
+
export function __setClockForTesting(fn) {
|
|
202
|
+
nowFn = fn;
|
|
203
|
+
}
|
|
204
|
+
/** Expose tunables so tests can read them instead of duplicating the numbers. */
|
|
205
|
+
export const __internals = {
|
|
206
|
+
WINDOW_MS,
|
|
207
|
+
CAP_TRIPS,
|
|
208
|
+
CAP_OFFERS,
|
|
209
|
+
ALERT_COOLDOWN_MS,
|
|
210
|
+
MIN_CALLS_BEFORE_ALERT,
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=look2book.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"look2book.js","sourceRoot":"","sources":["../src/look2book.ts"],"names":[],"mappings":"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"}
|