@nestr/mcp 0.1.73 → 0.1.90

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 (45) hide show
  1. package/build/api/client.d.ts +39 -3
  2. package/build/api/client.d.ts.map +1 -1
  3. package/build/api/client.js +57 -5
  4. package/build/api/client.js.map +1 -1
  5. package/build/help/articles.d.ts +146 -0
  6. package/build/help/articles.d.ts.map +1 -0
  7. package/build/help/articles.js +574 -0
  8. package/build/help/articles.js.map +1 -0
  9. package/build/help/cross-links.d.ts +21 -0
  10. package/build/help/cross-links.d.ts.map +1 -0
  11. package/build/help/cross-links.js +61 -0
  12. package/build/help/cross-links.js.map +1 -0
  13. package/build/help/topics.d.ts.map +1 -1
  14. package/build/help/topics.js +315 -15
  15. package/build/help/topics.js.map +1 -1
  16. package/build/http.d.ts +4 -13
  17. package/build/http.d.ts.map +1 -1
  18. package/build/http.js +341 -89
  19. package/build/http.js.map +1 -1
  20. package/build/oauth/client-info.d.ts +58 -0
  21. package/build/oauth/client-info.d.ts.map +1 -0
  22. package/build/oauth/client-info.js +68 -0
  23. package/build/oauth/client-info.js.map +1 -0
  24. package/build/oauth/config.d.ts +19 -0
  25. package/build/oauth/config.d.ts.map +1 -1
  26. package/build/oauth/config.js +12 -0
  27. package/build/oauth/config.js.map +1 -1
  28. package/build/server.d.ts +7 -0
  29. package/build/server.d.ts.map +1 -1
  30. package/build/server.js +23 -4
  31. package/build/server.js.map +1 -1
  32. package/build/skills/tension-processing.d.ts.map +1 -1
  33. package/build/skills/tension-processing.js +11 -1
  34. package/build/skills/tension-processing.js.map +1 -1
  35. package/build/tools/index.d.ts +612 -68
  36. package/build/tools/index.d.ts.map +1 -1
  37. package/build/tools/index.js +592 -63
  38. package/build/tools/index.js.map +1 -1
  39. package/build/tools/validation.d.ts +42 -0
  40. package/build/tools/validation.d.ts.map +1 -0
  41. package/build/tools/validation.js +97 -0
  42. package/build/tools/validation.js.map +1 -0
  43. package/package.json +2 -1
  44. package/web/index.html +25 -0
  45. package/web/styles.css +62 -0
@@ -0,0 +1,574 @@
1
+ /**
2
+ * Help-article integration. Sources `/help/articles/<slug>` URLs from the
3
+ * public nestr.io sitemap, exposes simple token-overlap search by slug, and
4
+ * fetches an article page lazily on demand (converting it to markdown so it
5
+ * fits in a tool response).
6
+ *
7
+ * The internal `nestr_help` topics in topics.ts are curated MCP-flavoured
8
+ * guidance. Articles are end-user UI docs. The tool routes between them:
9
+ * exact internal topic match first, then article search/fetch.
10
+ */
11
+ const SITEMAP_URL = "https://nestr.io/sitemap.xml";
12
+ const HELP_ARTICLE_PREFIX = "https://nestr.io/help/articles/";
13
+ const INDEX_TTL_MS = 15 * 60 * 1000;
14
+ const ARTICLE_TTL_MS = 15 * 60 * 1000;
15
+ const FETCH_TIMEOUT_MS = 8000;
16
+ // Inline-image limits (base64 attachment for hosts that render images).
17
+ const DEFAULT_INLINE_IMAGES = 3;
18
+ const MAX_INLINE_IMAGES_CAP = 6; // upper bound when a caller raises maxImages
19
+ const MAX_IMAGE_BYTES = 2 * 1024 * 1024; // 2 MB per source image — Webflow screenshots are far smaller
20
+ const IMAGE_CACHE_MAX = 50;
21
+ const MAX_IMAGE_WIDTH = 1200; // downscale wider screenshots to bound token cost
22
+ const IMAGE_JPEG_QUALITY = 80;
23
+ let indexCache = null;
24
+ const articleCache = new Map();
25
+ const metaCache = new Map();
26
+ const imageCache = new Map();
27
+ // Test-only: reset caches between cases without poking at module internals.
28
+ export function _resetCaches() {
29
+ indexCache = null;
30
+ articleCache.clear();
31
+ metaCache.clear();
32
+ imageCache.clear();
33
+ }
34
+ async function fetchWithTimeout(url) {
35
+ const controller = new AbortController();
36
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
37
+ try {
38
+ return await fetch(url, { signal: controller.signal });
39
+ }
40
+ finally {
41
+ clearTimeout(timer);
42
+ }
43
+ }
44
+ /**
45
+ * Load (or return cached) the list of help-article slugs from the sitemap.
46
+ * Sitemap is plain XML with `<loc>...</loc>` entries; we only need the URLs,
47
+ * so a regex extract is cheaper than pulling in an XML parser.
48
+ */
49
+ export async function loadArticleIndex() {
50
+ if (indexCache && Date.now() < indexCache.expiresAt) {
51
+ return indexCache.entries;
52
+ }
53
+ const res = await fetchWithTimeout(SITEMAP_URL);
54
+ if (!res.ok) {
55
+ throw new Error(`Sitemap fetch failed: ${res.status} ${res.statusText}`);
56
+ }
57
+ const xml = await res.text();
58
+ const entries = [];
59
+ for (const match of xml.matchAll(/<loc>([^<]+)<\/loc>/g)) {
60
+ const url = match[1].trim();
61
+ if (url.startsWith(HELP_ARTICLE_PREFIX)) {
62
+ const slug = url.slice(HELP_ARTICLE_PREFIX.length).replace(/\/$/, "");
63
+ if (slug)
64
+ entries.push({ slug, url });
65
+ }
66
+ }
67
+ indexCache = { entries, expiresAt: Date.now() + INDEX_TTL_MS };
68
+ return entries;
69
+ }
70
+ /**
71
+ * Extra search keywords for articles whose slug undersells their content, so a
72
+ * query like "kanban" or "burndown" still finds `scrum-agile-app`. Keyed by
73
+ * slug; merged into the search haystack alongside the slug-as-words. This is
74
+ * the synonym layer — keep it focused on real user vocabulary that the slug
75
+ * itself omits.
76
+ */
77
+ const ARTICLE_KEYWORDS = {
78
+ "scrum-agile-app": ["sprint", "sprints", "kanban", "backlog", "burndown", "epic", "epics", "milestone", "milestones", "iteration", "userstory", "story", "stories", "standup", "velocity", "board"],
79
+ "running-meetings-in-nestr": ["tactical", "governance", "standup", "retro", "retrospective", "facilitation", "facilitator", "agenda"],
80
+ "tensions-and-governance-proposals": ["proposal", "proposals", "holacracy", "sociocracy", "amend", "amendment", "objection"],
81
+ "nestr-the-power-of-labels": ["tag", "tags", "tagging"],
82
+ "building-your-org-structure-roles-circles": ["hierarchy", "department", "team", "org", "orgchart", "chart", "accountability", "accountabilities"],
83
+ "giving-or-requesting-feedback-in-nestr": ["review", "praise", "kudos", "appraisal"],
84
+ "projects-and-todos-creating-tracking-managing-work": ["task", "tasks", "todo", "todos", "project", "projects", "checklist", "deadline"],
85
+ "nestr-mcp-connect-ai-assistants-to-your-workspace": ["mcp", "assistant", "assistants", "claude", "cursor", "llm", "agent"],
86
+ "chat-channels-and-communication-in-nestr": ["chat", "message", "messaging", "channel", "channels", "notification", "notifications", "mention", "comment"],
87
+ "managing-users-invitations-permissions": ["invite", "invitation", "permission", "permissions", "member", "members", "user", "users", "access"],
88
+ "pricing-plans-what-you-pay-for": ["pricing", "price", "plan", "plans", "billing", "subscription", "cost", "payment"],
89
+ };
90
+ /**
91
+ * Classic Levenshtein distance, capped for our needs. Only used to rescue
92
+ * near-miss typos (e.g. "scum" → "scrum") against single words, so the full
93
+ * matrix on short inputs is fine. Returns early past our max threshold.
94
+ */
95
+ function levenshtein(a, b) {
96
+ const m = a.length, n = b.length;
97
+ if (Math.abs(m - n) > 2)
98
+ return 3;
99
+ const prev = new Array(n + 1);
100
+ for (let j = 0; j <= n; j++)
101
+ prev[j] = j;
102
+ for (let i = 1; i <= m; i++) {
103
+ let diag = prev[0];
104
+ prev[0] = i;
105
+ for (let j = 1; j <= n; j++) {
106
+ const tmp = prev[j];
107
+ prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
108
+ diag = tmp;
109
+ }
110
+ }
111
+ return prev[n];
112
+ }
113
+ /**
114
+ * Token-overlap search against slug-as-words plus curated keywords. Slugs are
115
+ * descriptive (e.g. `building-your-org-structure-roles-circles`), so
116
+ * dash-to-space gives a usable signal without fetching every article's title.
117
+ * Exact substring matches score 1; a typo rescued by Levenshtein scores 0.5,
118
+ * so an exact hit always outranks a fuzzy one.
119
+ */
120
+ export function searchArticleIndex(entries, query, limit = 10) {
121
+ const tokens = query
122
+ .toLowerCase()
123
+ .split(/[^a-z0-9]+/)
124
+ .filter(t => t.length >= 2);
125
+ if (tokens.length === 0)
126
+ return [];
127
+ const hits = [];
128
+ for (const entry of entries) {
129
+ const slugWords = entry.slug.toLowerCase().replace(/-/g, " ");
130
+ const keywords = ARTICLE_KEYWORDS[entry.slug] ?? [];
131
+ const haystack = keywords.length ? `${slugWords} ${keywords.join(" ")}` : slugWords;
132
+ const words = haystack.split(/\s+/).filter(Boolean);
133
+ let score = 0;
134
+ for (const token of tokens) {
135
+ if (haystack.includes(token)) {
136
+ score += 1; // exact substring match (original behaviour)
137
+ }
138
+ else if (token.length >= 4) {
139
+ // Typo rescue: allow 1 edit for short tokens, 2 for longer ones.
140
+ const threshold = token.length >= 7 ? 2 : 1;
141
+ if (words.some(w => w.length >= 4 && levenshtein(w, token) <= threshold)) {
142
+ score += 0.5; // weaker than exact, so exact matches win ties
143
+ }
144
+ }
145
+ }
146
+ if (score > 0)
147
+ hits.push({ ...entry, score });
148
+ }
149
+ hits.sort((a, b) => b.score - a.score || a.slug.localeCompare(b.slug));
150
+ return hits.slice(0, limit);
151
+ }
152
+ /**
153
+ * Fetch a single article and convert to a readable markdown payload. Cached
154
+ * per-slug for ARTICLE_TTL_MS. The conversion is intentionally lossy —
155
+ * Webflow output is noisy, and the LLM only needs the textual body.
156
+ */
157
+ export async function fetchArticleMarkdown(slug) {
158
+ const cleanSlug = slug.replace(/^\/+|\/+$/g, "");
159
+ const cached = articleCache.get(cleanSlug);
160
+ if (cached && Date.now() < cached.expiresAt) {
161
+ return {
162
+ slug: cleanSlug,
163
+ url: HELP_ARTICLE_PREFIX + cleanSlug,
164
+ title: cached.title,
165
+ description: cached.description,
166
+ markdown: cached.markdown,
167
+ };
168
+ }
169
+ const url = HELP_ARTICLE_PREFIX + cleanSlug;
170
+ const res = await fetchWithTimeout(url);
171
+ if (!res.ok) {
172
+ throw new Error(`Article fetch failed for "${cleanSlug}": ${res.status} ${res.statusText}`);
173
+ }
174
+ const html = await res.text();
175
+ const { title, description } = extractArticleMeta(html);
176
+ const markdown = htmlToMarkdown(extractArticleBody(html, title));
177
+ articleCache.set(cleanSlug, {
178
+ title,
179
+ description,
180
+ markdown,
181
+ expiresAt: Date.now() + ARTICLE_TTL_MS,
182
+ });
183
+ metaCache.set(cleanSlug, { title, description, expiresAt: Date.now() + ARTICLE_TTL_MS });
184
+ return { slug: cleanSlug, url, title, description, markdown };
185
+ }
186
+ /**
187
+ * Fetch just an article's title + description, for search-result snippets,
188
+ * without converting the whole body. Reuses a full-article or prior meta cache
189
+ * entry when one is fresh, and caches meta separately so repeated searches are
190
+ * cheap. Throws on a non-OK response — callers enrich best-effort and fall back
191
+ * to the bare slug on failure.
192
+ */
193
+ export async function fetchArticleMeta(slug) {
194
+ const cleanSlug = slug.replace(/^\/+|\/+$/g, "");
195
+ const cachedMeta = metaCache.get(cleanSlug);
196
+ if (cachedMeta && Date.now() < cachedMeta.expiresAt) {
197
+ return { slug: cleanSlug, title: cachedMeta.title, description: cachedMeta.description };
198
+ }
199
+ const full = articleCache.get(cleanSlug);
200
+ if (full && Date.now() < full.expiresAt) {
201
+ return { slug: cleanSlug, title: full.title, description: full.description };
202
+ }
203
+ const res = await fetchWithTimeout(HELP_ARTICLE_PREFIX + cleanSlug);
204
+ if (!res.ok) {
205
+ throw new Error(`Article meta fetch failed for "${cleanSlug}": ${res.status} ${res.statusText}`);
206
+ }
207
+ const { title, description } = extractArticleMeta(await res.text());
208
+ metaCache.set(cleanSlug, { title, description, expiresAt: Date.now() + ARTICLE_TTL_MS });
209
+ return { slug: cleanSlug, title, description };
210
+ }
211
+ /**
212
+ * Pull the in-body images out of converted article markdown as structured data
213
+ * so callers can surface screenshots as first-class items (some MCP hosts
214
+ * render them inline; text-only clients still get the caption + URL). SVGs are
215
+ * skipped — on these pages they're UI chrome/icons, not content. Dedupes by
216
+ * URL, preserves document order. Run this on the article body markdown so nav
217
+ * and marketing imagery (already cut by extractArticleBody) stays out.
218
+ *
219
+ * Each image is flagged `decorative` when it has no caption OR appears before
220
+ * the first content heading (the header/category-bar thumbnail at the top of an
221
+ * article). Decorative images are listed and remain addressable by index, but
222
+ * are never part of the default selection. "First content heading" is the second
223
+ * heading in the body — the first is the article title — so when a body has no
224
+ * sub-headings the position rule is skipped and only the caption test applies.
225
+ */
226
+ export function extractImages(markdown) {
227
+ const headings = [...markdown.matchAll(/^#{1,6}[ \t]+/gm)];
228
+ const firstContentHeadingPos = headings.length >= 2 ? (headings[1].index ?? -1) : -1;
229
+ const seen = new Set();
230
+ const images = [];
231
+ // Matches `![alt](url)` and the inner image of a linked image `[![alt](url)](href)`.
232
+ for (const m of markdown.matchAll(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) {
233
+ const url = m[2].trim();
234
+ if (!url || seen.has(url) || /\.svg(\?|#|$)/i.test(url))
235
+ continue;
236
+ seen.add(url);
237
+ const caption = m[1].trim();
238
+ const beforeContent = firstContentHeadingPos >= 0 && (m.index ?? 0) < firstContentHeadingPos;
239
+ images.push({ url, caption, decorative: !caption || beforeContent });
240
+ }
241
+ return images;
242
+ }
243
+ /** Clamp a caller-supplied maxImages to [1, MAX_INLINE_IMAGES_CAP], default 3. */
244
+ export function clampMaxImages(max) {
245
+ if (max === undefined || !Number.isFinite(max) || max < 1)
246
+ return DEFAULT_INLINE_IMAGES;
247
+ return Math.min(Math.floor(max), MAX_INLINE_IMAGES_CAP);
248
+ }
249
+ /**
250
+ * Decide which images (by index into `images`) to attach inline.
251
+ *
252
+ * - Explicit `indexes`: the caller picked specific entries from the numbered
253
+ * list, so honour them verbatim — valid, de-duped, in the given order, with
254
+ * NO cap (overrides maxImages and the default selection, so a decorative
255
+ * image can still be attached on explicit request).
256
+ * - Default: the first `max` non-decorative (content) images in document order.
257
+ * This skips the uncaptioned hero/avatar and any header thumbnail before the
258
+ * first content heading; masthead/footer imagery is already gone because we
259
+ * only see body markdown.
260
+ */
261
+ export function selectImageIndexes(images, opts = {}) {
262
+ const n = images.length;
263
+ if (opts.indexes && opts.indexes.length > 0) {
264
+ const seen = new Set();
265
+ const out = [];
266
+ for (const idx of opts.indexes) {
267
+ if (Number.isInteger(idx) && idx >= 0 && idx < n && !seen.has(idx)) {
268
+ seen.add(idx);
269
+ out.push(idx);
270
+ }
271
+ }
272
+ return out;
273
+ }
274
+ const cap = clampMaxImages(opts.max);
275
+ const out = [];
276
+ for (let i = 0; i < n && out.length < cap; i++) {
277
+ if (!images[i].decorative)
278
+ out.push(i);
279
+ }
280
+ return out;
281
+ }
282
+ /**
283
+ * Guard the image URLs we'll fetch server-side. Image URLs come from
284
+ * Nestr-authored help articles (low risk), but we still only fetch public
285
+ * https origins — never loopback, link-local, or private ranges — as
286
+ * defence-in-depth against SSRF.
287
+ */
288
+ function isFetchableImageUrl(raw) {
289
+ let u;
290
+ try {
291
+ u = new URL(raw);
292
+ }
293
+ catch {
294
+ return false;
295
+ }
296
+ if (u.protocol !== "https:")
297
+ return false;
298
+ const host = u.hostname.toLowerCase();
299
+ // Reject IPv6 literals outright. Legit CDN/help image URLs use DNS hostnames,
300
+ // and IPv6 range-matching is unreliable (Node normalises ::ffff:127.0.0.1 to
301
+ // ::ffff:7f00:1). URL.hostname brackets any IPv6 literal, so this single check
302
+ // covers loopback, link-local, unique-local, and IPv4-mapped addresses.
303
+ if (host.startsWith("["))
304
+ return false;
305
+ if (host === "localhost" || host.endsWith(".local") || host.endsWith(".internal"))
306
+ return false;
307
+ if (host === "0.0.0.0")
308
+ return false;
309
+ if (/^(127\.|10\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(host))
310
+ return false;
311
+ return true;
312
+ }
313
+ function mimeFromExtension(url) {
314
+ const path = url.split(/[?#]/)[0].toLowerCase();
315
+ if (path.endsWith(".png"))
316
+ return "image/png";
317
+ if (path.endsWith(".jpg") || path.endsWith(".jpeg"))
318
+ return "image/jpeg";
319
+ if (path.endsWith(".webp"))
320
+ return "image/webp";
321
+ if (path.endsWith(".gif"))
322
+ return "image/gif";
323
+ return null;
324
+ }
325
+ /**
326
+ * Downscale/recompress an image to keep inline token cost reasonable: anything
327
+ * wider than MAX_IMAGE_WIDTH is resized (aspect preserved) and re-encoded as
328
+ * JPEG. Images already within bounds are returned untouched. Uses jimp (pure
329
+ * JS — no native deps, so it builds on the alpine image), loaded lazily and
330
+ * wrapped so any failure (or jimp being absent) degrades to the original bytes
331
+ * rather than dropping the image.
332
+ */
333
+ async function downscaleImage(buf, mimeType) {
334
+ try {
335
+ const { Jimp } = await import("jimp");
336
+ const img = await Jimp.read(buf);
337
+ if (img.width <= MAX_IMAGE_WIDTH)
338
+ return { data: buf, mimeType }; // already small enough
339
+ // Resize (aspect preserved) and re-encode to JPEG. We always take the
340
+ // resized version when the source is wide: model image-token cost tracks
341
+ // pixel count, so fewer pixels is the win even if JPEG bytes don't shrink.
342
+ img.resize({ w: MAX_IMAGE_WIDTH });
343
+ const out = Buffer.from(await img.getBuffer("image/jpeg", { quality: IMAGE_JPEG_QUALITY }));
344
+ return { data: out, mimeType: "image/jpeg" };
345
+ }
346
+ catch {
347
+ return { data: buf, mimeType };
348
+ }
349
+ }
350
+ /**
351
+ * Fetch a single image and return it as base64 + MIME type for an MCP `image`
352
+ * content block, or null if it can't be safely inlined (disallowed URL,
353
+ * non-image content, too large, or any network error). Wide images are
354
+ * downscaled to bound token cost. Best-effort by design — callers fall back to
355
+ * the text URL list. Bounded FIFO cache by URL (oldest entry evicted once the
356
+ * cap is reached).
357
+ */
358
+ export async function fetchImageAsBase64(url) {
359
+ if (!isFetchableImageUrl(url))
360
+ return null;
361
+ const cached = imageCache.get(url);
362
+ if (cached && Date.now() < cached.expiresAt) {
363
+ return { data: cached.data, mimeType: cached.mimeType };
364
+ }
365
+ try {
366
+ const res = await fetchWithTimeout(url);
367
+ if (!res.ok)
368
+ return null;
369
+ const contentType = (res.headers?.get?.("content-type") || "").split(";")[0].trim().toLowerCase();
370
+ const sourceMime = contentType.startsWith("image/") ? contentType : mimeFromExtension(url);
371
+ if (!sourceMime || sourceMime === "image/svg+xml")
372
+ return null; // svg = chrome/icons, skip
373
+ const declaredLength = Number(res.headers?.get?.("content-length") || 0);
374
+ if (declaredLength > MAX_IMAGE_BYTES)
375
+ return null;
376
+ const buf = Buffer.from(await res.arrayBuffer());
377
+ if (buf.byteLength === 0 || buf.byteLength > MAX_IMAGE_BYTES)
378
+ return null;
379
+ const reduced = await downscaleImage(buf, sourceMime);
380
+ const data = reduced.data.toString("base64");
381
+ const mimeType = reduced.mimeType;
382
+ if (imageCache.size >= IMAGE_CACHE_MAX) {
383
+ const oldest = imageCache.keys().next().value;
384
+ if (oldest !== undefined)
385
+ imageCache.delete(oldest);
386
+ }
387
+ imageCache.set(url, { data, mimeType, expiresAt: Date.now() + ARTICLE_TTL_MS });
388
+ return { data, mimeType };
389
+ }
390
+ catch {
391
+ return null;
392
+ }
393
+ }
394
+ /**
395
+ * Fetch the selected article images (see selectImageIndexes) as inline base64
396
+ * blocks, carrying each image's index in the full list. Concurrent and
397
+ * best-effort — any that can't be inlined are dropped.
398
+ */
399
+ export async function collectArticleImages(images, opts = {}) {
400
+ const indexes = selectImageIndexes(images, opts);
401
+ const settled = await Promise.all(indexes.map(async (i) => {
402
+ const bytes = await fetchImageAsBase64(images[i].url);
403
+ return bytes ? { ...images[i], index: i, ...bytes } : null;
404
+ }));
405
+ return settled.filter((r) => r !== null);
406
+ }
407
+ /**
408
+ * Pull title + description from the article's JSON-LD `TechArticle` block
409
+ * when present (every article currently includes one). Falls back to
410
+ * `<title>` / `<meta name="description">` tags.
411
+ */
412
+ export function extractArticleMeta(html) {
413
+ let title = "";
414
+ let description = "";
415
+ for (const match of html.matchAll(/<script[^>]+application\/ld\+json[^>]*>([\s\S]*?)<\/script>/gi)) {
416
+ try {
417
+ const parsed = JSON.parse(match[1].trim());
418
+ const candidates = Array.isArray(parsed) ? parsed : [parsed];
419
+ for (const item of candidates) {
420
+ if (item && typeof item === "object" && item["@type"] === "TechArticle") {
421
+ title = String(item.headline || "").trim();
422
+ description = String(item.description || "").trim();
423
+ break;
424
+ }
425
+ }
426
+ if (title)
427
+ break;
428
+ }
429
+ catch {
430
+ // ignore malformed JSON-LD; fall through to meta tags
431
+ }
432
+ }
433
+ if (!title) {
434
+ const m = html.match(/<title[^>]*>([^<]+)<\/title>/i);
435
+ if (m)
436
+ title = decodeEntities(m[1]).replace(/\s*\|\s*Nestr Help\s*$/, "").trim();
437
+ }
438
+ if (!description) {
439
+ const m = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']+)["']/i);
440
+ if (m)
441
+ description = decodeEntities(m[1]).trim();
442
+ }
443
+ return { title, description };
444
+ }
445
+ /**
446
+ * Best-effort extraction of the article body. We can't rely on a single
447
+ * named container on Webflow output, so we cut from the article's `<h1>`
448
+ * down to where chrome resumes (footer / nav). When the page has multiple
449
+ * `<h1>` elements (Webflow's hidden signup form puts one in before the
450
+ * article), pass `headlineHint` so we can pick the matching one.
451
+ */
452
+ export function extractArticleBody(html, headlineHint) {
453
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
454
+ let body = bodyMatch ? bodyMatch[1] : html;
455
+ // Drop <script>, <style>, <noscript>, <svg>, and head leftovers before any
456
+ // text-extraction so they don't bleed into the markdown.
457
+ body = body
458
+ .replace(/<script[\s\S]*?<\/script>/gi, "")
459
+ .replace(/<style[\s\S]*?<\/style>/gi, "")
460
+ .replace(/<noscript[\s\S]*?<\/noscript>/gi, "")
461
+ .replace(/<svg[\s\S]*?<\/svg>/gi, "");
462
+ // Pick the right <h1> to start from. If a headline hint is provided, find
463
+ // the <h1> whose visible text contains it (case-insensitive substring).
464
+ // Falls back to the first <h1>, which is correct for pages with a single
465
+ // article heading.
466
+ const h1Re = /<h1\b[^>]*>([\s\S]*?)<\/h1>/gi;
467
+ const h1Matches = [...body.matchAll(h1Re)];
468
+ let startIdx = -1;
469
+ if (headlineHint && h1Matches.length > 1) {
470
+ const needle = headlineHint.toLowerCase().trim();
471
+ for (const m of h1Matches) {
472
+ const inner = m[1].replace(/<[^>]+>/g, "").toLowerCase().trim();
473
+ if (inner && needle.includes(inner) || inner.includes(needle)) {
474
+ startIdx = m.index ?? -1;
475
+ break;
476
+ }
477
+ }
478
+ }
479
+ if (startIdx < 0) {
480
+ const firstH1 = body.search(/<h1\b/i);
481
+ startIdx = firstH1;
482
+ }
483
+ if (startIdx > -1)
484
+ body = body.slice(startIdx);
485
+ // Cut at the start of the page footer. Webflow uses both `<footer>` and
486
+ // `<div class="footer">` patterns; match either. We also stop at a
487
+ // "related articles" section if present, which sits between the article
488
+ // body and the footer on some templates.
489
+ const cutRes = [
490
+ /<footer\b/i,
491
+ /<div[^>]+class=["'][^"']*\bfooter\b[^"']*["']/i,
492
+ /<div[^>]+class=["'][^"']*\brelated-articles?\b[^"']*["']/i,
493
+ ];
494
+ let cutIdx = -1;
495
+ for (const re of cutRes) {
496
+ const idx = body.search(re);
497
+ if (idx > -1 && (cutIdx < 0 || idx < cutIdx))
498
+ cutIdx = idx;
499
+ }
500
+ if (cutIdx > -1)
501
+ body = body.slice(0, cutIdx);
502
+ return body;
503
+ }
504
+ /**
505
+ * Minimal HTML → markdown converter. Handles the elements that actually show
506
+ * up in the article body (headings, paragraphs, lists, links, emphasis,
507
+ * inline code, line breaks). Anything else is reduced to its text content.
508
+ * Not a general-purpose converter — just enough that the LLM can read it.
509
+ */
510
+ export function htmlToMarkdown(html) {
511
+ let out = html;
512
+ // Inline elements first — block handlers call stripTags() on their inner
513
+ // content to clean leftover tags, which would otherwise drop these
514
+ // conversions before they take effect.
515
+ //
516
+ // Images come before links so a linked image (`<a><img/></a>`) survives the
517
+ // outer link conversion as a markdown linked-image (`[![alt](src)](href)`).
518
+ // No transformations are applied to the URL — Webflow CDN URLs are returned
519
+ // as-is so MCP hosts that can render images inline (Claude, Claude Desktop,
520
+ // and similar) display them, while text-only clients still see the alt.
521
+ out = out.replace(/<img\b([^>]*)\/?>/gi, (_m, attrs) => {
522
+ const srcMatch = attrs.match(/\bsrc=["']([^"']+)["']/i);
523
+ if (!srcMatch)
524
+ return "";
525
+ const altMatch = attrs.match(/\balt=["']([^"']*)["']/i);
526
+ const src = srcMatch[1];
527
+ const alt = (altMatch?.[1] ?? "").trim();
528
+ return `![${alt}](${src})`;
529
+ });
530
+ out = out.replace(/<a[^>]+href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_m, href, inner) => {
531
+ const text = stripTags(inner).trim();
532
+ return text ? `[${text}](${href})` : href;
533
+ });
534
+ out = out.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, (_m, _t, inner) => `**${stripTags(inner)}**`);
535
+ out = out.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, (_m, _t, inner) => `*${stripTags(inner)}*`);
536
+ out = out.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_m, inner) => `\`${stripTags(inner)}\``);
537
+ out = out.replace(/<br\s*\/?>/gi, "\n");
538
+ // Block elements next — by now the inline replacements have been made, so
539
+ // stripTags() inside these handlers only removes leftover tag noise rather
540
+ // than discarding markdown markers.
541
+ out = out.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, level, inner) => {
542
+ return `\n\n${"#".repeat(Number(level))} ${stripTags(inner).trim()}\n\n`;
543
+ });
544
+ out = out.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_m, inner) => {
545
+ return `\n\n${stripTags(inner).trim()}\n\n`;
546
+ });
547
+ out = out.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_m, inner) => `\n- ${stripTags(inner).trim()}`);
548
+ out = out.replace(/<\/?(ul|ol)[^>]*>/gi, "\n");
549
+ out = out.replace(/<hr\s*\/?>/gi, "\n\n---\n\n");
550
+ // Strip remaining tags but keep their inner text.
551
+ out = stripTags(out);
552
+ // Normalise whitespace: collapse 3+ newlines, trim trailing spaces on each
553
+ // line, and tidy the head/tail.
554
+ out = decodeEntities(out)
555
+ .replace(/[ \t]+\n/g, "\n")
556
+ .replace(/\n{3,}/g, "\n\n")
557
+ .trim();
558
+ return out;
559
+ }
560
+ function stripTags(input) {
561
+ return input.replace(/<[^>]+>/g, "");
562
+ }
563
+ function decodeEntities(input) {
564
+ return input
565
+ .replace(/&nbsp;/g, " ")
566
+ .replace(/&amp;/g, "&")
567
+ .replace(/&lt;/g, "<")
568
+ .replace(/&gt;/g, ">")
569
+ .replace(/&quot;/g, '"')
570
+ .replace(/&#39;/g, "'")
571
+ .replace(/&apos;/g, "'")
572
+ .replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(Number(code)));
573
+ }
574
+ //# sourceMappingURL=articles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"articles.js","sourceRoot":"","sources":["../../src/help/articles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Cross-links between the curated internal `nestr_help` topics and the public
3
+ * help-article corpus (nestr.io/help/articles/<slug>).
4
+ *
5
+ * Two directions, two small hand-curated tables:
6
+ * - TOPIC_TO_ARTICLES: an internal topic points to deeper end-user articles
7
+ * (an internal topic may suggest several).
8
+ * - ARTICLE_TO_TOPIC: an article points back to the single most relevant
9
+ * internal topic (agent-flavoured tool-call guidance).
10
+ *
11
+ * Slugs are validated by hand against https://nestr.io/sitemap.xml; topic keys
12
+ * must exist in HELP_TOPICS (asserted in tests). When a topic and an article
13
+ * cover the same ground, add the pair to BOTH tables.
14
+ */
15
+ export declare const TOPIC_TO_ARTICLES: Record<string, string[]>;
16
+ export declare const ARTICLE_TO_TOPIC: Record<string, string>;
17
+ /** Public help articles that go deeper on a curated internal topic. */
18
+ export declare function relatedArticlesForTopic(topicKey: string): string[];
19
+ /** The internal topic (if any) that gives agent-flavoured guidance for an article. */
20
+ export declare function relatedTopicForArticle(slug: string): string | undefined;
21
+ //# sourceMappingURL=cross-links.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-links.d.ts","sourceRoot":"","sources":["../../src/help/cross-links.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAoBtD,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAgBnD,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAElE;AAED,sFAAsF;AACtF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEvE"}