@get-technology-inc/jamf-docs-mcp-server 3.0.6 → 3.0.8

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 (190) hide show
  1. package/README.md +37 -2
  2. package/dist/core/constants/doc-types.d.ts +48 -0
  3. package/dist/core/constants/doc-types.d.ts.map +1 -0
  4. package/dist/core/constants/doc-types.js +58 -0
  5. package/dist/core/constants/doc-types.js.map +1 -0
  6. package/dist/core/constants/index.d.ts +16 -0
  7. package/dist/core/constants/index.d.ts.map +1 -0
  8. package/dist/core/constants/index.js +12 -0
  9. package/dist/core/constants/index.js.map +1 -0
  10. package/dist/core/constants/limits.d.ts +39 -0
  11. package/dist/core/constants/limits.d.ts.map +1 -0
  12. package/dist/core/constants/limits.js +51 -0
  13. package/dist/core/constants/limits.js.map +1 -0
  14. package/dist/core/constants/locales.d.ts +34 -0
  15. package/dist/core/constants/locales.d.ts.map +1 -0
  16. package/dist/core/constants/locales.js +20 -0
  17. package/dist/core/constants/locales.js.map +1 -0
  18. package/dist/core/constants/products.d.ts +127 -0
  19. package/dist/core/constants/products.d.ts.map +1 -0
  20. package/dist/core/constants/products.js +135 -0
  21. package/dist/core/constants/products.js.map +1 -0
  22. package/dist/core/constants/topics.d.ts +168 -0
  23. package/dist/core/constants/topics.d.ts.map +1 -0
  24. package/dist/core/constants/topics.js +186 -0
  25. package/dist/core/constants/topics.js.map +1 -0
  26. package/dist/core/constants.d.ts +7 -404
  27. package/dist/core/constants.d.ts.map +1 -1
  28. package/dist/core/constants.js +16 -427
  29. package/dist/core/constants.js.map +1 -1
  30. package/dist/core/http-client.d.ts +14 -2
  31. package/dist/core/http-client.d.ts.map +1 -1
  32. package/dist/core/http-client.js +148 -17
  33. package/dist/core/http-client.js.map +1 -1
  34. package/dist/core/index.d.ts +12 -12
  35. package/dist/core/index.d.ts.map +1 -1
  36. package/dist/core/index.js +12 -13
  37. package/dist/core/index.js.map +1 -1
  38. package/dist/core/resources/index.js +1 -1
  39. package/dist/core/resources/index.js.map +1 -1
  40. package/dist/core/schemas/index.d.ts +3 -1
  41. package/dist/core/schemas/index.d.ts.map +1 -1
  42. package/dist/core/schemas/index.js +13 -3
  43. package/dist/core/schemas/index.js.map +1 -1
  44. package/dist/core/schemas/output.d.ts +7 -0
  45. package/dist/core/schemas/output.d.ts.map +1 -1
  46. package/dist/core/schemas/output.js +7 -0
  47. package/dist/core/schemas/output.js.map +1 -1
  48. package/dist/core/services/article-service.d.ts +24 -0
  49. package/dist/core/services/article-service.d.ts.map +1 -0
  50. package/dist/core/services/article-service.js +140 -0
  51. package/dist/core/services/article-service.js.map +1 -0
  52. package/dist/core/services/content-parser.d.ts +38 -0
  53. package/dist/core/services/content-parser.d.ts.map +1 -0
  54. package/dist/core/services/content-parser.js +148 -0
  55. package/dist/core/services/content-parser.js.map +1 -0
  56. package/dist/core/services/ft-client.d.ts +52 -0
  57. package/dist/core/services/ft-client.d.ts.map +1 -0
  58. package/dist/core/services/ft-client.js +87 -0
  59. package/dist/core/services/ft-client.js.map +1 -0
  60. package/dist/core/services/glossary.d.ts +9 -5
  61. package/dist/core/services/glossary.d.ts.map +1 -1
  62. package/dist/core/services/glossary.js +175 -108
  63. package/dist/core/services/glossary.js.map +1 -1
  64. package/dist/core/services/interfaces/cache.d.ts +6 -6
  65. package/dist/core/services/interfaces/cache.d.ts.map +1 -1
  66. package/dist/core/services/interfaces/index.d.ts +3 -3
  67. package/dist/core/services/interfaces/index.d.ts.map +1 -1
  68. package/dist/core/services/interfaces/logger.d.ts +17 -10
  69. package/dist/core/services/interfaces/logger.d.ts.map +1 -1
  70. package/dist/core/services/interfaces/metadata.d.ts +0 -10
  71. package/dist/core/services/interfaces/metadata.d.ts.map +1 -1
  72. package/dist/core/services/interfaces/providers.d.ts +23 -10
  73. package/dist/core/services/interfaces/providers.d.ts.map +1 -1
  74. package/dist/core/services/logging.d.ts +38 -18
  75. package/dist/core/services/logging.d.ts.map +1 -1
  76. package/dist/core/services/logging.js +78 -29
  77. package/dist/core/services/logging.js.map +1 -1
  78. package/dist/core/services/maps-registry.d.ts +82 -0
  79. package/dist/core/services/maps-registry.d.ts.map +1 -0
  80. package/dist/core/services/maps-registry.js +232 -0
  81. package/dist/core/services/maps-registry.js.map +1 -0
  82. package/dist/core/services/metadata.d.ts +18 -9
  83. package/dist/core/services/metadata.d.ts.map +1 -1
  84. package/dist/core/services/metadata.js +113 -254
  85. package/dist/core/services/metadata.js.map +1 -1
  86. package/dist/core/services/search-service.d.ts +31 -0
  87. package/dist/core/services/search-service.d.ts.map +1 -0
  88. package/dist/core/services/search-service.js +462 -0
  89. package/dist/core/services/search-service.js.map +1 -0
  90. package/dist/core/services/toc-service.d.ts +30 -0
  91. package/dist/core/services/toc-service.d.ts.map +1 -0
  92. package/dist/core/services/toc-service.js +107 -0
  93. package/dist/core/services/toc-service.js.map +1 -0
  94. package/dist/core/services/tokenizer.d.ts +20 -1
  95. package/dist/core/services/tokenizer.d.ts.map +1 -1
  96. package/dist/core/services/tokenizer.js +32 -4
  97. package/dist/core/services/tokenizer.js.map +1 -1
  98. package/dist/core/services/topic-resolver.d.ts +74 -0
  99. package/dist/core/services/topic-resolver.d.ts.map +1 -0
  100. package/dist/core/services/topic-resolver.js +191 -0
  101. package/dist/core/services/topic-resolver.js.map +1 -0
  102. package/dist/core/tools/batch-get-articles.d.ts +0 -2
  103. package/dist/core/tools/batch-get-articles.d.ts.map +1 -1
  104. package/dist/core/tools/batch-get-articles.js +18 -46
  105. package/dist/core/tools/batch-get-articles.js.map +1 -1
  106. package/dist/core/tools/get-article.d.ts.map +1 -1
  107. package/dist/core/tools/get-article.js +29 -112
  108. package/dist/core/tools/get-article.js.map +1 -1
  109. package/dist/core/tools/get-toc.js +1 -1
  110. package/dist/core/tools/get-toc.js.map +1 -1
  111. package/dist/core/tools/glossary-lookup.d.ts.map +1 -1
  112. package/dist/core/tools/glossary-lookup.js +26 -7
  113. package/dist/core/tools/glossary-lookup.js.map +1 -1
  114. package/dist/core/tools/list-products.d.ts.map +1 -1
  115. package/dist/core/tools/list-products.js +5 -3
  116. package/dist/core/tools/list-products.js.map +1 -1
  117. package/dist/core/tools/search.js +5 -5
  118. package/dist/core/tools/search.js.map +1 -1
  119. package/dist/core/types/context.d.ts +5 -2
  120. package/dist/core/types/context.d.ts.map +1 -1
  121. package/dist/core/types.d.ts +141 -31
  122. package/dist/core/types.d.ts.map +1 -1
  123. package/dist/core/types.js.map +1 -1
  124. package/dist/core/utils/bundle.d.ts +14 -1
  125. package/dist/core/utils/bundle.d.ts.map +1 -1
  126. package/dist/core/utils/bundle.js +19 -2
  127. package/dist/core/utils/bundle.js.map +1 -1
  128. package/dist/core/utils/format-article.d.ts +49 -0
  129. package/dist/core/utils/format-article.d.ts.map +1 -0
  130. package/dist/core/utils/format-article.js +188 -0
  131. package/dist/core/utils/format-article.js.map +1 -0
  132. package/dist/core/utils/ft-metadata.d.ts +22 -0
  133. package/dist/core/utils/ft-metadata.d.ts.map +1 -0
  134. package/dist/core/utils/ft-metadata.js +30 -0
  135. package/dist/core/utils/ft-metadata.js.map +1 -0
  136. package/dist/core/utils/sanitize.d.ts.map +1 -1
  137. package/dist/core/utils/sanitize.js +0 -2
  138. package/dist/core/utils/sanitize.js.map +1 -1
  139. package/dist/core/utils/url.d.ts.map +1 -1
  140. package/dist/core/utils/url.js +0 -1
  141. package/dist/core/utils/url.js.map +1 -1
  142. package/dist/index.js +10 -6
  143. package/dist/index.js.map +1 -1
  144. package/dist/platforms/node/http-server.d.ts +13 -0
  145. package/dist/platforms/node/http-server.d.ts.map +1 -0
  146. package/dist/platforms/node/http-server.js +231 -0
  147. package/dist/platforms/node/http-server.js.map +1 -0
  148. package/dist/platforms/node/index.d.ts +1 -1
  149. package/dist/platforms/node/index.d.ts.map +1 -1
  150. package/dist/platforms/node/index.js +1 -1
  151. package/dist/platforms/node/index.js.map +1 -1
  152. package/dist/platforms/node/logger.d.ts +13 -2
  153. package/dist/platforms/node/logger.d.ts.map +1 -1
  154. package/dist/platforms/node/logger.js +17 -4
  155. package/dist/platforms/node/logger.js.map +1 -1
  156. package/dist/transport/http-handler.d.ts +25 -0
  157. package/dist/transport/http-handler.d.ts.map +1 -0
  158. package/dist/transport/http-handler.js +193 -0
  159. package/dist/transport/http-handler.js.map +1 -0
  160. package/dist/transport/http-types.d.ts +22 -0
  161. package/dist/transport/http-types.d.ts.map +1 -0
  162. package/dist/transport/http-types.js +65 -0
  163. package/dist/transport/http-types.js.map +1 -0
  164. package/dist/transport/http.d.ts +4 -8
  165. package/dist/transport/http.d.ts.map +1 -1
  166. package/dist/transport/http.js +4 -376
  167. package/dist/transport/http.js.map +1 -1
  168. package/dist/transport/index.js +2 -2
  169. package/dist/transport/index.js.map +1 -1
  170. package/dist/transport/rate-limiter.d.ts +16 -0
  171. package/dist/transport/rate-limiter.d.ts.map +1 -0
  172. package/dist/transport/rate-limiter.js +55 -0
  173. package/dist/transport/rate-limiter.js.map +1 -0
  174. package/package.json +3 -3
  175. package/dist/core/services/interfaces.d.ts +0 -94
  176. package/dist/core/services/interfaces.d.ts.map +0 -1
  177. package/dist/core/services/interfaces.js +0 -8
  178. package/dist/core/services/interfaces.js.map +0 -1
  179. package/dist/core/services/scraper.d.ts +0 -90
  180. package/dist/core/services/scraper.d.ts.map +0 -1
  181. package/dist/core/services/scraper.js +0 -990
  182. package/dist/core/services/scraper.js.map +0 -1
  183. package/dist/core/utils/doc-type.d.ts +0 -12
  184. package/dist/core/utils/doc-type.d.ts.map +0 -1
  185. package/dist/core/utils/doc-type.js +0 -24
  186. package/dist/core/utils/doc-type.js.map +0 -1
  187. package/dist/platforms/node/metadata.d.ts +0 -18
  188. package/dist/platforms/node/metadata.d.ts.map +0 -1
  189. package/dist/platforms/node/metadata.js +0 -35
  190. package/dist/platforms/node/metadata.js.map +0 -1
@@ -0,0 +1,462 @@
1
+ /**
2
+ * Search service — Fluid Topics powered search
3
+ */
4
+ import { JAMF_PRODUCTS, JAMF_TOPICS, DOC_TYPE_CONTENT_TYPE_MAP, DOC_TYPE_LABEL_MAP, CONTENT_LIMITS, TOKEN_CONFIG, PAGINATION_CONFIG, DEFAULT_LOCALE, } from '../constants.js';
5
+ import { search as ftSearch } from './ft-client.js';
6
+ import { buildDisplayUrl } from './topic-resolver.js';
7
+ import { cleanSnippet } from './content-parser.js';
8
+ import { getMetaValues, FT_META } from '../utils/ft-metadata.js';
9
+ import { estimateTokens, calculatePagination, truncateListByTokens, buildPaginationNote, } from './tokenizer.js';
10
+ // ─── Helpers ───────────────────────────────────────────────────
11
+ /** Pre-computed lowercase keywords per topic */
12
+ const TOPIC_KEYWORDS_LOWER = Object.fromEntries(Object.keys(JAMF_TOPICS).map(id => [
13
+ id,
14
+ JAMF_TOPICS[id].keywords.map(k => k.toLowerCase()),
15
+ ]));
16
+ const ALL_TOPIC_IDS = Object.keys(JAMF_TOPICS);
17
+ function matchTopics(title, snippet) {
18
+ const searchText = `${title} ${snippet}`.toLowerCase();
19
+ return ALL_TOPIC_IDS.filter(topicId => TOPIC_KEYWORDS_LOWER[topicId].some(kw => searchText.includes(kw)));
20
+ }
21
+ /** Pre-computed reverse lookup: product display name → ProductId */
22
+ const PRODUCT_NAME_TO_ID = Object.fromEntries(Object.keys(JAMF_PRODUCTS).map(id => [JAMF_PRODUCTS[id].name, id]));
23
+ /**
24
+ * Resolve a product display name (e.g. 'Jamf Pro') to its ProductId
25
+ * (e.g. 'jamf-pro'). Returns null when the name is unknown.
26
+ */
27
+ function productNameToId(name) {
28
+ if (name === null) {
29
+ return null;
30
+ }
31
+ return PRODUCT_NAME_TO_ID[name] ?? null;
32
+ }
33
+ /**
34
+ * Extract product ID (e.g. 'jamf-pro') from a legacy metadata value
35
+ * like 'product-pro'. Falls back to scanning known searchLabel values.
36
+ */
37
+ function extractProductFromZoominMeta(metadata) {
38
+ const values = getMetaValues(metadata, FT_META.ZOOMIN_METADATA);
39
+ for (const val of values) {
40
+ // Match against known searchLabels in JAMF_PRODUCTS
41
+ const matched = Object.entries(JAMF_PRODUCTS).find(([, product]) => product.searchLabel === val);
42
+ if (matched !== undefined) {
43
+ return matched[1].name;
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+ /**
49
+ * Derive docType from FT metadata `jamf:contentType`.
50
+ * Falls back to 'documentation'.
51
+ *
52
+ * Note: The FT API uses 'Technical Documentation' for multiple doc types
53
+ * (documentation, training, solution-guide, getting-started). Because
54
+ * DOC_TYPE_CONTENT_TYPE_MAP is iterated in insertion order, this reverse
55
+ * lookup always returns 'documentation' for any of those types. This is
56
+ * a known FT API limitation — there is no metadata to distinguish them.
57
+ */
58
+ function docTypeFromFtMetadata(metadata) {
59
+ const values = getMetaValues(metadata, FT_META.CONTENT_TYPE);
60
+ if (values.length > 0) {
61
+ // Reverse-lookup DOC_TYPE_CONTENT_TYPE_MAP
62
+ for (const [docType, contentType] of Object.entries(DOC_TYPE_CONTENT_TYPE_MAP)) {
63
+ if (values.includes(contentType)) {
64
+ return docType;
65
+ }
66
+ }
67
+ }
68
+ return 'documentation';
69
+ }
70
+ // ─── Filter Construction ───────────────────────────────────────
71
+ /**
72
+ * Build Fluid Topics search filters from search params.
73
+ *
74
+ * - product → `zoominmetadata` filter using searchLabel
75
+ * - docType → `jamf:contentType` filter using DOC_TYPE_CONTENT_TYPE_MAP
76
+ * - version → `version` filter
77
+ * - auto-adds `latestVersion=yes` when no version specified
78
+ */
79
+ export function buildSearchFilters(params) {
80
+ const filters = [];
81
+ // Product filter
82
+ if (params.product !== undefined) {
83
+ const productDef = JAMF_PRODUCTS[params.product];
84
+ filters.push({
85
+ key: FT_META.ZOOMIN_METADATA,
86
+ values: [productDef.searchLabel],
87
+ });
88
+ }
89
+ // Document type filter
90
+ if (params.docType !== undefined) {
91
+ const contentType = DOC_TYPE_CONTENT_TYPE_MAP[params.docType];
92
+ if (contentType !== undefined) {
93
+ filters.push({
94
+ key: FT_META.CONTENT_TYPE,
95
+ values: [contentType],
96
+ });
97
+ }
98
+ }
99
+ // Version handling
100
+ if (params.version !== undefined && params.version !== '' && params.version !== 'current') {
101
+ filters.push({
102
+ key: FT_META.VERSION,
103
+ values: [params.version],
104
+ });
105
+ }
106
+ else {
107
+ // Auto-add latestVersion filter when no specific version requested
108
+ filters.push({
109
+ key: FT_META.LATEST_VERSION,
110
+ values: ['yes'],
111
+ });
112
+ }
113
+ return filters;
114
+ }
115
+ // ─── Result Transformation ─────────────────────────────────────
116
+ /**
117
+ * Transform a Fluid Topics search entry into an enriched SearchResult.
118
+ */
119
+ export function transformFtSearchResult(entry) {
120
+ if (entry.type === 'TOPIC' && entry.topic !== undefined) {
121
+ return transformTopicEntry(entry);
122
+ }
123
+ if (entry.type === 'MAP' && entry.map !== undefined) {
124
+ return transformMapEntry(entry);
125
+ }
126
+ // Fallback for unexpected entry shapes
127
+ return {
128
+ title: 'Untitled',
129
+ url: '',
130
+ snippet: '',
131
+ product: null,
132
+ };
133
+ }
134
+ /**
135
+ * Shared builder: turns the common fields of a TOPIC or MAP entry
136
+ * into a fully-populated SearchResult.
137
+ */
138
+ function buildSearchResult(fields) {
139
+ const { metadata } = fields;
140
+ const title = fields.title !== '' ? fields.title : 'Untitled';
141
+ const product = extractProductFromZoominMeta(metadata);
142
+ const snippet = cleanSnippet(fields.htmlExcerpt, title, product);
143
+ const versionValues = getMetaValues(metadata, FT_META.VERSION);
144
+ const docType = docTypeFromFtMetadata(metadata);
145
+ const result = {
146
+ title,
147
+ url: fields.url,
148
+ snippet,
149
+ product,
150
+ docType,
151
+ mapId: fields.mapId,
152
+ };
153
+ if (fields.contentId !== undefined) {
154
+ result.contentId = fields.contentId;
155
+ }
156
+ const firstVersion = versionValues[0];
157
+ if (firstVersion !== undefined) {
158
+ result.version = firstVersion;
159
+ }
160
+ if (fields.breadcrumb !== undefined && fields.breadcrumb.length > 0) {
161
+ result.breadcrumb = fields.breadcrumb;
162
+ }
163
+ if (fields.mapTitle !== undefined && fields.mapTitle !== '') {
164
+ result.mapTitle = fields.mapTitle;
165
+ }
166
+ return result;
167
+ }
168
+ function resolveTopicUrl(topic) {
169
+ const prettyUrls = getMetaValues(topic.metadata, FT_META.PRETTY_URL);
170
+ const rawPrettyUrl = prettyUrls[0];
171
+ if (rawPrettyUrl !== undefined) {
172
+ const normalizedPath = rawPrettyUrl.startsWith('/')
173
+ ? rawPrettyUrl
174
+ : `/r/${rawPrettyUrl}`;
175
+ return buildDisplayUrl(normalizedPath);
176
+ }
177
+ return buildDisplayUrl(`/r/en-US/${topic.mapId}/${topic.contentId}`);
178
+ }
179
+ function resolveMapUrl(map) {
180
+ if (map.readerUrl !== '') {
181
+ return buildDisplayUrl(map.readerUrl);
182
+ }
183
+ if (map.mapUrl !== '') {
184
+ return buildDisplayUrl(map.mapUrl);
185
+ }
186
+ return '';
187
+ }
188
+ function transformTopicEntry(entry) {
189
+ const { topic } = entry;
190
+ if (topic === undefined) {
191
+ return { title: 'Untitled', url: '', snippet: '', product: null };
192
+ }
193
+ return buildSearchResult({
194
+ title: topic.title,
195
+ url: resolveTopicUrl(topic),
196
+ htmlExcerpt: topic.htmlExcerpt,
197
+ metadata: topic.metadata,
198
+ mapId: topic.mapId,
199
+ contentId: topic.contentId,
200
+ breadcrumb: topic.breadcrumb,
201
+ mapTitle: topic.mapTitle,
202
+ });
203
+ }
204
+ function transformMapEntry(entry) {
205
+ const { map } = entry;
206
+ if (map === undefined) {
207
+ return { title: 'Untitled', url: '', snippet: '', product: null };
208
+ }
209
+ return buildSearchResult({
210
+ title: map.title,
211
+ url: resolveMapUrl(map),
212
+ htmlExcerpt: map.htmlExcerpt,
213
+ metadata: map.metadata,
214
+ mapId: map.mapId,
215
+ mapTitle: map.title,
216
+ });
217
+ }
218
+ // ─── Filter Relaxation ─────────────────────────────────────────
219
+ /**
220
+ * Build active filters from search params for progressive relaxation.
221
+ * These are client-side post-filters for topic matching (which FT
222
+ * doesn't support server-side).
223
+ */
224
+ function buildActiveFilters(params) {
225
+ const activeFilters = [];
226
+ if (params.product !== undefined) {
227
+ const productId = params.product;
228
+ activeFilters.push({
229
+ name: 'product',
230
+ value: productId,
231
+ apply: (results) => results.filter(r => r.bundleSlug === productId),
232
+ });
233
+ }
234
+ if (params.topic !== undefined) {
235
+ const topicFilter = params.topic;
236
+ activeFilters.push({
237
+ name: 'topic',
238
+ value: topicFilter,
239
+ apply: (results) => results.filter(r => r.matchedTopics.includes(topicFilter)),
240
+ });
241
+ }
242
+ if (params.docType !== undefined) {
243
+ const docTypeFilter = params.docType;
244
+ const targetLabelKey = DOC_TYPE_LABEL_MAP[docTypeFilter];
245
+ if (targetLabelKey !== undefined) {
246
+ activeFilters.push({
247
+ name: 'docType',
248
+ value: docTypeFilter,
249
+ apply: (results) => results.filter(r => {
250
+ if (r.labelKeys.length === 0) {
251
+ return true;
252
+ }
253
+ return r.labelKeys.includes(targetLabelKey);
254
+ }),
255
+ });
256
+ }
257
+ }
258
+ return activeFilters;
259
+ }
260
+ /**
261
+ * Apply filters with progressive relaxation when results are zero.
262
+ * Relaxation order: docType -> topic -> product
263
+ */
264
+ function applyFiltersWithFallback(allResults, activeFilters) {
265
+ let filtered = allResults;
266
+ for (const filter of activeFilters) {
267
+ filtered = filter.apply(filtered);
268
+ }
269
+ if (filtered.length > 0 || activeFilters.length === 0) {
270
+ return { filtered };
271
+ }
272
+ // Progressive relaxation
273
+ const relaxOrder = ['docType', 'topic', 'product'];
274
+ const removed = [];
275
+ const original = {};
276
+ for (const filterName of relaxOrder) {
277
+ if (filtered.length > 0) {
278
+ break;
279
+ }
280
+ const filterIndex = activeFilters.findIndex(f => f.name === filterName);
281
+ if (filterIndex === -1) {
282
+ continue;
283
+ }
284
+ const removedFilter = activeFilters[filterIndex];
285
+ if (removedFilter === undefined) {
286
+ continue;
287
+ }
288
+ removed.push(removedFilter.name);
289
+ original[removedFilter.name] = removedFilter.value;
290
+ activeFilters.splice(filterIndex, 1);
291
+ // Re-apply remaining filters
292
+ filtered = allResults;
293
+ for (const filter of activeFilters) {
294
+ filtered = filter.apply(filtered);
295
+ }
296
+ }
297
+ if (removed.length > 0) {
298
+ return {
299
+ filtered,
300
+ relaxation: {
301
+ removed,
302
+ original,
303
+ message: `No results with all filters applied. Removed filter(s): ${removed.join(', ')}. Try broader search terms or fewer filters.`,
304
+ },
305
+ };
306
+ }
307
+ return { filtered };
308
+ }
309
+ // ─── Token Truncation ──────────────────────────────────────────
310
+ function resultToString(r) {
311
+ return `${r.title}\n${r.snippet}\n${r.url}`;
312
+ }
313
+ function truncateSearchResults(paginatedResults, maxTokens) {
314
+ const { items: finalResults, tokenCount: finalTokenCount, truncated } = truncateListByTokens(paginatedResults, maxTokens, resultToString);
315
+ if (!truncated) {
316
+ return { finalResults, finalTokenCount, truncated: false };
317
+ }
318
+ const omittedResults = paginatedResults.slice(finalResults.length);
319
+ const truncatedContent = {
320
+ omittedCount: omittedResults.length,
321
+ omittedItems: omittedResults.map(r => ({
322
+ title: r.title,
323
+ estimatedTokens: estimateTokens(resultToString(r)),
324
+ })),
325
+ };
326
+ return { finalResults, finalTokenCount, truncated: true, truncatedContent };
327
+ }
328
+ // ─── Convert flat SearchResult to SearchResultWithMeta ─────────
329
+ function toSearchResultWithMeta(result, needsTopicMatching) {
330
+ // Derive bundleSlug from the product display name (extracted from
331
+ // zoominmetadata). Do NOT use result.mapId — FT API mapIds are
332
+ // opaque hashes (e.g. 'uRhiWJWbjHyL1vegaHmj8g'), not readable
333
+ // bundle stems.
334
+ const bundleSlug = productNameToId(result.product);
335
+ const labelKeys = result.docType !== undefined
336
+ ? [DOC_TYPE_LABEL_MAP[result.docType]]
337
+ : [];
338
+ return {
339
+ result,
340
+ bundleSlug,
341
+ matchedTopics: needsTopicMatching
342
+ ? matchTopics(result.title, result.snippet)
343
+ : [],
344
+ labelKeys,
345
+ };
346
+ }
347
+ // ─── Main Search Function ──────────────────────────────────────
348
+ /**
349
+ * Search Jamf documentation using the Fluid Topics clustered-search API.
350
+ *
351
+ * 1. Checks SearchProvider first (custom backend injection).
352
+ * 2. Calls ft-client.search() with constructed filters.
353
+ * 3. Transforms results and applies post-processing pipeline:
354
+ * - Client-side topic/docType filtering with progressive relaxation
355
+ * - Pagination
356
+ * - Token truncation
357
+ */
358
+ export async function searchDocumentation(ctx, params) {
359
+ const log = ctx.logger.createLogger('search-service');
360
+ const page = params.page ?? PAGINATION_CONFIG.DEFAULT_PAGE;
361
+ const pageSize = params.limit ?? CONTENT_LIMITS.DEFAULT_SEARCH_RESULTS;
362
+ const maxTokens = params.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS;
363
+ let allResults;
364
+ let searchError;
365
+ try {
366
+ allResults = await resolveSearchResults(ctx, params, log);
367
+ }
368
+ catch (error) {
369
+ const message = String(error);
370
+ log.error(`Search error: ${message}`);
371
+ allResults = [];
372
+ searchError = message;
373
+ }
374
+ // Build and apply filters with progressive relaxation
375
+ const activeFilters = buildActiveFilters(params);
376
+ const { filtered: filteredResults, relaxation: filterRelaxation } = applyFiltersWithFallback(allResults, activeFilters);
377
+ // Calculate pagination
378
+ const paginationInfo = calculatePagination(filteredResults.length, page, pageSize);
379
+ const paginatedResults = filteredResults
380
+ .slice(paginationInfo.startIndex, paginationInfo.endIndex)
381
+ .map(r => r.result);
382
+ const { finalResults, finalTokenCount, truncated, truncatedContent } = truncateSearchResults(paginatedResults, maxTokens);
383
+ const paginationNote = buildPaginationNote(paginationInfo);
384
+ return {
385
+ results: finalResults,
386
+ pagination: {
387
+ page: paginationInfo.page,
388
+ pageSize: paginationInfo.pageSize,
389
+ totalPages: paginationInfo.totalPages,
390
+ totalItems: filteredResults.length,
391
+ hasNext: paginationInfo.hasNext,
392
+ hasPrev: paginationInfo.hasPrev,
393
+ },
394
+ tokenInfo: {
395
+ tokenCount: finalTokenCount,
396
+ truncated,
397
+ maxTokens,
398
+ },
399
+ ...(paginationNote !== undefined ? { paginationNote } : {}),
400
+ ...(filterRelaxation !== undefined ? { filterRelaxation } : {}),
401
+ ...(truncatedContent !== undefined ? { truncatedContent } : {}),
402
+ ...(searchError !== undefined ? { searchError } : {}),
403
+ };
404
+ }
405
+ // ─── Internal: Resolve results from provider or FT API ─────────
406
+ /**
407
+ * Build a deterministic cache key for FT search results.
408
+ *
409
+ * Derived from query + filters + locale (NOT page/limit, since
410
+ * pagination is applied client-side after caching).
411
+ */
412
+ function buildSearchCacheKey(query, filters, locale) {
413
+ const sortedFilters = [...filters]
414
+ .sort((a, b) => a.key.localeCompare(b.key))
415
+ .map(f => `${f.key}=${f.values.sort().join(',')}`)
416
+ .join('|');
417
+ return `ft-search:${locale}:${query}:${sortedFilters}`;
418
+ }
419
+ async function resolveSearchResults(ctx, params, log) {
420
+ const needsTopicMatching = params.topic !== undefined;
421
+ // 1. Try SearchProvider first (custom backend injection — no caching)
422
+ if (ctx.searchProvider !== undefined) {
423
+ const provided = await ctx.searchProvider.search(params);
424
+ if (provided !== null) {
425
+ return provided.map(r => toSearchResultWithMeta(r, needsTopicMatching));
426
+ }
427
+ }
428
+ // 2. Build FT search request
429
+ const locale = params.language ?? DEFAULT_LOCALE;
430
+ const filters = buildSearchFilters(params);
431
+ const cacheKey = buildSearchCacheKey(params.query, filters, locale);
432
+ // 3. Check cache before hitting the FT API
433
+ const cached = await ctx.cache.get(cacheKey);
434
+ if (cached !== null) {
435
+ log.debug(`Search cache hit: key="${cacheKey}", ${cached.length} results`);
436
+ return cached;
437
+ }
438
+ const perPage = Math.min(CONTENT_LIMITS.MAX_SEARCH_RESULTS, CONTENT_LIMITS.FILTER_OVERFETCH_CAP);
439
+ log.debug(`FT search: query="${params.query}", product=${params.product ?? 'all'}, ` +
440
+ `locale=${locale}, filters=${JSON.stringify(filters)}`);
441
+ const ftResponse = await ftSearch({
442
+ query: params.query,
443
+ contentLocale: locale,
444
+ paging: { perPage, page: 1 },
445
+ filters,
446
+ });
447
+ // 4. Flatten clusters and transform entries
448
+ const results = [];
449
+ for (const cluster of ftResponse.results) {
450
+ for (const entry of cluster.entries) {
451
+ const searchResult = transformFtSearchResult(entry);
452
+ if (searchResult.url !== '') {
453
+ results.push(toSearchResultWithMeta(searchResult, needsTopicMatching));
454
+ }
455
+ }
456
+ }
457
+ // 5. Cache the raw results (before client-side filtering)
458
+ await ctx.cache.set(cacheKey, results, ctx.config.cacheTtl.search);
459
+ log.debug(`FT search returned ${results.length} results (cached)`);
460
+ return results;
461
+ }
462
+ //# sourceMappingURL=search-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-service.js","sourceRoot":"","sources":["../../../src/core/services/search-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * TOC Service — fetch and transform Fluid Topics TOC into TocEntry[]
3
+ *
4
+ * Replaces the scraper-based TOC fetching with the FT API
5
+ * via ft-client + MapsRegistry.
6
+ */
7
+ import type { ProductId } from '../constants.js';
8
+ import type { ServerContext } from '../types/context.js';
9
+ import type { FtTocNode, TocEntry, FetchTocOptions, FetchTocResult } from '../types.js';
10
+ /**
11
+ * Recursively convert FtTocNode[] → TocEntry[]
12
+ *
13
+ * Enriches each entry with:
14
+ * - url: buildDisplayUrl(prettyUrl)
15
+ * - contentId / tocId from the FT node
16
+ * - recursively transformed children
17
+ */
18
+ export declare function transformFtTocToTocEntries(nodes: FtTocNode[]): TocEntry[];
19
+ /**
20
+ * Fetch table of contents for a product via the Fluid Topics API.
21
+ *
22
+ * Resolution order:
23
+ * 1. ctx.tocProvider (if configured)
24
+ * 2. MapsRegistry → mapId → ft-client.fetchMapToc
25
+ * 3. Transform FtTocNode[] → TocEntry[]
26
+ *
27
+ * Results are cached under `ft-toc:{locale}:{product}:{version}`.
28
+ */
29
+ export declare function fetchTableOfContents(ctx: ServerContext, product: ProductId, version?: string, options?: FetchTocOptions): Promise<FetchTocResult>;
30
+ //# sourceMappingURL=toc-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toc-service.d.ts","sourceRoot":"","sources":["../../../src/core/services/toc-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAkB,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKxG;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,QAAQ,EAAE,CAezE;AA0BD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,SAAS,EAClB,OAAO,SAAY,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAkEzB"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * TOC Service — fetch and transform Fluid Topics TOC into TocEntry[]
3
+ *
4
+ * Replaces the scraper-based TOC fetching with the FT API
5
+ * via ft-client + MapsRegistry.
6
+ */
7
+ import { fetchMapToc } from './ft-client.js';
8
+ import { buildDisplayUrl } from './topic-resolver.js';
9
+ import { calculatePagination, truncateListByTokens, buildPaginationNote, } from './tokenizer.js';
10
+ import { JAMF_PRODUCTS, DEFAULT_LOCALE, PAGINATION_CONFIG, TOKEN_CONFIG, } from '../constants.js';
11
+ import { JamfDocsError, JamfDocsErrorCode } from '../types.js';
12
+ // ─── Transform helpers ─────────────────────────────────────────
13
+ /**
14
+ * Recursively convert FtTocNode[] → TocEntry[]
15
+ *
16
+ * Enriches each entry with:
17
+ * - url: buildDisplayUrl(prettyUrl)
18
+ * - contentId / tocId from the FT node
19
+ * - recursively transformed children
20
+ */
21
+ export function transformFtTocToTocEntries(nodes) {
22
+ return nodes.map((node) => {
23
+ const entry = {
24
+ title: node.title,
25
+ url: buildDisplayUrl(node.prettyUrl),
26
+ contentId: node.contentId,
27
+ tocId: node.tocId,
28
+ };
29
+ if (node.children.length > 0) {
30
+ entry.children = transformFtTocToTocEntries(node.children);
31
+ }
32
+ return entry;
33
+ });
34
+ }
35
+ // ─── Counting / serialisation helpers ──────────────────────────
36
+ /**
37
+ * Count total TOC entries including nested children
38
+ */
39
+ function countTocEntries(entries) {
40
+ return entries.reduce((count, entry) => count + 1 + (entry.children !== undefined ? countTocEntries(entry.children) : 0), 0);
41
+ }
42
+ /**
43
+ * Serialise a single TOC entry for token estimation
44
+ */
45
+ function tocEntryToString(entry, depth = 0) {
46
+ const indent = ' '.repeat(depth);
47
+ const childrenStr = entry.children?.map(c => tocEntryToString(c, depth + 1)).join('') ?? '';
48
+ return `${indent}- ${entry.title}\n${childrenStr}`;
49
+ }
50
+ // ─── Main fetch function ───────────────────────────────────────
51
+ /**
52
+ * Fetch table of contents for a product via the Fluid Topics API.
53
+ *
54
+ * Resolution order:
55
+ * 1. ctx.tocProvider (if configured)
56
+ * 2. MapsRegistry → mapId → ft-client.fetchMapToc
57
+ * 3. Transform FtTocNode[] → TocEntry[]
58
+ *
59
+ * Results are cached under `ft-toc:{locale}:{product}:{version}`.
60
+ */
61
+ export async function fetchTableOfContents(ctx, product, version = 'current', options = {}) {
62
+ if (ctx.tocProvider !== undefined) {
63
+ const provided = await ctx.tocProvider.getTableOfContents(product, version, options);
64
+ if (provided !== null) {
65
+ return provided;
66
+ }
67
+ }
68
+ const page = options.page ?? PAGINATION_CONFIG.DEFAULT_PAGE;
69
+ const maxTokens = options.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS;
70
+ const locale = options.locale ?? DEFAULT_LOCALE;
71
+ const cacheKey = `ft-toc:${locale}:${product}:${version}`;
72
+ let allToc = await ctx.cache.get(cacheKey);
73
+ if (allToc === null) {
74
+ const productInfo = JAMF_PRODUCTS[product];
75
+ const mapId = await ctx.mapsRegistry.resolveMapId(productInfo.bundleId, version !== 'current' ? version : undefined, locale);
76
+ if (mapId === null) {
77
+ throw new JamfDocsError(`Could not resolve map for ${product} version ${version} locale ${locale}`, JamfDocsErrorCode.NOT_FOUND);
78
+ }
79
+ const ftNodes = await fetchMapToc(mapId);
80
+ allToc = transformFtTocToTocEntries(ftNodes);
81
+ await ctx.cache.set(cacheKey, allToc, ctx.config.cacheTtl.article);
82
+ }
83
+ // ─── Pagination & token truncation ───────────────────────────
84
+ const totalItems = countTocEntries(allToc);
85
+ const topLevelCount = allToc.length;
86
+ const pageSize = PAGINATION_CONFIG.DEFAULT_PAGE_SIZE;
87
+ const paginationCalc = calculatePagination(topLevelCount, page, pageSize);
88
+ const paginatedToc = allToc.slice(paginationCalc.startIndex, paginationCalc.endIndex);
89
+ const { items: finalToc, tokenCount, truncated } = truncateListByTokens(paginatedToc, maxTokens, tocEntryToString);
90
+ const tokenInfo = { tokenCount, truncated, maxTokens };
91
+ const pagination = {
92
+ page: paginationCalc.page,
93
+ pageSize: paginationCalc.pageSize,
94
+ totalPages: paginationCalc.totalPages,
95
+ totalItems,
96
+ hasNext: paginationCalc.hasNext,
97
+ hasPrev: paginationCalc.hasPrev,
98
+ };
99
+ const paginationNote = buildPaginationNote(paginationCalc);
100
+ return {
101
+ toc: finalToc,
102
+ pagination,
103
+ tokenInfo,
104
+ ...(paginationNote !== undefined ? { paginationNote } : {}),
105
+ };
106
+ }
107
+ //# sourceMappingURL=toc-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toc-service.js","sourceRoot":"","sources":["../../../src/core/services/toc-service.ts"],"names":[],"mappings":"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"}
@@ -65,7 +65,7 @@ export declare function extractSection(content: string, sectionIdentifier: strin
65
65
  * - Correctly closes code blocks
66
66
  * - Lists remaining sections when truncated
67
67
  */
68
- export declare function truncateToTokenLimit(content: string, maxTokens?: number): TruncateResult;
68
+ export declare function truncateToTokenLimit(content: string, maxTokens?: number, preExtractedSections?: ArticleSection[]): TruncateResult;
69
69
  /**
70
70
  * Extract summary from Markdown content
71
71
  * Returns the first paragraph and an outline of sections with token estimates
@@ -83,7 +83,26 @@ export declare function calculatePagination(totalItems: number, page: number, pa
83
83
  hasPrev: boolean;
84
84
  startIndex: number;
85
85
  endIndex: number;
86
+ requestedPage: number;
87
+ pageWasClamped: boolean;
86
88
  };
89
+ /**
90
+ * Truncate an already-paginated list to fit within a token budget.
91
+ * Items are dropped from the end once cumulative tokens exceed maxTokens.
92
+ */
93
+ export declare function truncateListByTokens<T>(items: T[], maxTokens: number, toString: (item: T) => string): {
94
+ items: T[];
95
+ tokenCount: number;
96
+ truncated: boolean;
97
+ };
98
+ /**
99
+ * Build a human-readable note when the requested page was clamped.
100
+ */
101
+ export declare function buildPaginationNote(pagination: {
102
+ pageWasClamped: boolean;
103
+ requestedPage: number;
104
+ totalPages: number;
105
+ }): string | undefined;
87
106
  /**
88
107
  * Truncate an array of items to fit within token limit
89
108
  * Returns items that fit and pagination info