@sknoble/slvsx-mcp-server 0.1.1 → 0.2.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.
- package/mcp-server.js +96 -76
- package/package.json +4 -2
- package/scripts/postinstall.js +202 -0
package/mcp-server.js
CHANGED
|
@@ -23,30 +23,38 @@ import { fileURLToPath } from 'url';
|
|
|
23
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
24
|
const __dirname = path.dirname(__filename);
|
|
25
25
|
|
|
26
|
-
// Find slvsx binary - check env var, PATH, then local build
|
|
26
|
+
// Find slvsx binary - check env var, bundled, PATH, then local build
|
|
27
27
|
function findSlvsxBinary() {
|
|
28
|
+
const ext = process.platform === 'win32' ? '.exe' : '';
|
|
29
|
+
|
|
28
30
|
// 1. Check explicit env var
|
|
29
31
|
if (process.env.SLVSX_BINARY) {
|
|
30
32
|
return process.env.SLVSX_BINARY;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
// 2. Check
|
|
35
|
+
// 2. Check bundled binary (installed via postinstall)
|
|
36
|
+
const bundled = path.join(__dirname, 'bin', `slvsx${ext}`);
|
|
37
|
+
if (fs.existsSync(bundled)) {
|
|
38
|
+
return bundled;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 3. Check if slvsx is in PATH
|
|
34
42
|
try {
|
|
35
43
|
const which = execSync('which slvsx 2>/dev/null || where slvsx 2>nul', { encoding: 'utf-8' }).trim();
|
|
36
|
-
|
|
44
|
+
// Handle Windows CRLF line endings by trimming each line
|
|
45
|
+
if (which) return which.split('\n')[0].trim();
|
|
37
46
|
} catch (e) {
|
|
38
47
|
// Not in PATH
|
|
39
48
|
}
|
|
40
49
|
|
|
41
|
-
//
|
|
42
|
-
const localBuild =
|
|
50
|
+
// 4. Check local build
|
|
51
|
+
const localBuild = `./target/release/slvsx${ext}`;
|
|
43
52
|
if (fs.existsSync(localBuild)) {
|
|
44
53
|
return localBuild;
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
//
|
|
48
|
-
const
|
|
49
|
-
const devBuild = path.join(scriptDir, 'target/release/slvsx');
|
|
56
|
+
// 5. Check relative to this script (for development)
|
|
57
|
+
const devBuild = path.join(__dirname, 'target/release', `slvsx${ext}`);
|
|
50
58
|
if (fs.existsSync(devBuild)) {
|
|
51
59
|
return devBuild;
|
|
52
60
|
}
|
|
@@ -433,49 +441,49 @@ class SlvsxServer {
|
|
|
433
441
|
schema: 'slvs-json/1',
|
|
434
442
|
units: 'mm',
|
|
435
443
|
entities: [
|
|
436
|
-
{
|
|
437
|
-
{
|
|
438
|
-
{
|
|
444
|
+
{ type: 'point', id: 'p1', at: [0, 0, 0] },
|
|
445
|
+
{ type: 'point', id: 'p2', at: [100, 0, 0] },
|
|
446
|
+
{ type: 'point', id: 'p3', at: [50, 86.6, 0] }
|
|
439
447
|
],
|
|
440
448
|
constraints: [
|
|
441
|
-
{ type: '
|
|
442
|
-
{ type: '
|
|
443
|
-
{ type: '
|
|
444
|
-
{ type: '
|
|
449
|
+
{ type: 'fixed', entity: 'p1' },
|
|
450
|
+
{ type: 'distance', between: ['p1', 'p2'], value: 100 },
|
|
451
|
+
{ type: 'distance', between: ['p2', 'p3'], value: 100 },
|
|
452
|
+
{ type: 'distance', between: ['p3', 'p1'], value: 100 }
|
|
445
453
|
]
|
|
446
454
|
},
|
|
447
455
|
square: {
|
|
448
456
|
schema: 'slvs-json/1',
|
|
449
457
|
units: 'mm',
|
|
450
458
|
entities: [
|
|
451
|
-
{
|
|
452
|
-
{
|
|
453
|
-
{
|
|
454
|
-
{
|
|
455
|
-
{
|
|
456
|
-
{
|
|
457
|
-
{
|
|
458
|
-
{
|
|
459
|
+
{ type: 'point', id: 'p1', at: [0, 0, 0] },
|
|
460
|
+
{ type: 'point', id: 'p2', at: [100, 0, 0] },
|
|
461
|
+
{ type: 'point', id: 'p3', at: [100, 100, 0] },
|
|
462
|
+
{ type: 'point', id: 'p4', at: [0, 100, 0] },
|
|
463
|
+
{ type: 'line', id: 'l1', p1: 'p1', p2: 'p2' },
|
|
464
|
+
{ type: 'line', id: 'l2', p1: 'p2', p2: 'p3' },
|
|
465
|
+
{ type: 'line', id: 'l3', p1: 'p3', p2: 'p4' },
|
|
466
|
+
{ type: 'line', id: 'l4', p1: 'p4', p2: 'p1' }
|
|
459
467
|
],
|
|
460
468
|
constraints: [
|
|
461
|
-
{ type: '
|
|
462
|
-
{ type: '
|
|
463
|
-
{ type: '
|
|
464
|
-
{ type: '
|
|
465
|
-
{ type: '
|
|
466
|
-
{ type: '
|
|
469
|
+
{ type: 'fixed', entity: 'p1' },
|
|
470
|
+
{ type: 'fixed', entity: 'p2' },
|
|
471
|
+
{ type: 'perpendicular', a: 'l1', b: 'l2' },
|
|
472
|
+
{ type: 'perpendicular', a: 'l2', b: 'l3' },
|
|
473
|
+
{ type: 'perpendicular', a: 'l3', b: 'l4' },
|
|
474
|
+
{ type: 'equal_length', entities: ['l1', 'l2'] }
|
|
467
475
|
]
|
|
468
476
|
},
|
|
469
477
|
circle: {
|
|
470
478
|
schema: 'slvs-json/1',
|
|
471
479
|
units: 'mm',
|
|
472
480
|
entities: [
|
|
473
|
-
{
|
|
474
|
-
{
|
|
481
|
+
{ type: 'point', id: 'center', at: [50, 50, 0] },
|
|
482
|
+
{ type: 'circle', id: 'c1', center: [50, 50, 0], diameter: 60 }
|
|
475
483
|
],
|
|
476
484
|
constraints: [
|
|
477
|
-
{ type: '
|
|
478
|
-
{ type: '
|
|
485
|
+
{ type: 'fixed', entity: 'center' },
|
|
486
|
+
{ type: 'diameter', circle: 'c1', value: 60 }
|
|
479
487
|
]
|
|
480
488
|
},
|
|
481
489
|
linkage: {
|
|
@@ -485,21 +493,20 @@ class SlvsxServer {
|
|
|
485
493
|
input_angle: 45
|
|
486
494
|
},
|
|
487
495
|
entities: [
|
|
488
|
-
{
|
|
489
|
-
{
|
|
490
|
-
{
|
|
491
|
-
{
|
|
492
|
-
{
|
|
493
|
-
{
|
|
494
|
-
{
|
|
496
|
+
{ type: 'point', id: 'ground1', at: [0, 0, 0] },
|
|
497
|
+
{ type: 'point', id: 'ground2', at: [100, 0, 0] },
|
|
498
|
+
{ type: 'point', id: 'joint1', at: [30, 30, 0] },
|
|
499
|
+
{ type: 'point', id: 'joint2', at: [70, 40, 0] },
|
|
500
|
+
{ type: 'line', id: 'link1', p1: 'ground1', p2: 'joint1' },
|
|
501
|
+
{ type: 'line', id: 'link2', p1: 'joint1', p2: 'joint2' },
|
|
502
|
+
{ type: 'line', id: 'link3', p1: 'joint2', p2: 'ground2' }
|
|
495
503
|
],
|
|
496
504
|
constraints: [
|
|
497
|
-
{ type: '
|
|
498
|
-
{ type: '
|
|
499
|
-
{ type: '
|
|
500
|
-
{ type: '
|
|
501
|
-
{ type: '
|
|
502
|
-
{ type: 'Angle', entities: ['link1'], angle: '$input_angle' }
|
|
505
|
+
{ type: 'fixed', entity: 'ground1' },
|
|
506
|
+
{ type: 'fixed', entity: 'ground2' },
|
|
507
|
+
{ type: 'distance', between: ['ground1', 'joint1'], value: 40 },
|
|
508
|
+
{ type: 'distance', between: ['joint1', 'joint2'], value: 50 },
|
|
509
|
+
{ type: 'distance', between: ['joint2', 'ground2'], value: 35 }
|
|
503
510
|
]
|
|
504
511
|
},
|
|
505
512
|
parametric: {
|
|
@@ -508,48 +515,61 @@ class SlvsxServer {
|
|
|
508
515
|
parameters: {
|
|
509
516
|
width: 150,
|
|
510
517
|
height: 100,
|
|
511
|
-
|
|
518
|
+
hole_diameter: 20
|
|
512
519
|
},
|
|
513
520
|
entities: [
|
|
514
|
-
{
|
|
515
|
-
{
|
|
516
|
-
{
|
|
517
|
-
{
|
|
518
|
-
{
|
|
519
|
-
{
|
|
521
|
+
{ type: 'point', id: 'p1', at: [0, 0, 0] },
|
|
522
|
+
{ type: 'point', id: 'p2', at: ['$width', 0, 0] },
|
|
523
|
+
{ type: 'point', id: 'p3', at: ['$width', '$height', 0] },
|
|
524
|
+
{ type: 'point', id: 'p4', at: [0, '$height', 0] },
|
|
525
|
+
{ type: 'line', id: 'bottom', p1: 'p1', p2: 'p2' },
|
|
526
|
+
{ type: 'line', id: 'right', p1: 'p2', p2: 'p3' },
|
|
527
|
+
{ type: 'line', id: 'top', p1: 'p3', p2: 'p4' },
|
|
528
|
+
{ type: 'line', id: 'left', p1: 'p4', p2: 'p1' },
|
|
529
|
+
{ type: 'circle', id: 'hole', center: [75, 50, 0], diameter: '$hole_diameter' }
|
|
520
530
|
],
|
|
521
531
|
constraints: [
|
|
522
|
-
{ type: '
|
|
523
|
-
{ type: '
|
|
524
|
-
{ type: '
|
|
525
|
-
{ type: '
|
|
526
|
-
{ type: '
|
|
527
|
-
{ type: '
|
|
532
|
+
{ type: 'fixed', entity: 'p1' },
|
|
533
|
+
{ type: 'distance', between: ['p1', 'p2'], value: '$width' },
|
|
534
|
+
{ type: 'distance', between: ['p1', 'p4'], value: '$height' },
|
|
535
|
+
{ type: 'perpendicular', a: 'bottom', b: 'right' },
|
|
536
|
+
{ type: 'perpendicular', a: 'right', b: 'top' },
|
|
537
|
+
{ type: 'diameter', circle: 'hole', value: '$hole_diameter' }
|
|
528
538
|
]
|
|
529
539
|
},
|
|
530
540
|
'3d': {
|
|
531
541
|
schema: 'slvs-json/1',
|
|
532
542
|
units: 'mm',
|
|
533
543
|
entities: [
|
|
534
|
-
{
|
|
535
|
-
{
|
|
536
|
-
{
|
|
537
|
-
{
|
|
538
|
-
{
|
|
539
|
-
{
|
|
540
|
-
{
|
|
541
|
-
{
|
|
544
|
+
{ type: 'point', id: 'p1', at: [0, 0, 0] },
|
|
545
|
+
{ type: 'point', id: 'p2', at: [100, 0, 0] },
|
|
546
|
+
{ type: 'point', id: 'p3', at: [100, 100, 0] },
|
|
547
|
+
{ type: 'point', id: 'p4', at: [0, 100, 0] },
|
|
548
|
+
{ type: 'point', id: 'p5', at: [0, 0, 50] },
|
|
549
|
+
{ type: 'point', id: 'p6', at: [100, 0, 50] },
|
|
550
|
+
{ type: 'point', id: 'p7', at: [100, 100, 50] },
|
|
551
|
+
{ type: 'point', id: 'p8', at: [0, 100, 50] },
|
|
552
|
+
{ type: 'line', id: 'base1', p1: 'p1', p2: 'p2' },
|
|
553
|
+
{ type: 'line', id: 'base2', p1: 'p2', p2: 'p3' },
|
|
554
|
+
{ type: 'line', id: 'base3', p1: 'p3', p2: 'p4' },
|
|
555
|
+
{ type: 'line', id: 'base4', p1: 'p4', p2: 'p1' },
|
|
556
|
+
{ type: 'line', id: 'top1', p1: 'p5', p2: 'p6' },
|
|
557
|
+
{ type: 'line', id: 'top2', p1: 'p6', p2: 'p7' },
|
|
558
|
+
{ type: 'line', id: 'top3', p1: 'p7', p2: 'p8' },
|
|
559
|
+
{ type: 'line', id: 'top4', p1: 'p8', p2: 'p5' },
|
|
560
|
+
{ type: 'line', id: 'vert1', p1: 'p1', p2: 'p5' },
|
|
561
|
+
{ type: 'line', id: 'vert2', p1: 'p2', p2: 'p6' },
|
|
562
|
+
{ type: 'line', id: 'vert3', p1: 'p3', p2: 'p7' },
|
|
563
|
+
{ type: 'line', id: 'vert4', p1: 'p4', p2: 'p8' }
|
|
542
564
|
],
|
|
543
565
|
constraints: [
|
|
544
|
-
{ type: '
|
|
545
|
-
{ type: '
|
|
546
|
-
{ type: '
|
|
547
|
-
{ type: '
|
|
548
|
-
{ type: '
|
|
549
|
-
{ type: '
|
|
550
|
-
{ type: '
|
|
551
|
-
{ type: 'Distance', entities: ['p3', 'p7'], distance: 50 },
|
|
552
|
-
{ type: 'Distance', entities: ['p4', 'p8'], distance: 50 }
|
|
566
|
+
{ type: 'fixed', entity: 'p1' },
|
|
567
|
+
{ type: 'fixed', entity: 'p2' },
|
|
568
|
+
{ type: 'fixed', entity: 'p4' },
|
|
569
|
+
{ type: 'distance', between: ['p1', 'p5'], value: 50 },
|
|
570
|
+
{ type: 'distance', between: ['p2', 'p6'], value: 50 },
|
|
571
|
+
{ type: 'distance', between: ['p3', 'p7'], value: 50 },
|
|
572
|
+
{ type: 'distance', between: ['p4', 'p8'], value: 50 }
|
|
553
573
|
]
|
|
554
574
|
}
|
|
555
575
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sknoble/slvsx-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for SLVSX geometric constraint solver",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node mcp-server.js",
|
|
12
12
|
"build:docs": "npx tsx scripts/build-docs.ts",
|
|
13
|
-
"test": "node tests/mcp-server.test.js"
|
|
13
|
+
"test": "node tests/mcp-server.test.js",
|
|
14
|
+
"postinstall": "node scripts/postinstall.js"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
17
|
"@langchain/textsplitters": "^0.1.0",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"files": [
|
|
39
40
|
"mcp-server.js",
|
|
40
41
|
"dist/docs.json",
|
|
42
|
+
"scripts/postinstall.js",
|
|
41
43
|
"README.md"
|
|
42
44
|
],
|
|
43
45
|
"engines": {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall script that downloads the slvsx binary for the current platform
|
|
5
|
+
* from GitHub releases.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import https from 'https';
|
|
12
|
+
import { createWriteStream } from 'fs';
|
|
13
|
+
import { exec } from 'child_process';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
|
|
16
|
+
const execAsync = promisify(exec);
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(__filename);
|
|
20
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
21
|
+
|
|
22
|
+
// Map Node.js platform/arch to our release asset naming
|
|
23
|
+
function getPlatformInfo() {
|
|
24
|
+
const platform = process.platform;
|
|
25
|
+
const arch = process.arch;
|
|
26
|
+
|
|
27
|
+
// Map to actual release asset names like: slvsx-macos-arm64.tar.gz, slvsx-linux.tar.gz
|
|
28
|
+
if (platform === 'darwin') {
|
|
29
|
+
if (arch === 'arm64') {
|
|
30
|
+
return { assetPattern: 'macos-arm64', extension: '' };
|
|
31
|
+
} else if (arch === 'x64') {
|
|
32
|
+
return { assetPattern: 'macos-x86_64', extension: '' };
|
|
33
|
+
}
|
|
34
|
+
} else if (platform === 'linux') {
|
|
35
|
+
// Linux release is just "linux" (x86_64)
|
|
36
|
+
if (arch === 'x64') {
|
|
37
|
+
return { assetPattern: 'linux', extension: '' };
|
|
38
|
+
}
|
|
39
|
+
} else if (platform === 'win32') {
|
|
40
|
+
if (arch === 'x64') {
|
|
41
|
+
return { assetPattern: 'windows', extension: '.exe' };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function getLatestRelease() {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const options = {
|
|
51
|
+
hostname: 'api.github.com',
|
|
52
|
+
path: '/repos/snoble/slvsx-cli/releases/latest',
|
|
53
|
+
headers: {
|
|
54
|
+
'User-Agent': 'slvsx-mcp-server-installer'
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
https.get(options, (res) => {
|
|
59
|
+
let data = '';
|
|
60
|
+
res.on('data', chunk => data += chunk);
|
|
61
|
+
res.on('end', () => {
|
|
62
|
+
try {
|
|
63
|
+
resolve(JSON.parse(data));
|
|
64
|
+
} catch (e) {
|
|
65
|
+
reject(new Error('Failed to parse release info'));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}).on('error', reject);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function downloadFile(url, dest) {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const follow = (url) => {
|
|
75
|
+
https.get(url, (res) => {
|
|
76
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
77
|
+
// Consume the redirect response to release the connection
|
|
78
|
+
res.resume();
|
|
79
|
+
follow(res.headers.location);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (res.statusCode !== 200) {
|
|
84
|
+
res.resume(); // Consume response body
|
|
85
|
+
reject(new Error(`Download failed with status ${res.statusCode}`));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const file = createWriteStream(dest);
|
|
90
|
+
// Handle file stream errors to prevent hanging
|
|
91
|
+
file.on('error', (err) => {
|
|
92
|
+
file.close();
|
|
93
|
+
fs.unlink(dest, () => {}); // Clean up partial file
|
|
94
|
+
reject(err);
|
|
95
|
+
});
|
|
96
|
+
res.pipe(file);
|
|
97
|
+
file.on('finish', () => {
|
|
98
|
+
file.close();
|
|
99
|
+
resolve();
|
|
100
|
+
});
|
|
101
|
+
}).on('error', reject);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
follow(url);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function extractTarGz(file, dest) {
|
|
109
|
+
await execAsync(`tar -xzf "${file}" -C "${dest}"`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function extractZip(file, dest) {
|
|
113
|
+
if (process.platform === 'win32') {
|
|
114
|
+
await execAsync(`powershell -Command "Expand-Archive -Path '${file}' -DestinationPath '${dest}'"`);
|
|
115
|
+
} else {
|
|
116
|
+
await execAsync(`unzip -o "${file}" -d "${dest}"`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function main() {
|
|
121
|
+
const binDir = path.join(packageRoot, 'bin');
|
|
122
|
+
const binaryPath = path.join(binDir, process.platform === 'win32' ? 'slvsx.exe' : 'slvsx');
|
|
123
|
+
|
|
124
|
+
// Skip if binary already exists
|
|
125
|
+
if (fs.existsSync(binaryPath)) {
|
|
126
|
+
console.log('slvsx binary already installed.');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const platformInfo = getPlatformInfo();
|
|
131
|
+
if (!platformInfo) {
|
|
132
|
+
console.error(`Unsupported platform: ${process.platform}-${process.arch}`);
|
|
133
|
+
console.error('Please install slvsx manually from https://github.com/snoble/slvsx-cli/releases');
|
|
134
|
+
process.exit(0); // Don't fail install, just warn
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(`Installing slvsx for ${process.platform}-${process.arch}...`);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const release = await getLatestRelease();
|
|
141
|
+
|
|
142
|
+
if (!release.assets) {
|
|
143
|
+
console.error('No release assets found. Please install slvsx manually.');
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Find the right asset
|
|
148
|
+
const asset = release.assets.find(a =>
|
|
149
|
+
a.name.includes(platformInfo.assetPattern) &&
|
|
150
|
+
(a.name.endsWith('.tar.gz') || a.name.endsWith('.zip'))
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (!asset) {
|
|
154
|
+
console.error(`No binary found for ${process.platform}-${process.arch} (looking for '${platformInfo.assetPattern}')`);
|
|
155
|
+
console.error('Available assets:', release.assets.map(a => a.name).join(', '));
|
|
156
|
+
console.error('Please install slvsx manually from https://github.com/snoble/slvsx-cli/releases');
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Create bin directory
|
|
161
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
162
|
+
|
|
163
|
+
// Download
|
|
164
|
+
const tempFile = path.join(binDir, asset.name);
|
|
165
|
+
console.log(`Downloading ${asset.name}...`);
|
|
166
|
+
await downloadFile(asset.browser_download_url, tempFile);
|
|
167
|
+
|
|
168
|
+
// Extract
|
|
169
|
+
console.log('Extracting...');
|
|
170
|
+
if (asset.name.endsWith('.tar.gz')) {
|
|
171
|
+
await extractTarGz(tempFile, binDir);
|
|
172
|
+
} else {
|
|
173
|
+
await extractZip(tempFile, binDir);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Clean up archive
|
|
177
|
+
fs.unlinkSync(tempFile);
|
|
178
|
+
|
|
179
|
+
// Make executable on Unix
|
|
180
|
+
if (process.platform !== 'win32') {
|
|
181
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Verify
|
|
185
|
+
if (fs.existsSync(binaryPath)) {
|
|
186
|
+
console.log(`✓ slvsx installed successfully to ${binaryPath}`);
|
|
187
|
+
} else {
|
|
188
|
+
// Binary might be in a subdirectory after extraction
|
|
189
|
+
const files = fs.readdirSync(binDir);
|
|
190
|
+
console.log('Extracted files:', files);
|
|
191
|
+
console.error('Binary not found at expected location. Please check extraction.');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('Failed to download slvsx:', error.message);
|
|
196
|
+
console.error('Please install manually from https://github.com/snoble/slvsx-cli/releases');
|
|
197
|
+
process.exit(0); // Don't fail the npm install
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
main();
|
|
202
|
+
|