@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.
- package/build/api/client.d.ts +39 -3
- package/build/api/client.d.ts.map +1 -1
- package/build/api/client.js +57 -5
- package/build/api/client.js.map +1 -1
- package/build/help/articles.d.ts +146 -0
- package/build/help/articles.d.ts.map +1 -0
- package/build/help/articles.js +574 -0
- package/build/help/articles.js.map +1 -0
- package/build/help/cross-links.d.ts +21 -0
- package/build/help/cross-links.d.ts.map +1 -0
- package/build/help/cross-links.js +61 -0
- package/build/help/cross-links.js.map +1 -0
- package/build/help/topics.d.ts.map +1 -1
- package/build/help/topics.js +315 -15
- package/build/help/topics.js.map +1 -1
- package/build/http.d.ts +4 -13
- package/build/http.d.ts.map +1 -1
- package/build/http.js +341 -89
- package/build/http.js.map +1 -1
- package/build/oauth/client-info.d.ts +58 -0
- package/build/oauth/client-info.d.ts.map +1 -0
- package/build/oauth/client-info.js +68 -0
- package/build/oauth/client-info.js.map +1 -0
- package/build/oauth/config.d.ts +19 -0
- package/build/oauth/config.d.ts.map +1 -1
- package/build/oauth/config.js +12 -0
- package/build/oauth/config.js.map +1 -1
- package/build/server.d.ts +7 -0
- package/build/server.d.ts.map +1 -1
- package/build/server.js +23 -4
- package/build/server.js.map +1 -1
- package/build/skills/tension-processing.d.ts.map +1 -1
- package/build/skills/tension-processing.js +11 -1
- package/build/skills/tension-processing.js.map +1 -1
- package/build/tools/index.d.ts +612 -68
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js +592 -63
- package/build/tools/index.js.map +1 -1
- package/build/tools/validation.d.ts +42 -0
- package/build/tools/validation.d.ts.map +1 -0
- package/build/tools/validation.js +97 -0
- package/build/tools/validation.js.map +1 -0
- package/package.json +2 -1
- package/web/index.html +25 -0
- 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 `` and the inner image of a linked image `[](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 (`[](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 ``;
|
|
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(/ /g, " ")
|
|
566
|
+
.replace(/&/g, "&")
|
|
567
|
+
.replace(/</g, "<")
|
|
568
|
+
.replace(/>/g, ">")
|
|
569
|
+
.replace(/"/g, '"')
|
|
570
|
+
.replace(/'/g, "'")
|
|
571
|
+
.replace(/'/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"}
|