@cyanheads/stackexchange-mcp-server 0.1.1

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 (53) hide show
  1. package/AGENTS.md +360 -0
  2. package/CLAUDE.md +360 -0
  3. package/Dockerfile +99 -0
  4. package/LICENSE +201 -0
  5. package/README.md +307 -0
  6. package/changelog/0.1.x/0.1.1.md +27 -0
  7. package/changelog/template.md +127 -0
  8. package/dist/config/server-config.d.ts +11 -0
  9. package/dist/config/server-config.d.ts.map +1 -0
  10. package/dist/config/server-config.js +21 -0
  11. package/dist/config/server-config.js.map +1 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +24 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/mcp-server/tools/definitions/index.d.ts +176 -0
  17. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
  18. package/dist/mcp-server/tools/definitions/index.js +22 -0
  19. package/dist/mcp-server/tools/definitions/index.js.map +1 -0
  20. package/dist/mcp-server/tools/definitions/stackexchange-get-tag-faq.tool.d.ts +37 -0
  21. package/dist/mcp-server/tools/definitions/stackexchange-get-tag-faq.tool.d.ts.map +1 -0
  22. package/dist/mcp-server/tools/definitions/stackexchange-get-tag-faq.tool.js +118 -0
  23. package/dist/mcp-server/tools/definitions/stackexchange-get-tag-faq.tool.js.map +1 -0
  24. package/dist/mcp-server/tools/definitions/stackexchange-get-thread.tool.d.ts +54 -0
  25. package/dist/mcp-server/tools/definitions/stackexchange-get-thread.tool.d.ts.map +1 -0
  26. package/dist/mcp-server/tools/definitions/stackexchange-get-thread.tool.js +205 -0
  27. package/dist/mcp-server/tools/definitions/stackexchange-get-thread.tool.js.map +1 -0
  28. package/dist/mcp-server/tools/definitions/stackexchange-get-user.tool.d.ts +48 -0
  29. package/dist/mcp-server/tools/definitions/stackexchange-get-user.tool.d.ts.map +1 -0
  30. package/dist/mcp-server/tools/definitions/stackexchange-get-user.tool.js +151 -0
  31. package/dist/mcp-server/tools/definitions/stackexchange-get-user.tool.js.map +1 -0
  32. package/dist/mcp-server/tools/definitions/stackexchange-list-sites.tool.d.ts +20 -0
  33. package/dist/mcp-server/tools/definitions/stackexchange-list-sites.tool.d.ts.map +1 -0
  34. package/dist/mcp-server/tools/definitions/stackexchange-list-sites.tool.js +94 -0
  35. package/dist/mcp-server/tools/definitions/stackexchange-list-sites.tool.js.map +1 -0
  36. package/dist/mcp-server/tools/definitions/stackexchange-search-questions.tool.d.ts +45 -0
  37. package/dist/mcp-server/tools/definitions/stackexchange-search-questions.tool.d.ts.map +1 -0
  38. package/dist/mcp-server/tools/definitions/stackexchange-search-questions.tool.js +145 -0
  39. package/dist/mcp-server/tools/definitions/stackexchange-search-questions.tool.js.map +1 -0
  40. package/dist/services/stackexchange/html-normalizer.d.ts +18 -0
  41. package/dist/services/stackexchange/html-normalizer.d.ts.map +1 -0
  42. package/dist/services/stackexchange/html-normalizer.js +143 -0
  43. package/dist/services/stackexchange/html-normalizer.js.map +1 -0
  44. package/dist/services/stackexchange/stackexchange-service.d.ts +143 -0
  45. package/dist/services/stackexchange/stackexchange-service.d.ts.map +1 -0
  46. package/dist/services/stackexchange/stackexchange-service.js +336 -0
  47. package/dist/services/stackexchange/stackexchange-service.js.map +1 -0
  48. package/dist/services/stackexchange/types.d.ts +104 -0
  49. package/dist/services/stackexchange/types.d.ts.map +1 -0
  50. package/dist/services/stackexchange/types.js +7 -0
  51. package/dist/services/stackexchange/types.js.map +1 -0
  52. package/package.json +101 -0
  53. package/server.json +111 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @fileoverview Lightweight HTML→markdown normalizer for Stack Exchange post bodies.
3
+ * Handles SE's known tag set: p, pre/code, strong, em, a, ul, ol, li, h1-h6,
4
+ * blockquote, inline code. No external dependency required.
5
+ * @module services/stackexchange/html-normalizer
6
+ */
7
+ /**
8
+ * Convert a Stack Exchange HTML post body to clean markdown.
9
+ * Operates on SE's predictable, limited HTML tag set.
10
+ */
11
+ export function normalizeHtml(html) {
12
+ if (!html)
13
+ return '';
14
+ let md = html;
15
+ // NOTE: do NOT call decodeEntities() here. HTML entities must remain encoded
16
+ // until after all HTML tags are processed. Pre-decoding </> converts
17
+ // them to < / > which (a) breaks stripTags() on attribute values containing >
18
+ // (e.g. alt="x >= 128" gets split mid-tag) and (b) makes entity-encoded
19
+ // angle brackets in code blocks look like real HTML tags that then get stripped.
20
+ // Entities are decoded inside code block handlers explicitly, and by the final
21
+ // decodeEntities() call at the end of this function.
22
+ // Fenced code blocks: <pre><code>...</code></pre> → ```\n...\n```
23
+ // SE wraps code blocks in both tags; capture the language hint from class if present.
24
+ md = md.replace(/<pre[^>]*>\s*<code[^>]*class="[^"]*language-([^"\s]+)[^"]*"[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/gi, (_, lang, code) => {
25
+ const cleaned = stripTags(code)
26
+ .replace(/&amp;/g, '&')
27
+ .replace(/&lt;/g, '<')
28
+ .replace(/&gt;/g, '>')
29
+ .replace(/&quot;/g, '"')
30
+ .replace(/&#39;/g, "'");
31
+ return `\`\`\`${lang}\n${cleaned.trim()}\n\`\`\``;
32
+ });
33
+ // Fenced code blocks without language class
34
+ md = md.replace(/<pre[^>]*>\s*<code[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/gi, (_, code) => {
35
+ const cleaned = stripTags(code)
36
+ .replace(/&amp;/g, '&')
37
+ .replace(/&lt;/g, '<')
38
+ .replace(/&gt;/g, '>')
39
+ .replace(/&quot;/g, '"')
40
+ .replace(/&#39;/g, "'");
41
+ return `\`\`\`\n${cleaned.trim()}\n\`\`\``;
42
+ });
43
+ // Headings h1–h6
44
+ md = md.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, content) => {
45
+ const hashes = '#'.repeat(parseInt(level, 10));
46
+ return `\n${hashes} ${stripTags(content).trim()}\n`;
47
+ });
48
+ // Blockquotes
49
+ md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
50
+ const inner = normalizeHtml(content).trim();
51
+ return inner
52
+ .split('\n')
53
+ .map((line) => `> ${line}`)
54
+ .join('\n');
55
+ });
56
+ // Bold
57
+ md = md.replace(/<strong[^>]*>([\s\S]*?)<\/strong>/gi, (_, content) => `**${stripTags(content).trim()}**`);
58
+ md = md.replace(/<b[^>]*>([\s\S]*?)<\/b>/gi, (_, content) => `**${stripTags(content).trim()}**`);
59
+ // Italic
60
+ md = md.replace(/<em[^>]*>([\s\S]*?)<\/em>/gi, (_, content) => `_${stripTags(content).trim()}_`);
61
+ md = md.replace(/<i[^>]*>([\s\S]*?)<\/i>/gi, (_, content) => `_${stripTags(content).trim()}_`);
62
+ // Inline code (not already inside pre blocks)
63
+ md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_, content) => {
64
+ const raw = content
65
+ .replace(/&amp;/g, '&')
66
+ .replace(/&lt;/g, '<')
67
+ .replace(/&gt;/g, '>')
68
+ .replace(/&quot;/g, '"')
69
+ .replace(/&#39;/g, "'");
70
+ return `\`${raw}\``;
71
+ });
72
+ // Links
73
+ md = md.replace(/<a[^>]+href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, (_, href, text) => {
74
+ const linkText = stripTags(text).trim() || href;
75
+ return `[${linkText}](${href})`;
76
+ });
77
+ md = md.replace(/<a[^>]+href='([^']*)'[^>]*>([\s\S]*?)<\/a>/gi, (_, href, text) => {
78
+ const linkText = stripTags(text).trim() || href;
79
+ return `[${linkText}](${href})`;
80
+ });
81
+ // Ordered lists
82
+ md = md.replace(/<ol[^>]*>([\s\S]*?)<\/ol>/gi, (_, content) => {
83
+ let counter = 0;
84
+ const items = content.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (__, item) => {
85
+ counter++;
86
+ return `${counter}. ${normalizeHtml(item).trim()}\n`;
87
+ });
88
+ return `\n${items}\n`;
89
+ });
90
+ // Unordered lists
91
+ md = md.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, content) => {
92
+ const items = content.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (__, item) => `- ${normalizeHtml(item).trim()}\n`);
93
+ return `\n${items}\n`;
94
+ });
95
+ // Paragraphs → double newline
96
+ md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, content) => `\n${normalizeHtml(content).trim()}\n`);
97
+ // Line breaks
98
+ md = md.replace(/<br\s*\/?>/gi, '\n');
99
+ // Strip any remaining HTML tags (img, hr, sub, sup, table, etc.).
100
+ // Protect already-converted fenced code blocks first — their decoded content
101
+ // (e.g. `#include <algorithm>`) contains angle brackets that would otherwise
102
+ // be misidentified as HTML tags and stripped.
103
+ const codeBlocks = [];
104
+ const PLACEHOLDER_PREFIX = 'MDCODEBLOCK';
105
+ const PLACEHOLDER_SUFFIX = 'ENDMDCODEBLOCK';
106
+ md = md.replace(/```[\s\S]*?```/g, (block) => {
107
+ codeBlocks.push(block);
108
+ return `${PLACEHOLDER_PREFIX}${codeBlocks.length - 1}${PLACEHOLDER_SUFFIX}`;
109
+ });
110
+ md = stripTags(md);
111
+ md = md.replace(new RegExp(`${PLACEHOLDER_PREFIX}(\\d+)${PLACEHOLDER_SUFFIX}`, 'g'), (_, idx) => codeBlocks[parseInt(idx, 10)] ?? '');
112
+ // Decode entities in the non-code portions (remaining &amp;, &lt;, etc.)
113
+ md = decodeEntities(md);
114
+ // Normalize excessive blank lines (max 2 consecutive newlines)
115
+ md = md.replace(/\n{3,}/g, '\n\n');
116
+ return md.trim();
117
+ }
118
+ /** Strip all HTML tags from a string. */
119
+ function stripTags(html) {
120
+ return html.replace(/<[^>]+>/g, '');
121
+ }
122
+ /**
123
+ * Decode basic HTML entities in a plain-text string (not HTML).
124
+ * Use this for SE API fields like site names and audiences that arrive
125
+ * HTML-encoded but contain no markup.
126
+ */
127
+ export function decodeHtmlEntities(text) {
128
+ return decodeEntities(text);
129
+ }
130
+ /** Decode basic HTML entities. */
131
+ function decodeEntities(text) {
132
+ return text
133
+ .replace(/&amp;/g, '&')
134
+ .replace(/&lt;/g, '<')
135
+ .replace(/&gt;/g, '>')
136
+ .replace(/&quot;/g, '"')
137
+ .replace(/&#39;/g, "'")
138
+ .replace(/&apos;/g, "'")
139
+ .replace(/&nbsp;/g, ' ')
140
+ .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)))
141
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
142
+ }
143
+ //# sourceMappingURL=html-normalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-normalizer.js","sourceRoot":"","sources":["../../../src/services/stackexchange/html-normalizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @fileoverview Stack Exchange API v2.3 HTTP client with backoff tracking,
3
+ * quota logging, gzip decompression, and typed domain methods.
4
+ * @module services/stackexchange/stackexchange-service
5
+ */
6
+ import type { Context } from '@cyanheads/mcp-ts-core';
7
+ import type { AppConfig } from '@cyanheads/mcp-ts-core/config';
8
+ import type { StorageService } from '@cyanheads/mcp-ts-core/storage';
9
+ export interface SearchQuestionsOptions {
10
+ acceptedOnly?: boolean;
11
+ /** API key from server config — injected by the service. */
12
+ apiKey?: string;
13
+ minScore?: number;
14
+ pageSize?: number;
15
+ query: string;
16
+ site: string;
17
+ sort?: 'relevance' | 'votes' | 'activity' | 'newest';
18
+ tags?: string[];
19
+ }
20
+ export interface GetThreadOptions {
21
+ apiKey?: string;
22
+ includeComments?: boolean;
23
+ maxAnswers?: number;
24
+ questionId: number;
25
+ site: string;
26
+ }
27
+ export interface GetUserOptions {
28
+ apiKey?: string;
29
+ site: string;
30
+ userId: number;
31
+ }
32
+ export interface GetTagFaqOptions {
33
+ apiKey?: string;
34
+ pageSize?: number;
35
+ site: string;
36
+ tag: string;
37
+ }
38
+ export interface GetSitesOptions {
39
+ apiKey?: string;
40
+ }
41
+ /** Normalized question for tool output. */
42
+ export interface NormalizedQuestion {
43
+ answerCount: number;
44
+ excerpt?: string;
45
+ isAnswered: boolean;
46
+ link: string;
47
+ questionId: number;
48
+ score: number;
49
+ tags: string[];
50
+ title: string;
51
+ }
52
+ /** Normalized answer for thread output. */
53
+ export interface NormalizedAnswer {
54
+ answerId: number;
55
+ authorLink?: string;
56
+ authorName?: string;
57
+ authorReputation?: number;
58
+ bodyMarkdown: string;
59
+ isAccepted: boolean;
60
+ score: number;
61
+ }
62
+ /** Normalized thread for tool output. */
63
+ export interface NormalizedThread {
64
+ acceptedAnswerId?: number;
65
+ answers: NormalizedAnswer[];
66
+ authorLink?: string;
67
+ authorName?: string;
68
+ bodyMarkdown: string;
69
+ link: string;
70
+ questionId: number;
71
+ score: number;
72
+ tags: string[];
73
+ title: string;
74
+ }
75
+ /** Normalized user profile for tool output. */
76
+ export interface NormalizedUser {
77
+ answerCount?: number;
78
+ badgeCounts?: {
79
+ gold?: number;
80
+ silver?: number;
81
+ bronze?: number;
82
+ };
83
+ displayName: string;
84
+ link: string;
85
+ location?: string;
86
+ questionCount?: number;
87
+ reputation: number;
88
+ topTags: {
89
+ tagName: string;
90
+ answerCount?: number;
91
+ answerScore?: number;
92
+ }[];
93
+ userId: number;
94
+ websiteUrl?: string;
95
+ }
96
+ /** Normalized site for tool output. */
97
+ export interface NormalizedSite {
98
+ apiSiteParameter: string;
99
+ audience?: string;
100
+ name: string;
101
+ siteUrl: string;
102
+ }
103
+ export declare class StackExchangeService {
104
+ private readonly apiKey;
105
+ constructor(_config: AppConfig, _storage: StorageService, apiKey?: string);
106
+ /** Build a URL with common params (key, gzip). */
107
+ private buildUrl;
108
+ /** Fetch, decompress (auto), parse, handle errors. */
109
+ private fetchSe;
110
+ /** Search questions (no bodies). */
111
+ searchQuestions(opts: SearchQuestionsOptions, ctx: Context): Promise<{
112
+ questions: NormalizedQuestion[];
113
+ quotaRemaining: number;
114
+ quotaMax: number;
115
+ }>;
116
+ /** Fetch a complete Q&A thread with HTML→markdown normalization. */
117
+ getThread(opts: GetThreadOptions, ctx: Context): Promise<{
118
+ thread: NormalizedThread;
119
+ quotaRemaining: number;
120
+ quotaMax: number;
121
+ }>;
122
+ /** Fetch the tag FAQ (highest-voted answered questions for a tag). */
123
+ getTagFaq(opts: GetTagFaqOptions, ctx: Context): Promise<{
124
+ questions: NormalizedQuestion[];
125
+ quotaRemaining: number;
126
+ quotaMax: number;
127
+ }>;
128
+ /** Fetch a user profile + top tags. */
129
+ getUser(opts: GetUserOptions, ctx: Context): Promise<{
130
+ user: NormalizedUser;
131
+ quotaRemaining: number;
132
+ quotaMax: number;
133
+ }>;
134
+ /** Fetch all sites in the SE network (paginated, bounded set). */
135
+ getSites(ctx: Context): Promise<{
136
+ sites: NormalizedSite[];
137
+ quotaRemaining: number;
138
+ quotaMax: number;
139
+ }>;
140
+ }
141
+ export declare function initStackExchangeService(config: AppConfig, storage: StorageService, apiKey?: string): void;
142
+ export declare function getStackExchangeService(): StackExchangeService;
143
+ //# sourceMappingURL=stackexchange-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stackexchange-service.d.ts","sourceRoot":"","sources":["../../../src/services/stackexchange/stackexchange-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,336 @@
1
+ /**
2
+ * @fileoverview Stack Exchange API v2.3 HTTP client with backoff tracking,
3
+ * quota logging, gzip decompression, and typed domain methods.
4
+ * @module services/stackexchange/stackexchange-service
5
+ */
6
+ import { invalidParams, notFound, rateLimited, serviceUnavailable, } from '@cyanheads/mcp-ts-core/errors';
7
+ import { withRetry } from '@cyanheads/mcp-ts-core/utils';
8
+ import { decodeHtmlEntities, normalizeHtml } from './html-normalizer.js';
9
+ const BASE_URL = 'https://api.stackexchange.com/2.3';
10
+ /** Module-level backoff tracking — per-process, acceptable for server-side use. */
11
+ let backoffUntil = 0;
12
+ /** Honour the SE `backoff` field before the next request. */
13
+ async function waitForBackoff() {
14
+ const now = Date.now();
15
+ if (now < backoffUntil) {
16
+ await new Promise((resolve) => setTimeout(resolve, backoffUntil - now));
17
+ }
18
+ }
19
+ /** Update the backoff window from a response envelope. */
20
+ function updateBackoff(wrapper) {
21
+ if (wrapper.backoff && wrapper.backoff > 0) {
22
+ backoffUntil = Date.now() + wrapper.backoff * 1000;
23
+ }
24
+ }
25
+ export class StackExchangeService {
26
+ apiKey;
27
+ constructor(_config, _storage, apiKey) {
28
+ this.apiKey = apiKey;
29
+ }
30
+ /** Build a URL with common params (key, gzip). */
31
+ buildUrl(path, params) {
32
+ const url = new URL(`${BASE_URL}${path}`);
33
+ // SE API always returns gzip — accept it explicitly
34
+ // (fetch auto-decompresses with Accept-Encoding: gzip)
35
+ for (const [k, v] of Object.entries(params)) {
36
+ if (v !== undefined && v !== '' && v !== null) {
37
+ url.searchParams.set(k, String(v));
38
+ }
39
+ }
40
+ if (this.apiKey) {
41
+ url.searchParams.set('key', this.apiKey);
42
+ }
43
+ return url.toString();
44
+ }
45
+ /** Fetch, decompress (auto), parse, handle errors. */
46
+ async fetchSe(url, ctx) {
47
+ await waitForBackoff();
48
+ const response = await fetch(url, {
49
+ headers: {
50
+ 'Accept-Encoding': 'gzip',
51
+ Accept: 'application/json',
52
+ },
53
+ signal: ctx.signal,
54
+ });
55
+ const text = await response.text();
56
+ // SE returns HTTP 400 with JSON error envelope for bad params
57
+ if (!response.ok) {
58
+ let errObj;
59
+ try {
60
+ errObj = JSON.parse(text);
61
+ }
62
+ catch {
63
+ // ignore parse failure
64
+ }
65
+ if (errObj?.error_name === 'bad_parameter') {
66
+ throw invalidParams(`Stack Exchange API error: ${errObj.error_message}`, {
67
+ reason: 'invalid_site',
68
+ error_name: errObj.error_name,
69
+ error_id: errObj.error_id,
70
+ });
71
+ }
72
+ throw serviceUnavailable(`Stack Exchange API returned HTTP ${response.status}`, {
73
+ status: response.status,
74
+ });
75
+ }
76
+ let wrapper;
77
+ try {
78
+ wrapper = JSON.parse(text);
79
+ }
80
+ catch (err) {
81
+ throw serviceUnavailable('Failed to parse Stack Exchange response', {}, { cause: err });
82
+ }
83
+ updateBackoff(wrapper);
84
+ ctx.log.debug('SE quota', {
85
+ quota_remaining: wrapper.quota_remaining,
86
+ quota_max: wrapper.quota_max,
87
+ });
88
+ return wrapper;
89
+ }
90
+ /** Search questions (no bodies). */
91
+ searchQuestions(opts, ctx) {
92
+ return withRetry(async () => {
93
+ const params = {
94
+ site: opts.site,
95
+ q: opts.query,
96
+ sort: opts.sort ?? 'relevance',
97
+ pagesize: opts.pageSize ?? 10,
98
+ intitle: undefined,
99
+ };
100
+ if (opts.tags && opts.tags.length > 0) {
101
+ params.tagged = opts.tags.join(';');
102
+ }
103
+ if (opts.acceptedOnly) {
104
+ params.accepted = 'True';
105
+ }
106
+ if (opts.minScore !== undefined) {
107
+ params.min = opts.minScore;
108
+ }
109
+ const url = this.buildUrl('/search/advanced', params);
110
+ const wrapper = await this.fetchSe(url, ctx);
111
+ if (wrapper.quota_remaining === 0) {
112
+ throw rateLimited('Stack Exchange API quota exhausted.', {
113
+ reason: 'quota_exceeded',
114
+ quota_remaining: 0,
115
+ quota_max: wrapper.quota_max,
116
+ });
117
+ }
118
+ const questions = wrapper.items.map((q) => ({
119
+ questionId: q.question_id,
120
+ title: q.title,
121
+ link: q.link,
122
+ score: q.score,
123
+ answerCount: q.answer_count,
124
+ isAnswered: q.is_answered,
125
+ tags: q.tags,
126
+ ...(q.excerpt ? { excerpt: q.excerpt } : {}),
127
+ }));
128
+ return { questions, quotaRemaining: wrapper.quota_remaining, quotaMax: wrapper.quota_max };
129
+ }, {
130
+ operation: 'searchQuestions',
131
+ context: ctx,
132
+ baseDelayMs: 1000,
133
+ signal: ctx.signal,
134
+ });
135
+ }
136
+ /** Fetch a complete Q&A thread with HTML→markdown normalization. */
137
+ getThread(opts, ctx) {
138
+ return withRetry(async () => {
139
+ const questionUrl = this.buildUrl(`/questions/${opts.questionId}`, {
140
+ site: opts.site,
141
+ filter: 'withbody',
142
+ });
143
+ const answersUrl = this.buildUrl(`/questions/${opts.questionId}/answers`, {
144
+ site: opts.site,
145
+ filter: 'withbody',
146
+ sort: 'votes',
147
+ pagesize: opts.maxAnswers ?? 10,
148
+ });
149
+ const [questionWrapper, answersWrapper] = await Promise.all([
150
+ this.fetchSe(questionUrl, ctx),
151
+ this.fetchSe(answersUrl, ctx),
152
+ ]);
153
+ if (questionWrapper.quota_remaining === 0) {
154
+ throw rateLimited('Stack Exchange API quota exhausted.', {
155
+ reason: 'quota_exceeded',
156
+ quota_remaining: 0,
157
+ quota_max: questionWrapper.quota_max,
158
+ });
159
+ }
160
+ if (questionWrapper.items.length === 0) {
161
+ throw notFound(`Question ID ${opts.questionId} not found on site "${opts.site}".`, {
162
+ reason: 'question_not_found',
163
+ questionId: opts.questionId,
164
+ site: opts.site,
165
+ });
166
+ }
167
+ const q = questionWrapper.items[0];
168
+ // Sort answers: accepted first, then by score descending
169
+ const answers = answersWrapper.items.slice().sort((a, b) => {
170
+ const aAccepted = a.answer_id === q.accepted_answer_id ? 1 : 0;
171
+ const bAccepted = b.answer_id === q.accepted_answer_id ? 1 : 0;
172
+ if (aAccepted !== bAccepted)
173
+ return bAccepted - aAccepted;
174
+ return b.score - a.score;
175
+ });
176
+ const normalizedAnswers = answers.map((a) => ({
177
+ answerId: a.answer_id,
178
+ score: a.score,
179
+ isAccepted: a.is_accepted,
180
+ bodyMarkdown: normalizeHtml(a.body ?? ''),
181
+ ...(a.owner?.display_name ? { authorName: a.owner.display_name } : {}),
182
+ ...(a.owner?.link ? { authorLink: a.owner.link } : {}),
183
+ ...(a.owner?.reputation !== undefined ? { authorReputation: a.owner.reputation } : {}),
184
+ }));
185
+ const thread = {
186
+ questionId: q.question_id,
187
+ title: q.title,
188
+ link: q.link,
189
+ score: q.score,
190
+ tags: q.tags,
191
+ bodyMarkdown: normalizeHtml(q.body ?? ''),
192
+ ...(q.owner?.display_name ? { authorName: q.owner.display_name } : {}),
193
+ ...(q.owner?.link ? { authorLink: q.owner.link } : {}),
194
+ answers: normalizedAnswers,
195
+ ...(q.accepted_answer_id !== undefined ? { acceptedAnswerId: q.accepted_answer_id } : {}),
196
+ };
197
+ return {
198
+ thread,
199
+ quotaRemaining: questionWrapper.quota_remaining,
200
+ quotaMax: questionWrapper.quota_max,
201
+ };
202
+ }, {
203
+ operation: 'getThread',
204
+ context: ctx,
205
+ baseDelayMs: 1000,
206
+ signal: ctx.signal,
207
+ });
208
+ }
209
+ /** Fetch the tag FAQ (highest-voted answered questions for a tag). */
210
+ getTagFaq(opts, ctx) {
211
+ return withRetry(async () => {
212
+ const url = this.buildUrl(`/tags/${encodeURIComponent(opts.tag)}/faq`, {
213
+ site: opts.site,
214
+ pagesize: opts.pageSize ?? 10,
215
+ });
216
+ const wrapper = await this.fetchSe(url, ctx);
217
+ if (wrapper.quota_remaining === 0) {
218
+ throw rateLimited('Stack Exchange API quota exhausted.', {
219
+ reason: 'quota_exceeded',
220
+ quota_remaining: 0,
221
+ quota_max: wrapper.quota_max,
222
+ });
223
+ }
224
+ const questions = wrapper.items.map((q) => ({
225
+ questionId: q.question_id,
226
+ title: q.title,
227
+ link: q.link,
228
+ score: q.score,
229
+ answerCount: q.answer_count,
230
+ isAnswered: q.is_answered,
231
+ tags: q.tags,
232
+ }));
233
+ return { questions, quotaRemaining: wrapper.quota_remaining, quotaMax: wrapper.quota_max };
234
+ }, {
235
+ operation: 'getTagFaq',
236
+ context: ctx,
237
+ baseDelayMs: 1000,
238
+ signal: ctx.signal,
239
+ });
240
+ }
241
+ /** Fetch a user profile + top tags. */
242
+ getUser(opts, ctx) {
243
+ return withRetry(async () => {
244
+ const profileUrl = this.buildUrl(`/users/${opts.userId}`, { site: opts.site });
245
+ const topTagsUrl = this.buildUrl(`/users/${opts.userId}/top-tags`, {
246
+ site: opts.site,
247
+ pagesize: 10,
248
+ });
249
+ const [profileWrapper, topTagsWrapper] = await Promise.all([
250
+ this.fetchSe(profileUrl, ctx),
251
+ this.fetchSe(topTagsUrl, ctx),
252
+ ]);
253
+ if (profileWrapper.quota_remaining === 0) {
254
+ throw rateLimited('Stack Exchange API quota exhausted.', {
255
+ reason: 'quota_exceeded',
256
+ quota_remaining: 0,
257
+ quota_max: profileWrapper.quota_max,
258
+ });
259
+ }
260
+ if (profileWrapper.items.length === 0) {
261
+ throw notFound(`User ID ${opts.userId} not found on site "${opts.site}".`, {
262
+ reason: 'user_not_found',
263
+ userId: opts.userId,
264
+ site: opts.site,
265
+ });
266
+ }
267
+ const u = profileWrapper.items[0];
268
+ const topTags = topTagsWrapper.items.map((t) => ({
269
+ tagName: t.tag_name,
270
+ ...(t.answer_count !== undefined ? { answerCount: t.answer_count } : {}),
271
+ ...(t.answer_score !== undefined ? { answerScore: t.answer_score } : {}),
272
+ }));
273
+ const user = {
274
+ userId: u.user_id,
275
+ displayName: u.display_name,
276
+ link: u.link,
277
+ reputation: u.reputation,
278
+ ...(u.badge_counts ? { badgeCounts: u.badge_counts } : {}),
279
+ ...(u.location ? { location: u.location } : {}),
280
+ ...(u.website_url ? { websiteUrl: u.website_url } : {}),
281
+ topTags,
282
+ ...(u.answer_count !== undefined ? { answerCount: u.answer_count } : {}),
283
+ ...(u.question_count !== undefined ? { questionCount: u.question_count } : {}),
284
+ };
285
+ return {
286
+ user,
287
+ quotaRemaining: profileWrapper.quota_remaining,
288
+ quotaMax: profileWrapper.quota_max,
289
+ };
290
+ }, {
291
+ operation: 'getUser',
292
+ context: ctx,
293
+ baseDelayMs: 1000,
294
+ signal: ctx.signal,
295
+ });
296
+ }
297
+ /** Fetch all sites in the SE network (paginated, bounded set). */
298
+ getSites(ctx) {
299
+ return withRetry(async () => {
300
+ const url = this.buildUrl('/sites', { pagesize: 100 });
301
+ const wrapper = await this.fetchSe(url, ctx);
302
+ // SE API returns site names and audiences HTML-encoded (e.g. "Unix &amp; Linux")
303
+ const normalizeSite = (s) => ({
304
+ name: decodeHtmlEntities(s.name),
305
+ apiSiteParameter: s.api_site_parameter,
306
+ siteUrl: s.site_url,
307
+ ...(s.audience ? { audience: decodeHtmlEntities(s.audience) } : {}),
308
+ });
309
+ const sites = wrapper.items.map(normalizeSite);
310
+ // If there are more pages, fetch them (SE has ~190 sites, fits in 2 pages at pagesize=100)
311
+ if (wrapper.has_more) {
312
+ const page2Url = this.buildUrl('/sites', { pagesize: 100, page: 2 });
313
+ const wrapper2 = await this.fetchSe(page2Url, ctx);
314
+ sites.push(...wrapper2.items.map(normalizeSite));
315
+ }
316
+ return { sites, quotaRemaining: wrapper.quota_remaining, quotaMax: wrapper.quota_max };
317
+ }, {
318
+ operation: 'getSites',
319
+ context: ctx,
320
+ baseDelayMs: 1000,
321
+ signal: ctx.signal,
322
+ });
323
+ }
324
+ }
325
+ // --- Init/accessor pattern ---
326
+ let _service;
327
+ export function initStackExchangeService(config, storage, apiKey) {
328
+ _service = new StackExchangeService(config, storage, apiKey);
329
+ }
330
+ export function getStackExchangeService() {
331
+ if (!_service) {
332
+ throw new Error('StackExchangeService not initialized — call initStackExchangeService() in setup()');
333
+ }
334
+ return _service;
335
+ }
336
+ //# sourceMappingURL=stackexchange-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stackexchange-service.js","sourceRoot":"","sources":["../../../src/services/stackexchange/stackexchange-service.ts"],"names":[],"mappings":"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"}