mcmodding-mcp 0.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +324 -0
  3. package/dist/db-versioning.d.ts +31 -0
  4. package/dist/db-versioning.d.ts.map +1 -0
  5. package/dist/db-versioning.js +206 -0
  6. package/dist/db-versioning.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +202 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/indexer/chunker.d.ts +32 -0
  12. package/dist/indexer/chunker.d.ts.map +1 -0
  13. package/dist/indexer/chunker.js +157 -0
  14. package/dist/indexer/chunker.js.map +1 -0
  15. package/dist/indexer/crawler.d.ts +28 -0
  16. package/dist/indexer/crawler.d.ts.map +1 -0
  17. package/dist/indexer/crawler.js +550 -0
  18. package/dist/indexer/crawler.js.map +1 -0
  19. package/dist/indexer/embeddings.d.ts +56 -0
  20. package/dist/indexer/embeddings.d.ts.map +1 -0
  21. package/dist/indexer/embeddings.js +200 -0
  22. package/dist/indexer/embeddings.js.map +1 -0
  23. package/dist/indexer/sitemap.d.ts +35 -0
  24. package/dist/indexer/sitemap.d.ts.map +1 -0
  25. package/dist/indexer/sitemap.js +263 -0
  26. package/dist/indexer/sitemap.js.map +1 -0
  27. package/dist/indexer/store.d.ts +237 -0
  28. package/dist/indexer/store.d.ts.map +1 -0
  29. package/dist/indexer/store.js +857 -0
  30. package/dist/indexer/store.js.map +1 -0
  31. package/dist/indexer/types.d.ts +77 -0
  32. package/dist/indexer/types.d.ts.map +1 -0
  33. package/dist/indexer/types.js +2 -0
  34. package/dist/indexer/types.js.map +1 -0
  35. package/dist/indexer/updater.d.ts +52 -0
  36. package/dist/indexer/updater.d.ts.map +1 -0
  37. package/dist/indexer/updater.js +263 -0
  38. package/dist/indexer/updater.js.map +1 -0
  39. package/dist/services/concept-service.d.ts +49 -0
  40. package/dist/services/concept-service.d.ts.map +1 -0
  41. package/dist/services/concept-service.js +554 -0
  42. package/dist/services/concept-service.js.map +1 -0
  43. package/dist/services/example-service.d.ts +52 -0
  44. package/dist/services/example-service.d.ts.map +1 -0
  45. package/dist/services/example-service.js +302 -0
  46. package/dist/services/example-service.js.map +1 -0
  47. package/dist/services/search-service.d.ts +54 -0
  48. package/dist/services/search-service.d.ts.map +1 -0
  49. package/dist/services/search-service.js +519 -0
  50. package/dist/services/search-service.js.map +1 -0
  51. package/dist/services/search-utils.d.ts +34 -0
  52. package/dist/services/search-utils.d.ts.map +1 -0
  53. package/dist/services/search-utils.js +239 -0
  54. package/dist/services/search-utils.js.map +1 -0
  55. package/dist/tools/explainConcept.d.ts +9 -0
  56. package/dist/tools/explainConcept.d.ts.map +1 -0
  57. package/dist/tools/explainConcept.js +93 -0
  58. package/dist/tools/explainConcept.js.map +1 -0
  59. package/dist/tools/getExample.d.ts +20 -0
  60. package/dist/tools/getExample.d.ts.map +1 -0
  61. package/dist/tools/getExample.js +88 -0
  62. package/dist/tools/getExample.js.map +1 -0
  63. package/dist/tools/getMinecraftVersion.d.ts +6 -0
  64. package/dist/tools/getMinecraftVersion.d.ts.map +1 -0
  65. package/dist/tools/getMinecraftVersion.js +57 -0
  66. package/dist/tools/getMinecraftVersion.js.map +1 -0
  67. package/dist/tools/searchDocs.d.ts +15 -0
  68. package/dist/tools/searchDocs.d.ts.map +1 -0
  69. package/dist/tools/searchDocs.js +144 -0
  70. package/dist/tools/searchDocs.js.map +1 -0
  71. package/package.json +111 -0
  72. package/scripts/postinstall.js +859 -0
@@ -0,0 +1,859 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MCModding-MCP Postinstall Script
5
+ * Downloads the documentation database during npm installation
6
+ * with a stunning visual CLI experience
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import crypto from 'crypto';
12
+ import https from 'https';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+
18
+ // ═══════════════════════════════════════════════════════════════════════════════
19
+ // CONFIGURATION
20
+ // ═══════════════════════════════════════════════════════════════════════════════
21
+
22
+ const CONFIG = {
23
+ repoUrl: 'https://api.github.com/repos/OGMatrix/mcmodding-mcp/releases/latest',
24
+ dataDir: path.join(__dirname, '..', 'data'),
25
+ dbFileName: 'mcmodding-docs.db',
26
+ manifestFileName: 'manifest.json',
27
+ userAgent: 'mcmodding-mcp-installer',
28
+ };
29
+
30
+ // ═══════════════════════════════════════════════════════════════════════════════
31
+ // ANSI COLORS & STYLES
32
+ // ═══════════════════════════════════════════════════════════════════════════════
33
+
34
+ const isColorSupported = process.stdout.isTTY && !process.env.NO_COLOR;
35
+
36
+ const c = {
37
+ // Reset
38
+ reset: isColorSupported ? '\x1b[0m' : '',
39
+ // Styles
40
+ bold: isColorSupported ? '\x1b[1m' : '',
41
+ dim: isColorSupported ? '\x1b[2m' : '',
42
+ italic: isColorSupported ? '\x1b[3m' : '',
43
+ underline: isColorSupported ? '\x1b[4m' : '',
44
+ // Colors
45
+ black: isColorSupported ? '\x1b[30m' : '',
46
+ red: isColorSupported ? '\x1b[31m' : '',
47
+ green: isColorSupported ? '\x1b[32m' : '',
48
+ yellow: isColorSupported ? '\x1b[33m' : '',
49
+ blue: isColorSupported ? '\x1b[34m' : '',
50
+ magenta: isColorSupported ? '\x1b[35m' : '',
51
+ cyan: isColorSupported ? '\x1b[36m' : '',
52
+ white: isColorSupported ? '\x1b[37m' : '',
53
+ // Bright colors
54
+ brightBlack: isColorSupported ? '\x1b[90m' : '',
55
+ brightRed: isColorSupported ? '\x1b[91m' : '',
56
+ brightGreen: isColorSupported ? '\x1b[92m' : '',
57
+ brightYellow: isColorSupported ? '\x1b[93m' : '',
58
+ brightBlue: isColorSupported ? '\x1b[94m' : '',
59
+ brightMagenta: isColorSupported ? '\x1b[95m' : '',
60
+ brightCyan: isColorSupported ? '\x1b[96m' : '',
61
+ brightWhite: isColorSupported ? '\x1b[97m' : '',
62
+ // Backgrounds
63
+ bgBlack: isColorSupported ? '\x1b[40m' : '',
64
+ bgRed: isColorSupported ? '\x1b[41m' : '',
65
+ bgGreen: isColorSupported ? '\x1b[42m' : '',
66
+ bgYellow: isColorSupported ? '\x1b[43m' : '',
67
+ bgBlue: isColorSupported ? '\x1b[44m' : '',
68
+ bgMagenta: isColorSupported ? '\x1b[45m' : '',
69
+ bgCyan: isColorSupported ? '\x1b[46m' : '',
70
+ bgWhite: isColorSupported ? '\x1b[47m' : '',
71
+ // Cursor
72
+ clearLine: isColorSupported ? '\x1b[2K' : '',
73
+ cursorUp: isColorSupported ? '\x1b[1A' : '',
74
+ cursorHide: isColorSupported ? '\x1b[?25l' : '',
75
+ cursorShow: isColorSupported ? '\x1b[?25h' : '',
76
+ };
77
+
78
+ // ═══════════════════════════════════════════════════════════════════════════════
79
+ // UNICODE SYMBOLS & BOX DRAWING
80
+ // ═══════════════════════════════════════════════════════════════════════════════
81
+
82
+ const sym = {
83
+ // Box drawing (double line)
84
+ topLeft: '╔',
85
+ topRight: '╗',
86
+ bottomLeft: '╚',
87
+ bottomRight: '╝',
88
+ horizontal: '═',
89
+ vertical: '║',
90
+ // Box drawing (single line)
91
+ sTopLeft: '┌',
92
+ sTopRight: '┐',
93
+ sBottomLeft: '└',
94
+ sBottomRight: '┘',
95
+ sHorizontal: '─',
96
+ sVertical: '│',
97
+ // Progress bar
98
+ barFull: '█',
99
+ barThreeQuarter: '▓',
100
+ barHalf: '▒',
101
+ barQuarter: '░',
102
+ barEmpty: '░',
103
+ // Status symbols
104
+ check: '✔',
105
+ cross: '✖',
106
+ warning: '⚠',
107
+ info: 'ℹ',
108
+ star: '★',
109
+ sparkle: '✨',
110
+ rocket: '🚀',
111
+ package: '📦',
112
+ database: '🗄️',
113
+ download: '⬇',
114
+ shield: '🛡️',
115
+ clock: '⏱',
116
+ lightning: '⚡',
117
+ cube: '◆',
118
+ arrow: '→',
119
+ arrowRight: '▶',
120
+ dot: '●',
121
+ circle: '○',
122
+ diamond: '◇',
123
+ // Minecraft themed
124
+ pickaxe: '⛏',
125
+ gear: '⚙',
126
+ book: '📖',
127
+ };
128
+
129
+ // ═══════════════════════════════════════════════════════════════════════════════
130
+ // UTILITY FUNCTIONS
131
+ // ═══════════════════════════════════════════════════════════════════════════════
132
+
133
+ function getTerminalWidth() {
134
+ return process.stdout.columns || 80;
135
+ }
136
+
137
+ function centerText(text, width) {
138
+ const cleanText = text.replace(/\x1b\[[0-9;]*m/g, '');
139
+ const totalPadding = Math.max(0, width - cleanText.length);
140
+ const leftPadding = Math.floor(totalPadding / 2);
141
+ const rightPadding = totalPadding - leftPadding;
142
+ return ' '.repeat(leftPadding) + text + ' '.repeat(rightPadding);
143
+ }
144
+
145
+ function padRight(text, width) {
146
+ const cleanText = text.replace(/\x1b\[[0-9;]*m/g, '');
147
+ const padding = Math.max(0, width - cleanText.length);
148
+ return text + ' '.repeat(padding);
149
+ }
150
+
151
+ function formatBytes(bytes) {
152
+ if (bytes === 0) return '0 B';
153
+ const k = 1024;
154
+ const sizes = ['B', 'KB', 'MB', 'GB'];
155
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
156
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
157
+ }
158
+
159
+ function formatSpeed(bytesPerSecond) {
160
+ return formatBytes(bytesPerSecond) + '/s';
161
+ }
162
+
163
+ function formatTime(seconds) {
164
+ if (seconds < 60) return `${Math.round(seconds)}s`;
165
+ const mins = Math.floor(seconds / 60);
166
+ const secs = Math.round(seconds % 60);
167
+ return `${mins}m ${secs}s`;
168
+ }
169
+
170
+ function sleep(ms) {
171
+ return new Promise((resolve) => setTimeout(resolve, ms));
172
+ }
173
+
174
+ // ═══════════════════════════════════════════════════════════════════════════════
175
+ // VISUAL COMPONENTS
176
+ // ═══════════════════════════════════════════════════════════════════════════════
177
+
178
+ function printBanner() {
179
+ const width = Math.min(getTerminalWidth(), 72);
180
+ const innerWidth = width - 2;
181
+
182
+ console.log();
183
+ console.log(
184
+ c.brightCyan + sym.topLeft + sym.horizontal.repeat(width - 2) + sym.topRight + c.reset
185
+ );
186
+
187
+ // ASCII Art Logo
188
+ const logo = [
189
+ `${c.brightGreen} __ __ ___ __ __ _ _ _ ${c.reset}`,
190
+ `${c.brightGreen} | \\/ |/ __| | \\/ | ___ __| | __| (_)_ __ __ _ ${c.reset}`,
191
+ `${c.brightGreen} | |\\/| | | | |\\/| |/ _ \\ / _\` |/ _\` | | '_ \\ / _\` |${c.reset}`,
192
+ `${c.brightGreen} | | | | |___ | | | | (_) | (_| | (_| | | | | | (_| |${c.reset}`,
193
+ `${c.brightGreen} |_| |_|\\____||_| |_|\\___/ \\__,_|\\__,_|_|_| |_|\\__, |${c.reset}`,
194
+ `${c.brightGreen} |___/ ${c.reset}`,
195
+ ];
196
+
197
+ logo.forEach((line) => {
198
+ console.log(
199
+ c.brightCyan +
200
+ sym.vertical +
201
+ c.reset +
202
+ centerText(line, innerWidth) +
203
+ c.brightCyan +
204
+ sym.vertical +
205
+ c.reset
206
+ );
207
+ });
208
+
209
+ // Subtitle
210
+ console.log(c.brightCyan + sym.vertical + ' '.repeat(innerWidth) + sym.vertical + c.reset);
211
+ const subtitle = `${c.brightMagenta}${sym.pickaxe} ${c.bold}Minecraft Modding Documentation${c.reset}${c.brightMagenta} ${sym.pickaxe}${c.reset}`;
212
+ console.log(
213
+ c.brightCyan +
214
+ sym.vertical +
215
+ c.reset +
216
+ centerText(subtitle, innerWidth) +
217
+ c.brightCyan +
218
+ sym.vertical +
219
+ c.reset
220
+ );
221
+
222
+ const subtitle2 = `${c.dim}Model Context Protocol Server${c.reset}`;
223
+ console.log(
224
+ c.brightCyan +
225
+ sym.vertical +
226
+ c.reset +
227
+ centerText(subtitle2, innerWidth) +
228
+ c.brightCyan +
229
+ sym.vertical +
230
+ c.reset
231
+ );
232
+
233
+ console.log(
234
+ c.brightCyan + sym.bottomLeft + sym.horizontal.repeat(width - 2) + sym.bottomRight + c.reset
235
+ );
236
+ console.log();
237
+ }
238
+
239
+ function printSectionHeader(title, icon = sym.arrowRight) {
240
+ const width = Math.min(getTerminalWidth(), 72);
241
+ console.log();
242
+ console.log(
243
+ c.brightBlue +
244
+ sym.sTopLeft +
245
+ sym.sHorizontal.repeat(2) +
246
+ c.reset +
247
+ ` ${c.bold}${icon} ${title}${c.reset} ` +
248
+ c.brightBlue +
249
+ sym.sHorizontal.repeat(Math.max(0, width - title.length - 10)) +
250
+ sym.sTopRight +
251
+ c.reset
252
+ );
253
+ }
254
+
255
+ function printSectionFooter() {
256
+ const width = Math.min(getTerminalWidth(), 72);
257
+ console.log(
258
+ c.brightBlue + sym.sBottomLeft + sym.sHorizontal.repeat(width - 2) + sym.sBottomRight + c.reset
259
+ );
260
+ }
261
+
262
+ function createProgressBar(progress, width = 40, showGradient = true) {
263
+ const filled = Math.round(progress * width);
264
+ const empty = width - filled;
265
+
266
+ let bar = '';
267
+ if (showGradient && isColorSupported) {
268
+ // Gradient effect from green to cyan
269
+ for (let i = 0; i < filled; i++) {
270
+ const ratio = i / width;
271
+ if (ratio < 0.33) bar += c.green + sym.barFull;
272
+ else if (ratio < 0.66) bar += c.brightGreen + sym.barFull;
273
+ else bar += c.brightCyan + sym.barFull;
274
+ }
275
+ bar += c.reset;
276
+ } else {
277
+ bar = c.brightGreen + sym.barFull.repeat(filled) + c.reset;
278
+ }
279
+
280
+ bar += c.dim + sym.barEmpty.repeat(empty) + c.reset;
281
+ return bar;
282
+ }
283
+
284
+ class ProgressDisplay {
285
+ constructor() {
286
+ this.lines = 0;
287
+ this.startTime = Date.now();
288
+ this.lastUpdate = 0;
289
+ this.speeds = [];
290
+ }
291
+
292
+ clear() {
293
+ if (isColorSupported) {
294
+ for (let i = 0; i < this.lines; i++) {
295
+ process.stdout.write(c.cursorUp + c.clearLine);
296
+ }
297
+ }
298
+ this.lines = 0;
299
+ }
300
+
301
+ calculateSpeed(downloaded, elapsed) {
302
+ if (elapsed === 0) return 0;
303
+ const currentSpeed = downloaded / elapsed;
304
+ this.speeds.push(currentSpeed);
305
+ if (this.speeds.length > 5) this.speeds.shift();
306
+ return this.speeds.reduce((a, b) => a + b, 0) / this.speeds.length;
307
+ }
308
+
309
+ update(downloaded, total, phase = 'download') {
310
+ const now = Date.now();
311
+ if (now - this.lastUpdate < 50) return; // Throttle updates
312
+ this.lastUpdate = now;
313
+
314
+ this.clear();
315
+
316
+ const width = Math.min(getTerminalWidth(), 72);
317
+ const barWidth = Math.max(20, width - 35);
318
+ const progress = total > 0 ? downloaded / total : 0;
319
+ const percent = Math.round(progress * 100);
320
+ const elapsed = (now - this.startTime) / 1000;
321
+ const speed = this.calculateSpeed(downloaded, elapsed);
322
+ const eta = speed > 0 ? (total - downloaded) / speed : 0;
323
+
324
+ const lines = [];
325
+
326
+ // Status line with icon
327
+ const statusIcon = phase === 'download' ? sym.download : sym.shield;
328
+ const statusText = phase === 'download' ? 'Downloading database...' : 'Verifying integrity...';
329
+ lines.push(` ${c.brightYellow}${statusIcon}${c.reset} ${c.bold}${statusText}${c.reset}`);
330
+
331
+ // Progress bar
332
+ const bar = createProgressBar(progress, barWidth);
333
+ const percentStr = `${percent}%`.padStart(4);
334
+ lines.push(
335
+ ` ${c.dim}[${c.reset}${bar}${c.dim}]${c.reset} ${c.brightWhite}${percentStr}${c.reset}`
336
+ );
337
+
338
+ // Stats line
339
+ const downloadedStr = formatBytes(downloaded);
340
+ const totalStr = formatBytes(total);
341
+ const speedStr = phase === 'download' ? formatSpeed(speed) : '';
342
+ const etaStr = phase === 'download' && eta > 0 ? `ETA: ${formatTime(eta)}` : '';
343
+
344
+ let statsLine = ` ${c.dim}${sym.cube}${c.reset} ${c.cyan}${downloadedStr}${c.reset} ${c.dim}/${c.reset} ${c.cyan}${totalStr}${c.reset}`;
345
+ if (speedStr)
346
+ statsLine += ` ${c.dim}${sym.lightning}${c.reset} ${c.brightMagenta}${speedStr}${c.reset}`;
347
+ if (etaStr) statsLine += ` ${c.dim}${sym.clock}${c.reset} ${c.yellow}${etaStr}${c.reset}`;
348
+
349
+ lines.push(statsLine);
350
+
351
+ // Print all lines
352
+ lines.forEach((line) => {
353
+ console.log(line);
354
+ this.lines++;
355
+ });
356
+ }
357
+
358
+ finish(success = true, message = '') {
359
+ this.clear();
360
+ const icon = success ? c.brightGreen + sym.check : c.brightRed + sym.cross;
361
+ const color = success ? c.brightGreen : c.brightRed;
362
+ console.log(` ${icon}${c.reset} ${color}${message}${c.reset}`);
363
+ this.lines = 1;
364
+ }
365
+ }
366
+
367
+ function printStepIndicator(step, total, description, status = 'pending') {
368
+ const icons = {
369
+ pending: c.dim + sym.circle + c.reset,
370
+ active: c.brightYellow + sym.dot + c.reset,
371
+ done: c.brightGreen + sym.check + c.reset,
372
+ error: c.brightRed + sym.cross + c.reset,
373
+ };
374
+
375
+ const colors = {
376
+ pending: c.dim,
377
+ active: c.brightWhite,
378
+ done: c.green,
379
+ error: c.red,
380
+ };
381
+
382
+ console.log(
383
+ ` ${icons[status]} ${colors[status]}Step ${step}/${total}: ${description}${c.reset}`
384
+ );
385
+ }
386
+
387
+ function printWelcomeScreen() {
388
+ const width = Math.min(getTerminalWidth(), 72);
389
+ const innerWidth = width - 4;
390
+
391
+ console.log();
392
+ console.log(
393
+ c.brightGreen + ' ' + sym.sparkle + ' Installation Complete! ' + sym.sparkle + c.reset
394
+ );
395
+ console.log();
396
+
397
+ // Welcome box
398
+ console.log(
399
+ c.green + ' ' + sym.topLeft + sym.horizontal.repeat(width - 4) + sym.topRight + c.reset
400
+ );
401
+
402
+ const welcomeLines = [
403
+ '',
404
+ `${c.bold}${c.brightWhite}Welcome to MCModding-MCP!${c.reset}`,
405
+ '',
406
+ `${c.dim}Your AI assistant now has access to comprehensive${c.reset}`,
407
+ `${c.dim}Minecraft modding documentation for:${c.reset}`,
408
+ '',
409
+ ` ${c.brightGreen}${sym.check}${c.reset} ${c.cyan}Fabric${c.reset} - Lightweight modding toolchain`,
410
+ ` ${c.brightGreen}${sym.check}${c.reset} ${c.magenta}NeoForge${c.reset} - Community-driven mod loader`,
411
+ '',
412
+ ];
413
+
414
+ welcomeLines.forEach((line) => {
415
+ const paddedLine = centerText(line, innerWidth);
416
+ console.log(
417
+ c.green + ' ' + sym.vertical + c.reset + paddedLine + c.green + sym.vertical + c.reset
418
+ );
419
+ });
420
+
421
+ console.log(
422
+ c.green + ' ' + sym.bottomLeft + sym.horizontal.repeat(width - 4) + sym.bottomRight + c.reset
423
+ );
424
+ console.log();
425
+
426
+ // Quick start section
427
+ console.log(
428
+ c.brightBlue +
429
+ ' ' +
430
+ sym.sTopLeft +
431
+ sym.sHorizontal.repeat(2) +
432
+ c.reset +
433
+ ` ${c.bold}${sym.rocket} Quick Start${c.reset} ` +
434
+ c.brightBlue +
435
+ sym.sHorizontal.repeat(width - 20) +
436
+ sym.sTopRight +
437
+ c.reset
438
+ );
439
+ console.log(c.brightBlue + ' ' + sym.sVertical + c.reset);
440
+
441
+ const quickStart = [
442
+ [`${c.yellow}Configure Claude Desktop:${c.reset}`, ''],
443
+ [
444
+ `${c.dim}Add to your ${c.reset}${c.cyan}claude_desktop_config.json${c.reset}${c.dim}:${c.reset}`,
445
+ '',
446
+ ],
447
+ ['', ''],
448
+ [` ${c.brightBlack}{${c.reset}`, ''],
449
+ [` ${c.brightBlue}"mcpServers"${c.reset}: {`, ''],
450
+ [` ${c.brightGreen}"mcmodding"${c.reset}: {`, ''],
451
+ [` ${c.brightMagenta}"command"${c.reset}: ${c.yellow}"npx"${c.reset},`, ''],
452
+ [` ${c.brightMagenta}"args"${c.reset}: [${c.yellow}"mcmodding-mcp"${c.reset}]`, ''],
453
+ [` }`, ''],
454
+ [` }`, ''],
455
+ [` ${c.brightBlack}}${c.reset}`, ''],
456
+ ];
457
+
458
+ quickStart.forEach(([line]) => {
459
+ console.log(c.brightBlue + ' ' + sym.sVertical + c.reset + ' ' + line);
460
+ });
461
+
462
+ console.log(c.brightBlue + ' ' + sym.sVertical + c.reset);
463
+ console.log(
464
+ c.brightBlue +
465
+ ' ' +
466
+ sym.sBottomLeft +
467
+ sym.sHorizontal.repeat(width - 4) +
468
+ sym.sBottomRight +
469
+ c.reset
470
+ );
471
+ console.log();
472
+
473
+ // Available tools section
474
+ console.log(
475
+ c.brightMagenta +
476
+ ' ' +
477
+ sym.sTopLeft +
478
+ sym.sHorizontal.repeat(2) +
479
+ c.reset +
480
+ ` ${c.bold}${sym.gear} Available Tools${c.reset} ` +
481
+ c.brightMagenta +
482
+ sym.sHorizontal.repeat(width - 24) +
483
+ sym.sTopRight +
484
+ c.reset
485
+ );
486
+ console.log(c.brightMagenta + ' ' + sym.sVertical + c.reset);
487
+
488
+ const tools = [
489
+ [`${c.brightCyan}search_docs${c.reset}`, 'Search documentation with semantic understanding'],
490
+ [`${c.brightCyan}get_document${c.reset}`, 'Retrieve full documentation pages'],
491
+ [`${c.brightCyan}list_categories${c.reset}`, 'Browse available documentation categories'],
492
+ [`${c.brightCyan}get_code_examples${c.reset}`, 'Find relevant code snippets and examples'],
493
+ ];
494
+
495
+ tools.forEach(([name, desc]) => {
496
+ console.log(c.brightMagenta + ' ' + sym.sVertical + c.reset + ` ${sym.arrowRight} ${name}`);
497
+ console.log(c.brightMagenta + ' ' + sym.sVertical + c.reset + ` ${c.dim}${desc}${c.reset}`);
498
+ });
499
+
500
+ console.log(c.brightMagenta + ' ' + sym.sVertical + c.reset);
501
+ console.log(
502
+ c.brightMagenta +
503
+ ' ' +
504
+ sym.sBottomLeft +
505
+ sym.sHorizontal.repeat(width - 4) +
506
+ sym.sBottomRight +
507
+ c.reset
508
+ );
509
+ console.log();
510
+
511
+ // Footer links
512
+ console.log(c.dim + ' ' + sym.sHorizontal.repeat(width - 4) + c.reset);
513
+ console.log();
514
+ console.log(
515
+ ` ${c.dim}${sym.book}${c.reset} ${c.brightBlue}GitHub:${c.reset} ${c.underline}https://github.com/OGMatrix/mcmodding-mcp${c.reset}`
516
+ );
517
+ console.log(
518
+ ` ${c.dim}${sym.warning}${c.reset} ${c.brightBlue}Issues:${c.reset} ${c.underline}https://github.com/OGMatrix/mcmodding-mcp/issues${c.reset}`
519
+ );
520
+ console.log();
521
+ console.log(c.dim + ' ' + sym.sHorizontal.repeat(width - 4) + c.reset);
522
+ console.log();
523
+ console.log(
524
+ ` ${c.brightGreen}${sym.sparkle}${c.reset} ${c.italic}Happy modding!${c.reset} ${c.brightGreen}${sym.sparkle}${c.reset}`
525
+ );
526
+ console.log();
527
+ }
528
+
529
+ // ═══════════════════════════════════════════════════════════════════════════════
530
+ // NETWORK FUNCTIONS
531
+ // ═══════════════════════════════════════════════════════════════════════════════
532
+
533
+ function httpsGet(url, options = {}) {
534
+ return new Promise((resolve, reject) => {
535
+ const req = https.get(
536
+ url,
537
+ {
538
+ headers: {
539
+ 'User-Agent': CONFIG.userAgent,
540
+ Accept: 'application/vnd.github.v3+json',
541
+ ...options.headers,
542
+ },
543
+ },
544
+ (res) => {
545
+ // Handle redirects
546
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
547
+ httpsGet(res.headers.location, options).then(resolve).catch(reject);
548
+ return;
549
+ }
550
+
551
+ if (res.statusCode !== 200) {
552
+ reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
553
+ return;
554
+ }
555
+
556
+ if (options.stream) {
557
+ resolve(res);
558
+ return;
559
+ }
560
+
561
+ let data = '';
562
+ res.on('data', (chunk) => (data += chunk));
563
+ res.on('end', () => resolve(data));
564
+ res.on('error', reject);
565
+ }
566
+ );
567
+
568
+ req.on('error', reject);
569
+ req.setTimeout(30000, () => {
570
+ req.destroy();
571
+ reject(new Error('Request timeout'));
572
+ });
573
+ });
574
+ }
575
+
576
+ async function downloadWithProgress(url, destPath, onProgress) {
577
+ return new Promise((resolve, reject) => {
578
+ const file = fs.createWriteStream(destPath);
579
+
580
+ const makeRequest = (requestUrl) => {
581
+ const urlObj = new URL(requestUrl);
582
+ const options = {
583
+ hostname: urlObj.hostname,
584
+ path: urlObj.pathname + urlObj.search,
585
+ headers: {
586
+ 'User-Agent': CONFIG.userAgent,
587
+ },
588
+ };
589
+
590
+ https
591
+ .get(options, (res) => {
592
+ // Handle redirects
593
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
594
+ makeRequest(res.headers.location);
595
+ return;
596
+ }
597
+
598
+ if (res.statusCode !== 200) {
599
+ file.close();
600
+ fs.unlinkSync(destPath);
601
+ reject(new Error(`HTTP ${res.statusCode}`));
602
+ return;
603
+ }
604
+
605
+ const total = parseInt(res.headers['content-length'], 10) || 0;
606
+ let downloaded = 0;
607
+
608
+ res.on('data', (chunk) => {
609
+ downloaded += chunk.length;
610
+ file.write(chunk);
611
+ if (onProgress) onProgress(downloaded, total);
612
+ });
613
+
614
+ res.on('end', () => {
615
+ file.end();
616
+ resolve({ downloaded, total });
617
+ });
618
+
619
+ res.on('error', (err) => {
620
+ file.close();
621
+ fs.unlinkSync(destPath);
622
+ reject(err);
623
+ });
624
+ })
625
+ .on('error', (err) => {
626
+ file.close();
627
+ if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
628
+ reject(err);
629
+ });
630
+ };
631
+
632
+ makeRequest(url);
633
+ });
634
+ }
635
+
636
+ // ═══════════════════════════════════════════════════════════════════════════════
637
+ // MAIN INSTALLATION LOGIC
638
+ // ═══════════════════════════════════════════════════════════════════════════════
639
+
640
+ async function fetchReleaseInfo() {
641
+ const response = await httpsGet(CONFIG.repoUrl);
642
+ return JSON.parse(response);
643
+ }
644
+
645
+ async function fetchManifest(manifestUrl) {
646
+ const response = await httpsGet(manifestUrl);
647
+ return JSON.parse(response);
648
+ }
649
+
650
+ async function calculateFileHash(filePath) {
651
+ return new Promise((resolve, reject) => {
652
+ const hash = crypto.createHash('sha256');
653
+ const stream = fs.createReadStream(filePath);
654
+ stream.on('data', (data) => hash.update(data));
655
+ stream.on('end', () => resolve(hash.digest('hex')));
656
+ stream.on('error', reject);
657
+ });
658
+ }
659
+
660
+ async function verifyWithProgress(filePath, expectedHash, progress) {
661
+ return new Promise((resolve, reject) => {
662
+ const hash = crypto.createHash('sha256');
663
+ const stats = fs.statSync(filePath);
664
+ const total = stats.size;
665
+ let processed = 0;
666
+
667
+ const stream = fs.createReadStream(filePath);
668
+
669
+ stream.on('data', (chunk) => {
670
+ hash.update(chunk);
671
+ processed += chunk.length;
672
+ progress.update(processed, total, 'verify');
673
+ });
674
+
675
+ stream.on('end', () => {
676
+ const actualHash = hash.digest('hex');
677
+ resolve(actualHash === expectedHash);
678
+ });
679
+
680
+ stream.on('error', reject);
681
+ });
682
+ }
683
+
684
+ async function main() {
685
+ // Hide cursor during installation
686
+ if (isColorSupported) process.stdout.write(c.cursorHide);
687
+
688
+ // Ensure cursor is shown on exit
689
+ const cleanup = () => {
690
+ if (isColorSupported) process.stdout.write(c.cursorShow);
691
+ };
692
+ process.on('exit', cleanup);
693
+ process.on('SIGINT', () => {
694
+ cleanup();
695
+ process.exit(1);
696
+ });
697
+ process.on('SIGTERM', () => {
698
+ cleanup();
699
+ process.exit(1);
700
+ });
701
+
702
+ try {
703
+ printBanner();
704
+
705
+ printSectionHeader('Installation Progress', sym.package);
706
+ console.log();
707
+
708
+ // Step 1: Check for existing database
709
+ printStepIndicator(1, 4, 'Checking existing installation...', 'active');
710
+ await sleep(300);
711
+
712
+ const dbPath = path.join(CONFIG.dataDir, CONFIG.dbFileName);
713
+ const manifestPath = path.join(CONFIG.dataDir, CONFIG.manifestFileName);
714
+
715
+ if (fs.existsSync(dbPath) && fs.existsSync(manifestPath)) {
716
+ process.stdout.write(c.cursorUp + c.clearLine);
717
+ printStepIndicator(1, 4, 'Existing database found - skipping download', 'done');
718
+ console.log();
719
+ printSectionFooter();
720
+ printWelcomeScreen();
721
+ return;
722
+ }
723
+
724
+ process.stdout.write(c.cursorUp + c.clearLine);
725
+ printStepIndicator(1, 4, 'No existing database - will download', 'done');
726
+
727
+ // Step 2: Fetch release information
728
+ printStepIndicator(2, 4, 'Fetching latest release information...', 'active');
729
+
730
+ let release, manifest;
731
+ try {
732
+ release = await fetchReleaseInfo();
733
+ const manifestAsset = release.assets.find((a) => a.name === 'db-manifest.json');
734
+ if (!manifestAsset) throw new Error('No manifest found in release');
735
+
736
+ manifest = await fetchManifest(manifestAsset.browser_download_url);
737
+ } catch (error) {
738
+ process.stdout.write(c.cursorUp + c.clearLine);
739
+ printStepIndicator(2, 4, `Failed to fetch release info: ${error.message}`, 'error');
740
+ console.log();
741
+ console.log(
742
+ c.yellow + ` ${sym.warning} The database will be downloaded on first use.${c.reset}`
743
+ );
744
+ printSectionFooter();
745
+ printWelcomeScreen();
746
+ return;
747
+ }
748
+
749
+ process.stdout.write(c.cursorUp + c.clearLine);
750
+ printStepIndicator(
751
+ 2,
752
+ 4,
753
+ `Found database v${manifest.version} (${formatBytes(manifest.size)})`,
754
+ 'done'
755
+ );
756
+
757
+ // Step 3: Download database
758
+ printStepIndicator(3, 4, 'Downloading database...', 'active');
759
+ console.log();
760
+
761
+ // Ensure data directory exists
762
+ if (!fs.existsSync(CONFIG.dataDir)) {
763
+ fs.mkdirSync(CONFIG.dataDir, { recursive: true });
764
+ }
765
+
766
+ const tempPath = dbPath + '.tmp';
767
+ const progress = new ProgressDisplay();
768
+
769
+ try {
770
+ await downloadWithProgress(manifest.downloadUrl, tempPath, (downloaded, total) => {
771
+ progress.update(downloaded, total || manifest.size, 'download');
772
+ });
773
+ progress.finish(true, 'Download complete!');
774
+ } catch (error) {
775
+ progress.finish(false, `Download failed: ${error.message}`);
776
+ if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
777
+ console.log();
778
+ console.log(
779
+ c.yellow + ` ${sym.warning} The database will be downloaded on first use.${c.reset}`
780
+ );
781
+ printSectionFooter();
782
+ printWelcomeScreen();
783
+ return;
784
+ }
785
+
786
+ // Clear the step indicator and update
787
+ process.stdout.write(c.cursorUp + c.cursorUp + c.clearLine);
788
+ printStepIndicator(3, 4, 'Database downloaded successfully', 'done');
789
+ process.stdout.write(c.cursorUp + c.clearLine);
790
+ process.stdout.write('\n'); // Move past the progress line
791
+
792
+ // Step 4: Verify integrity
793
+ printStepIndicator(4, 4, 'Verifying file integrity...', 'active');
794
+ console.log();
795
+
796
+ const verifyProgress = new ProgressDisplay();
797
+
798
+ try {
799
+ const isValid = await verifyWithProgress(tempPath, manifest.hash, verifyProgress);
800
+
801
+ if (!isValid) {
802
+ verifyProgress.finish(false, 'Hash verification failed!');
803
+ if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
804
+ console.log();
805
+ console.log(
806
+ c.yellow + ` ${sym.warning} The database will be downloaded on first use.${c.reset}`
807
+ );
808
+ printSectionFooter();
809
+ printWelcomeScreen();
810
+ return;
811
+ }
812
+
813
+ verifyProgress.finish(true, 'Integrity verified!');
814
+
815
+ // Move temp file to final location
816
+ fs.renameSync(tempPath, dbPath);
817
+
818
+ // Save manifest
819
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
820
+ } catch (error) {
821
+ verifyProgress.finish(false, `Verification failed: ${error.message}`);
822
+ if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
823
+ console.log();
824
+ console.log(
825
+ c.yellow + ` ${sym.warning} The database will be downloaded on first use.${c.reset}`
826
+ );
827
+ printSectionFooter();
828
+ printWelcomeScreen();
829
+ return;
830
+ }
831
+
832
+ // Clear and update final step
833
+ process.stdout.write(c.cursorUp + c.cursorUp + c.clearLine);
834
+ printStepIndicator(4, 4, 'Database verified and installed', 'done');
835
+ console.log();
836
+
837
+ printSectionFooter();
838
+
839
+ // Show welcome screen
840
+ printWelcomeScreen();
841
+ } catch (error) {
842
+ console.error();
843
+ console.error(c.brightRed + ` ${sym.cross} Installation error: ${error.message}${c.reset}`);
844
+ console.error();
845
+ console.log(
846
+ c.yellow + ` ${sym.warning} The database will be downloaded on first use.${c.reset}`
847
+ );
848
+ console.log();
849
+ printWelcomeScreen();
850
+ } finally {
851
+ cleanup();
852
+ }
853
+ }
854
+
855
+ // Run the installer
856
+ main().catch((error) => {
857
+ console.error('Fatal error:', error);
858
+ process.exit(0); // Don't fail npm install
859
+ });