@learnrudi/cli 1.6.0 → 1.7.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/package.json +3 -1
- package/scripts/postinstall.js +187 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learnrudi/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "RUDI CLI - Install and manage MCP stacks, runtimes, and AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"scripts",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"start": "node src/index.js",
|
|
16
17
|
"build": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.cjs --external:better-sqlite3",
|
|
18
|
+
"postinstall": "node scripts/postinstall.js",
|
|
17
19
|
"prepublishOnly": "npm run build",
|
|
18
20
|
"test": "node --test src/__tests__/"
|
|
19
21
|
},
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Post-install script for @learnrudi/cli
|
|
4
|
+
*
|
|
5
|
+
* Fetches runtime manifests from registry and downloads:
|
|
6
|
+
* 1. Node.js runtime → ~/.rudi/runtimes/node/
|
|
7
|
+
* 2. Python runtime → ~/.rudi/runtimes/python/
|
|
8
|
+
* 3. Creates shims → ~/.rudi/shims/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import * as os from 'os';
|
|
15
|
+
|
|
16
|
+
const RUDI_HOME = path.join(os.homedir(), '.rudi');
|
|
17
|
+
const REGISTRY_BASE = 'https://raw.githubusercontent.com/learn-rudi/registry/main';
|
|
18
|
+
|
|
19
|
+
// Detect platform
|
|
20
|
+
function getPlatformArch() {
|
|
21
|
+
const platform = process.platform;
|
|
22
|
+
const arch = process.arch;
|
|
23
|
+
|
|
24
|
+
if (platform === 'darwin') {
|
|
25
|
+
return arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
|
|
26
|
+
} else if (platform === 'linux') {
|
|
27
|
+
return arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
|
|
28
|
+
} else if (platform === 'win32') {
|
|
29
|
+
return 'win32-x64';
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Create directory structure
|
|
35
|
+
function ensureDirectories() {
|
|
36
|
+
const dirs = [
|
|
37
|
+
RUDI_HOME,
|
|
38
|
+
path.join(RUDI_HOME, 'runtimes'),
|
|
39
|
+
path.join(RUDI_HOME, 'stacks'),
|
|
40
|
+
path.join(RUDI_HOME, 'tools'),
|
|
41
|
+
path.join(RUDI_HOME, 'shims'),
|
|
42
|
+
path.join(RUDI_HOME, 'cache'),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
for (const dir of dirs) {
|
|
46
|
+
if (!fs.existsSync(dir)) {
|
|
47
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fetch JSON from URL
|
|
53
|
+
async function fetchJson(url) {
|
|
54
|
+
const response = await fetch(url);
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error(`Failed to fetch ${url}: ${response.status}`);
|
|
57
|
+
}
|
|
58
|
+
return response.json();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Download and extract a tarball
|
|
62
|
+
async function downloadAndExtract(url, destDir, name) {
|
|
63
|
+
const tempFile = path.join(RUDI_HOME, 'cache', `${name}.tar.gz`);
|
|
64
|
+
|
|
65
|
+
console.log(` Downloading ${name}...`);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Download using curl
|
|
69
|
+
execSync(`curl -fsSL "${url}" -o "${tempFile}"`, { stdio: 'pipe' });
|
|
70
|
+
|
|
71
|
+
// Create dest directory
|
|
72
|
+
if (fs.existsSync(destDir)) {
|
|
73
|
+
fs.rmSync(destDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
76
|
+
|
|
77
|
+
// Extract - strip first component to avoid nested dirs
|
|
78
|
+
execSync(`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`, { stdio: 'pipe' });
|
|
79
|
+
|
|
80
|
+
// Clean up
|
|
81
|
+
fs.unlinkSync(tempFile);
|
|
82
|
+
|
|
83
|
+
console.log(` ✓ ${name} installed`);
|
|
84
|
+
return true;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.log(` ⚠ Failed to install ${name}: ${error.message}`);
|
|
87
|
+
if (fs.existsSync(tempFile)) {
|
|
88
|
+
fs.unlinkSync(tempFile);
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Download runtime from manifest
|
|
95
|
+
async function downloadRuntime(runtimeId, platformArch) {
|
|
96
|
+
const manifestUrl = `${REGISTRY_BASE}/catalog/runtimes/${runtimeId}.json`;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const manifest = await fetchJson(manifestUrl);
|
|
100
|
+
const downloadUrl = manifest.download?.[platformArch];
|
|
101
|
+
|
|
102
|
+
if (!downloadUrl) {
|
|
103
|
+
console.log(` ⚠ No ${runtimeId} available for ${platformArch}`);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const destDir = path.join(RUDI_HOME, 'runtimes', runtimeId);
|
|
108
|
+
const binaryPath = path.join(destDir, manifest.binary || `bin/${runtimeId}`);
|
|
109
|
+
|
|
110
|
+
// Skip if already installed
|
|
111
|
+
if (fs.existsSync(binaryPath)) {
|
|
112
|
+
console.log(` ✓ ${manifest.name} already installed`);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return await downloadAndExtract(downloadUrl, destDir, manifest.name);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.log(` ⚠ Failed to fetch ${runtimeId} manifest: ${error.message}`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Create the rudi-mcp shim
|
|
124
|
+
function createShim() {
|
|
125
|
+
const shimPath = path.join(RUDI_HOME, 'shims', 'rudi-mcp');
|
|
126
|
+
|
|
127
|
+
const shimContent = `#!/bin/bash
|
|
128
|
+
# RUDI MCP Shim - Routes agent calls to rudi mcp command
|
|
129
|
+
# Usage: rudi-mcp <stack-name>
|
|
130
|
+
exec rudi mcp "$@"
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
fs.writeFileSync(shimPath, shimContent);
|
|
134
|
+
fs.chmodSync(shimPath, 0o755);
|
|
135
|
+
console.log(` ✓ Created shim at ${shimPath}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Initialize secrets file with secure permissions
|
|
139
|
+
function initSecrets() {
|
|
140
|
+
const secretsPath = path.join(RUDI_HOME, 'secrets.json');
|
|
141
|
+
|
|
142
|
+
if (!fs.existsSync(secretsPath)) {
|
|
143
|
+
fs.writeFileSync(secretsPath, '{}', { mode: 0o600 });
|
|
144
|
+
console.log(` ✓ Created secrets store`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Main setup
|
|
149
|
+
async function setup() {
|
|
150
|
+
console.log('\nSetting up RUDI...\n');
|
|
151
|
+
|
|
152
|
+
const platformArch = getPlatformArch();
|
|
153
|
+
if (!platformArch) {
|
|
154
|
+
console.log('⚠ Unsupported platform. Skipping runtime download.');
|
|
155
|
+
console.log(' You can manually install runtimes later with: rudi install runtime:node\n');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Create directories
|
|
160
|
+
ensureDirectories();
|
|
161
|
+
console.log('✓ Created ~/.rudi directory structure\n');
|
|
162
|
+
|
|
163
|
+
// Download runtimes from registry manifests
|
|
164
|
+
console.log('Installing runtimes...');
|
|
165
|
+
await downloadRuntime('node', platformArch);
|
|
166
|
+
await downloadRuntime('python', platformArch);
|
|
167
|
+
|
|
168
|
+
// Create shim
|
|
169
|
+
console.log('\nSetting up shims...');
|
|
170
|
+
createShim();
|
|
171
|
+
|
|
172
|
+
// Initialize secrets
|
|
173
|
+
initSecrets();
|
|
174
|
+
|
|
175
|
+
console.log('\n✓ RUDI setup complete!\n');
|
|
176
|
+
console.log('Get started:');
|
|
177
|
+
console.log(' rudi search --all # See available stacks');
|
|
178
|
+
console.log(' rudi install slack # Install a stack');
|
|
179
|
+
console.log(' rudi doctor # Check system health\n');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Run
|
|
183
|
+
setup().catch(err => {
|
|
184
|
+
console.error('Setup error:', err.message);
|
|
185
|
+
// Don't fail npm install - user can run rudi doctor later
|
|
186
|
+
process.exit(0);
|
|
187
|
+
});
|