@get-technology-inc/jamf-docs-mcp-server 0.0.1 → 1.1.0

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -29
  3. package/dist/constants.d.ts +263 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +308 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/index.d.ts +9 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +40 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/resources/index.d.ts +13 -0
  12. package/dist/resources/index.d.ts.map +1 -0
  13. package/dist/resources/index.js +45 -0
  14. package/dist/resources/index.js.map +1 -0
  15. package/dist/schemas/index.d.ts +61 -0
  16. package/dist/schemas/index.d.ts.map +1 -0
  17. package/dist/schemas/index.js +127 -0
  18. package/dist/schemas/index.js.map +1 -0
  19. package/dist/services/cache.d.ts +47 -0
  20. package/dist/services/cache.d.ts.map +1 -0
  21. package/dist/services/cache.js +165 -0
  22. package/dist/services/cache.js.map +1 -0
  23. package/dist/services/metadata.d.ts +78 -0
  24. package/dist/services/metadata.d.ts.map +1 -0
  25. package/dist/services/metadata.js +349 -0
  26. package/dist/services/metadata.js.map +1 -0
  27. package/dist/services/scraper.d.ts +62 -0
  28. package/dist/services/scraper.d.ts.map +1 -0
  29. package/dist/services/scraper.js +544 -0
  30. package/dist/services/scraper.js.map +1 -0
  31. package/dist/services/search-suggestions.d.ts +27 -0
  32. package/dist/services/search-suggestions.d.ts.map +1 -0
  33. package/dist/services/search-suggestions.js +193 -0
  34. package/dist/services/search-suggestions.js.map +1 -0
  35. package/dist/services/tokenizer.d.ts +93 -0
  36. package/dist/services/tokenizer.d.ts.map +1 -0
  37. package/dist/services/tokenizer.js +330 -0
  38. package/dist/services/tokenizer.js.map +1 -0
  39. package/dist/tools/get-article.d.ts +7 -0
  40. package/dist/tools/get-article.d.ts.map +1 -0
  41. package/dist/tools/get-article.js +244 -0
  42. package/dist/tools/get-article.js.map +1 -0
  43. package/dist/tools/get-toc.d.ts +7 -0
  44. package/dist/tools/get-toc.d.ts.map +1 -0
  45. package/dist/tools/get-toc.js +202 -0
  46. package/dist/tools/get-toc.js.map +1 -0
  47. package/dist/tools/list-products.d.ts +7 -0
  48. package/dist/tools/list-products.d.ts.map +1 -0
  49. package/dist/tools/list-products.js +150 -0
  50. package/dist/tools/list-products.js.map +1 -0
  51. package/dist/tools/search.d.ts +7 -0
  52. package/dist/tools/search.d.ts.map +1 -0
  53. package/dist/tools/search.js +252 -0
  54. package/dist/tools/search.js.map +1 -0
  55. package/dist/types.d.ts +188 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +28 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +68 -6
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Caching service for Jamf documentation
3
+ *
4
+ * Provides file-based caching with TTL support
5
+ */
6
+ /**
7
+ * File-based cache implementation
8
+ */
9
+ declare class FileCache {
10
+ private readonly cacheDir;
11
+ private readonly memoryCache;
12
+ constructor(cacheDir?: string);
13
+ private static getCacheKey;
14
+ private getCachePath;
15
+ private ensureCacheDir;
16
+ /**
17
+ * Get a value from cache
18
+ */
19
+ get<T>(key: string): Promise<T | null>;
20
+ /**
21
+ * Set a value in cache
22
+ */
23
+ set(key: string, data: unknown, ttl?: number): Promise<void>;
24
+ /**
25
+ * Delete a cache entry
26
+ */
27
+ delete(key: string): Promise<void>;
28
+ /**
29
+ * Clear all cache entries
30
+ */
31
+ clear(): Promise<void>;
32
+ /**
33
+ * Get cache statistics
34
+ */
35
+ stats(): Promise<{
36
+ memoryEntries: number;
37
+ fileEntries: number;
38
+ totalSize: number;
39
+ }>;
40
+ /**
41
+ * Prune expired entries
42
+ */
43
+ prune(): Promise<number>;
44
+ }
45
+ export declare const cache: FileCache;
46
+ export {};
47
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/services/cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH;;GAEG;AACH,cAAM,SAAS;IACb,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0C;gBAE1D,QAAQ,GAAE,MAAkB;IAIxC,OAAO,CAAC,MAAM,CAAC,WAAW;IAI1B,OAAO,CAAC,YAAY;YAIN,cAAc;IAI5B;;OAEG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAiC5C;;OAEG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,GAAE,MAAkC,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB7F;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBzF;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;CAmC/B;AAGD,eAAO,MAAM,KAAK,WAAkB,CAAC"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Caching service for Jamf documentation
3
+ *
4
+ * Provides file-based caching with TTL support
5
+ */
6
+ import * as fs from 'fs/promises';
7
+ import * as path from 'path';
8
+ import * as crypto from 'crypto';
9
+ import { CACHE_TTL, CACHE_DIR } from '../constants.js';
10
+ /**
11
+ * File-based cache implementation
12
+ */
13
+ class FileCache {
14
+ cacheDir;
15
+ memoryCache = new Map();
16
+ constructor(cacheDir = CACHE_DIR) {
17
+ this.cacheDir = cacheDir;
18
+ }
19
+ static getCacheKey(key) {
20
+ return crypto.createHash('md5').update(key).digest('hex');
21
+ }
22
+ getCachePath(key) {
23
+ return path.join(this.cacheDir, `${FileCache.getCacheKey(key)}.json`);
24
+ }
25
+ async ensureCacheDir() {
26
+ await fs.mkdir(this.cacheDir, { recursive: true }).catch(() => { });
27
+ }
28
+ /**
29
+ * Get a value from cache
30
+ */
31
+ async get(key) {
32
+ // Check memory cache first
33
+ const memCached = this.memoryCache.get(key);
34
+ if (memCached !== undefined) {
35
+ if (Date.now() - memCached.timestamp < memCached.ttl) {
36
+ return memCached.data;
37
+ }
38
+ // Expired, remove from memory
39
+ this.memoryCache.delete(key);
40
+ }
41
+ // Check file cache
42
+ try {
43
+ const cachePath = this.getCachePath(key);
44
+ const content = await fs.readFile(cachePath, 'utf-8');
45
+ const entry = JSON.parse(content);
46
+ // Check if expired
47
+ if (Date.now() - entry.timestamp > entry.ttl) {
48
+ await this.delete(key);
49
+ return null;
50
+ }
51
+ // Store in memory cache for faster access
52
+ this.memoryCache.set(key, entry);
53
+ return entry.data;
54
+ }
55
+ catch {
56
+ // File doesn't exist or is invalid
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Set a value in cache
62
+ */
63
+ async set(key, data, ttl = CACHE_TTL.ARTICLE_CONTENT) {
64
+ await this.ensureCacheDir();
65
+ const entry = {
66
+ data,
67
+ timestamp: Date.now(),
68
+ ttl
69
+ };
70
+ // Store in memory
71
+ this.memoryCache.set(key, entry);
72
+ // Store in file
73
+ try {
74
+ const cachePath = this.getCachePath(key);
75
+ await fs.writeFile(cachePath, JSON.stringify(entry), 'utf-8');
76
+ }
77
+ catch (error) {
78
+ // Log but don't fail
79
+ console.error(`[CACHE] Failed to write cache: ${String(error)}`);
80
+ }
81
+ }
82
+ /**
83
+ * Delete a cache entry
84
+ */
85
+ async delete(key) {
86
+ this.memoryCache.delete(key);
87
+ await fs.unlink(this.getCachePath(key)).catch(() => { });
88
+ }
89
+ /**
90
+ * Clear all cache entries
91
+ */
92
+ async clear() {
93
+ this.memoryCache.clear();
94
+ try {
95
+ const files = await fs.readdir(this.cacheDir);
96
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
97
+ await Promise.all(jsonFiles.map(async (f) => { await fs.unlink(path.join(this.cacheDir, f)); }));
98
+ }
99
+ catch {
100
+ // Directory may not exist
101
+ }
102
+ }
103
+ /**
104
+ * Get cache statistics
105
+ */
106
+ async stats() {
107
+ let fileEntries = 0;
108
+ let totalSize = 0;
109
+ try {
110
+ const files = await fs.readdir(this.cacheDir);
111
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
112
+ fileEntries = jsonFiles.length;
113
+ for (const file of jsonFiles) {
114
+ const stat = await fs.stat(path.join(this.cacheDir, file));
115
+ totalSize += stat.size;
116
+ }
117
+ }
118
+ catch {
119
+ // Directory may not exist
120
+ }
121
+ return {
122
+ memoryEntries: this.memoryCache.size,
123
+ fileEntries,
124
+ totalSize
125
+ };
126
+ }
127
+ /**
128
+ * Prune expired entries
129
+ */
130
+ async prune() {
131
+ let pruned = 0;
132
+ // Prune memory cache
133
+ for (const [key, entry] of this.memoryCache.entries()) {
134
+ if (Date.now() - entry.timestamp > entry.ttl) {
135
+ this.memoryCache.delete(key);
136
+ pruned++;
137
+ }
138
+ }
139
+ // Prune file cache
140
+ try {
141
+ const files = await fs.readdir(this.cacheDir);
142
+ for (const file of files.filter(f => f.endsWith('.json'))) {
143
+ try {
144
+ const filePath = path.join(this.cacheDir, file);
145
+ const content = await fs.readFile(filePath, 'utf-8');
146
+ const entry = JSON.parse(content);
147
+ if (Date.now() - entry.timestamp > entry.ttl) {
148
+ await fs.unlink(filePath);
149
+ pruned++;
150
+ }
151
+ }
152
+ catch {
153
+ // Skip invalid files
154
+ }
155
+ }
156
+ }
157
+ catch {
158
+ // Directory may not exist
159
+ }
160
+ return pruned;
161
+ }
162
+ }
163
+ // Export singleton instance
164
+ export const cache = new FileCache();
165
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/services/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Metadata service for Jamf documentation
3
+ *
4
+ * Dynamically fetches product versions and topic categories from the API
5
+ * with fallback to static constants.
6
+ */
7
+ import { type ProductId } from '../constants.js';
8
+ export interface ProductMetadata {
9
+ id: string;
10
+ name: string;
11
+ description: string;
12
+ bundleId: string;
13
+ latestVersion: string;
14
+ availableVersions: string[];
15
+ labelKey: string;
16
+ }
17
+ export interface TopicMetadata {
18
+ id: string;
19
+ name: string;
20
+ source: 'toc' | 'manual';
21
+ articleCount?: number;
22
+ }
23
+ export interface TocCategory {
24
+ navId: string;
25
+ title: string;
26
+ articleCount: number;
27
+ children: string[];
28
+ }
29
+ /**
30
+ * Get all products with their latest metadata
31
+ * Uses cache with fallback to static constants
32
+ */
33
+ export declare function getProductsMetadata(): Promise<ProductMetadata[]>;
34
+ /**
35
+ * Get the bundle ID for a specific product and version
36
+ * Returns the latest bundle ID if version is undefined or 'current'
37
+ */
38
+ export declare function getBundleIdForVersion(productId: ProductId, version?: string): Promise<string | null>;
39
+ /**
40
+ * Get available versions for a product
41
+ */
42
+ export declare function getAvailableVersions(productId: ProductId): Promise<string[]>;
43
+ /**
44
+ * Get all topics, combining TOC-derived from all products and manual topics
45
+ */
46
+ export declare function getTopicsMetadata(): Promise<TopicMetadata[]>;
47
+ /**
48
+ * Get products data formatted for resource response
49
+ */
50
+ export declare function getProductsResourceData(): Promise<{
51
+ description: string;
52
+ products: {
53
+ id: string;
54
+ name: string;
55
+ description: string;
56
+ latestVersion: string;
57
+ availableVersions: string[];
58
+ bundleId: string;
59
+ }[];
60
+ lastUpdated: string;
61
+ usage: string;
62
+ }>;
63
+ /**
64
+ * Get topics data formatted for resource response
65
+ */
66
+ export declare function getTopicsResourceData(): Promise<{
67
+ description: string;
68
+ totalTopics: number;
69
+ topics: {
70
+ id: string;
71
+ name: string;
72
+ source: string;
73
+ articleCount?: number;
74
+ }[];
75
+ lastUpdated: string;
76
+ usage: string;
77
+ }>;
78
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/services/metadata.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AAOzB,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,GAAG,QAAQ,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAmID;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAoCtE;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,SAAS,EACpB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsBxB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAIlF;AAmHD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAyClE;AAMD;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,EAAE,MAAM,EAAE,CAAC;QAC5B,QAAQ,EAAE,MAAM,CAAC;KAClB,EAAE,CAAC;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAgBD;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,EAAE,CAAC;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAeD"}
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Metadata service for Jamf documentation
3
+ *
4
+ * Dynamically fetches product versions and topic categories from the API
5
+ * with fallback to static constants.
6
+ */
7
+ import axios from 'axios';
8
+ import * as cheerio from 'cheerio';
9
+ import { DOCS_API_URL, JAMF_PRODUCTS, JAMF_TOPICS, REQUEST_CONFIG, CACHE_TTL } from '../constants.js';
10
+ import { cache } from './cache.js';
11
+ // ============================================================================
12
+ // API Helpers
13
+ // ============================================================================
14
+ async function fetchJson(url) {
15
+ const response = await axios.get(url, {
16
+ timeout: REQUEST_CONFIG.TIMEOUT,
17
+ headers: {
18
+ 'User-Agent': REQUEST_CONFIG.USER_AGENT,
19
+ 'Accept': 'application/json'
20
+ }
21
+ });
22
+ return response.data;
23
+ }
24
+ /**
25
+ * Discover all available versions for a product
26
+ */
27
+ async function discoverProductVersions(productId) {
28
+ const product = JAMF_PRODUCTS[productId];
29
+ const versions = new Set();
30
+ try {
31
+ // Search with a broad query to find all bundle versions
32
+ const apiUrl = `${DOCS_API_URL}/api/search?q=${encodeURIComponent(product.name)}&rpp=100`;
33
+ const response = await fetchJson(apiUrl);
34
+ const versionRegex = /-(\d+\.\d+\.\d+)$/;
35
+ const bundlePrefix = `${product.bundleId}-`;
36
+ for (const wrapper of response.Results) {
37
+ const bundleId = wrapper.leading_result?.bundle_id;
38
+ // Skip if no bundle_id (null or undefined)
39
+ if (bundleId === null || bundleId === undefined) {
40
+ continue;
41
+ }
42
+ // Check if this is a versioned documentation bundle for our product
43
+ if (bundleId.startsWith(bundlePrefix) || bundleId === product.bundleId) {
44
+ const match = versionRegex.exec(bundleId);
45
+ if (match?.[1] !== undefined) {
46
+ versions.add(match[1]);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ catch (error) {
52
+ console.error(`[METADATA] Error discovering versions for ${productId}:`, error);
53
+ }
54
+ // Sort versions in descending order (newest first)
55
+ return Array.from(versions).sort((a, b) => {
56
+ const partsA = a.split('.').map(Number);
57
+ const partsB = b.split('.').map(Number);
58
+ for (let i = 0; i < 3; i++) {
59
+ const diff = (partsB[i] ?? 0) - (partsA[i] ?? 0);
60
+ if (diff !== 0) {
61
+ return diff;
62
+ }
63
+ }
64
+ return 0;
65
+ });
66
+ }
67
+ /**
68
+ * Fetch latest version and metadata for a product from the API
69
+ */
70
+ async function fetchProductMetadata(productId) {
71
+ const product = JAMF_PRODUCTS[productId];
72
+ try {
73
+ // Discover all available versions
74
+ const availableVersions = await discoverProductVersions(productId);
75
+ // Search for a doc from this product to get the bundle ID and labels
76
+ const apiUrl = `${DOCS_API_URL}/api/search?q=${encodeURIComponent(product.name)}&rpp=5`;
77
+ const response = await fetchJson(apiUrl);
78
+ for (const wrapper of response.Results) {
79
+ const result = wrapper.leading_result;
80
+ const bundleId = result?.bundle_id;
81
+ // Skip if no bundle_id or doesn't match our product
82
+ if (bundleId?.startsWith(product.bundleId) !== true) {
83
+ continue;
84
+ }
85
+ // Extract version from bundle_id (e.g., "jamf-pro-documentation-11.24.0" → "11.24.0")
86
+ const versionRegex = /-(\d+\.\d+\.\d+)$/;
87
+ const versionMatch = versionRegex.exec(bundleId);
88
+ const latestVersion = versionMatch?.[1] ?? 'current';
89
+ // Find product label key
90
+ const productLabel = result?.labels?.find(l => l.key.startsWith('product-') && !l.key.includes('-'));
91
+ const labelKey = productLabel?.key ?? product.searchLabel;
92
+ return {
93
+ id: productId,
94
+ name: product.name,
95
+ description: product.description,
96
+ bundleId,
97
+ latestVersion,
98
+ availableVersions: availableVersions.length > 0 ? availableVersions : [latestVersion],
99
+ labelKey
100
+ };
101
+ }
102
+ }
103
+ catch (error) {
104
+ console.error(`[METADATA] Error fetching metadata for ${productId}:`, error);
105
+ }
106
+ return null;
107
+ }
108
+ /**
109
+ * Get all products with their latest metadata
110
+ * Uses cache with fallback to static constants
111
+ */
112
+ export async function getProductsMetadata() {
113
+ const cacheKey = 'metadata:products';
114
+ // Check cache
115
+ const cached = await cache.get(cacheKey);
116
+ if (cached !== null) {
117
+ return cached;
118
+ }
119
+ const products = [];
120
+ // Fetch metadata for each product
121
+ for (const productId of Object.keys(JAMF_PRODUCTS)) {
122
+ const metadata = await fetchProductMetadata(productId);
123
+ if (metadata !== null) {
124
+ products.push(metadata);
125
+ }
126
+ else {
127
+ // Fallback to static data
128
+ const product = JAMF_PRODUCTS[productId];
129
+ products.push({
130
+ id: productId,
131
+ name: product.name,
132
+ description: product.description,
133
+ bundleId: product.bundleId,
134
+ latestVersion: product.latestVersion,
135
+ availableVersions: [product.latestVersion],
136
+ labelKey: product.searchLabel
137
+ });
138
+ }
139
+ }
140
+ // Cache for 24 hours
141
+ await cache.set(cacheKey, products, CACHE_TTL.ARTICLE_CONTENT);
142
+ return products;
143
+ }
144
+ /**
145
+ * Get the bundle ID for a specific product and version
146
+ * Returns the latest bundle ID if version is undefined or 'current'
147
+ */
148
+ export async function getBundleIdForVersion(productId, version) {
149
+ const products = await getProductsMetadata();
150
+ const product = products.find(p => p.id === productId);
151
+ if (product === undefined) {
152
+ return null;
153
+ }
154
+ // If no version specified or 'current', return latest
155
+ if (version === undefined || version === 'current' || version === 'latest') {
156
+ return product.bundleId;
157
+ }
158
+ // Check if requested version is available
159
+ if (!product.availableVersions.includes(version)) {
160
+ console.error(`[METADATA] Version ${version} not available for ${productId}. Available: ${product.availableVersions.join(', ')}`);
161
+ return null;
162
+ }
163
+ // Construct versioned bundle ID
164
+ const baseBundle = JAMF_PRODUCTS[productId].bundleId;
165
+ return `${baseBundle}-${version}`;
166
+ }
167
+ /**
168
+ * Get available versions for a product
169
+ */
170
+ export async function getAvailableVersions(productId) {
171
+ const products = await getProductsMetadata();
172
+ const product = products.find(p => p.id === productId);
173
+ return product?.availableVersions ?? [];
174
+ }
175
+ // ============================================================================
176
+ // Topic Categories from TOC
177
+ // ============================================================================
178
+ /**
179
+ * Parse TOC HTML to extract categories
180
+ */
181
+ function parseTocCategories(tocData) {
182
+ const categories = [];
183
+ // Sort by nav ID number
184
+ const sortedEntries = Object.entries(tocData)
185
+ .filter(([key]) => key.startsWith('nav-'))
186
+ .sort((a, b) => {
187
+ const numA = parseInt(a[0].replace('nav-', ''), 10);
188
+ const numB = parseInt(b[0].replace('nav-', ''), 10);
189
+ return numA - numB;
190
+ });
191
+ for (const [navId, html] of sortedEntries) {
192
+ if (typeof html !== 'string' || !html.includes('<ul')) {
193
+ continue;
194
+ }
195
+ const $ = cheerio.load(html);
196
+ const children = [];
197
+ // Get all article titles in this section
198
+ $('a').each((_, el) => {
199
+ const title = $(el).text().trim();
200
+ if (title !== '') {
201
+ children.push(title);
202
+ }
203
+ });
204
+ if (children.length > 0) {
205
+ // First child is usually the section title
206
+ const title = children[0] ?? 'Untitled';
207
+ categories.push({
208
+ navId,
209
+ title,
210
+ articleCount: children.length,
211
+ children: children.slice(1) // Exclude the title itself
212
+ });
213
+ }
214
+ }
215
+ return categories;
216
+ }
217
+ /**
218
+ * Convert TOC category to topic ID (slug)
219
+ */
220
+ function categoryToTopicId(title) {
221
+ return title
222
+ .toLowerCase()
223
+ .replace(/[^a-z0-9\s-]/g, '')
224
+ .replace(/\s+/g, '-')
225
+ .replace(/-+/g, '-')
226
+ .replace(/^-|-$/g, '')
227
+ .slice(0, 30); // Limit length
228
+ }
229
+ /**
230
+ * Fetch topic categories from TOC for a product
231
+ */
232
+ async function fetchTopicCategories(productId) {
233
+ try {
234
+ // First get the latest bundle ID
235
+ const products = await getProductsMetadata();
236
+ const product = products.find(p => p.id === productId);
237
+ if (product === undefined) {
238
+ return [];
239
+ }
240
+ const tocUrl = `${DOCS_API_URL}/bundle/${product.bundleId}/toc`;
241
+ const tocData = await fetchJson(tocUrl);
242
+ return parseTocCategories(tocData);
243
+ }
244
+ catch (error) {
245
+ console.error(`[METADATA] Error fetching TOC categories for ${productId}:`, error);
246
+ return [];
247
+ }
248
+ }
249
+ /**
250
+ * Add or update a topic in the map
251
+ */
252
+ function upsertTopic(topicsMap, category) {
253
+ const topicId = categoryToTopicId(category.title);
254
+ if (!topicsMap.has(topicId)) {
255
+ // Add new topic from TOC
256
+ topicsMap.set(topicId, {
257
+ id: topicId,
258
+ name: category.title,
259
+ source: 'toc',
260
+ articleCount: category.articleCount
261
+ });
262
+ }
263
+ else {
264
+ // Update existing topic with article count (accumulate across products)
265
+ const existing = topicsMap.get(topicId);
266
+ if (existing !== undefined) {
267
+ existing.articleCount = (existing.articleCount ?? 0) + category.articleCount;
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * Get all topics, combining TOC-derived from all products and manual topics
273
+ */
274
+ export async function getTopicsMetadata() {
275
+ const cacheKey = 'metadata:topics';
276
+ // Check cache
277
+ const cached = await cache.get(cacheKey);
278
+ if (cached !== null) {
279
+ return cached;
280
+ }
281
+ const topicsMap = new Map();
282
+ // Start with manual topics as fallback
283
+ for (const [id, topic] of Object.entries(JAMF_TOPICS)) {
284
+ topicsMap.set(id, {
285
+ id,
286
+ name: topic.name,
287
+ source: 'manual'
288
+ });
289
+ }
290
+ // Fetch TOC categories from ALL products
291
+ const productIds = Object.keys(JAMF_PRODUCTS);
292
+ for (const productId of productIds) {
293
+ try {
294
+ const categories = await fetchTopicCategories(productId);
295
+ for (const category of categories) {
296
+ upsertTopic(topicsMap, category);
297
+ }
298
+ }
299
+ catch (error) {
300
+ console.error(`[METADATA] Error fetching TOC for ${productId}:`, error);
301
+ // Continue with other products
302
+ }
303
+ }
304
+ const topics = Array.from(topicsMap.values());
305
+ // Cache for 24 hours
306
+ await cache.set(cacheKey, topics, CACHE_TTL.ARTICLE_CONTENT);
307
+ return topics;
308
+ }
309
+ // ============================================================================
310
+ // Convenience functions for Resources
311
+ // ============================================================================
312
+ /**
313
+ * Get products data formatted for resource response
314
+ */
315
+ export async function getProductsResourceData() {
316
+ const products = await getProductsMetadata();
317
+ return {
318
+ description: 'Available Jamf products for documentation search',
319
+ products: products.map(p => ({
320
+ id: p.id,
321
+ name: p.name,
322
+ description: p.description,
323
+ latestVersion: p.latestVersion,
324
+ availableVersions: p.availableVersions,
325
+ bundleId: p.bundleId
326
+ })),
327
+ lastUpdated: new Date().toISOString(),
328
+ usage: 'Use product ID (e.g., "jamf-pro") with jamf_docs_search or jamf_docs_get_toc tools. Use version parameter to query specific versions.'
329
+ };
330
+ }
331
+ /**
332
+ * Get topics data formatted for resource response
333
+ */
334
+ export async function getTopicsResourceData() {
335
+ const topics = await getTopicsMetadata();
336
+ return {
337
+ description: 'Topic categories for filtering Jamf documentation searches',
338
+ totalTopics: topics.length,
339
+ topics: topics.map(t => ({
340
+ id: t.id,
341
+ name: t.name,
342
+ source: t.source,
343
+ ...(t.articleCount !== undefined ? { articleCount: t.articleCount } : {})
344
+ })),
345
+ lastUpdated: new Date().toISOString(),
346
+ usage: 'Use topic ID (e.g., "enrollment") with jamf_docs_search tool to filter results'
347
+ };
348
+ }
349
+ //# sourceMappingURL=metadata.js.map