@learnrudi/cli 1.6.0 → 1.8.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/dist/index.cjs +137 -38
- package/package.json +3 -1
- package/scripts/postinstall.js +187 -0
package/dist/index.cjs
CHANGED
|
@@ -186,7 +186,6 @@ __export(src_exports, {
|
|
|
186
186
|
PACKAGE_KINDS: () => PACKAGE_KINDS2,
|
|
187
187
|
RUNTIMES_DOWNLOAD_BASE: () => RUNTIMES_DOWNLOAD_BASE,
|
|
188
188
|
RUNTIMES_RELEASE_VERSION: () => RUNTIMES_RELEASE_VERSION,
|
|
189
|
-
STACKS_RELEASE_VERSION: () => STACKS_RELEASE_VERSION,
|
|
190
189
|
checkCache: () => checkCache,
|
|
191
190
|
clearCache: () => clearCache,
|
|
192
191
|
computeHash: () => computeHash,
|
|
@@ -383,7 +382,7 @@ async function downloadPackage(pkg, destPath, options = {}) {
|
|
|
383
382
|
}
|
|
384
383
|
onProgress?.({ phase: "downloading", package: pkg.name || pkg.id });
|
|
385
384
|
if (pkg.kind === "stack" || registryPath.includes("/stacks/")) {
|
|
386
|
-
await
|
|
385
|
+
await downloadStackFromGitHub(registryPath, destPath, onProgress);
|
|
387
386
|
return { success: true, path: destPath };
|
|
388
387
|
}
|
|
389
388
|
if (registryPath.endsWith(".md")) {
|
|
@@ -404,45 +403,129 @@ async function downloadPackage(pkg, destPath, options = {}) {
|
|
|
404
403
|
}
|
|
405
404
|
throw new Error(`Unsupported package type: ${registryPath}`);
|
|
406
405
|
}
|
|
407
|
-
async function
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
406
|
+
async function downloadStackFromGitHub(registryPath, destPath, onProgress) {
|
|
407
|
+
const baseUrl = `${GITHUB_RAW_BASE}/${registryPath}`;
|
|
408
|
+
const apiUrl = `https://api.github.com/repos/learn-rudi/registry/contents/${registryPath}`;
|
|
409
|
+
const listResponse = await fetch(apiUrl, {
|
|
410
|
+
headers: {
|
|
411
|
+
"User-Agent": "rudi-cli/2.0",
|
|
412
|
+
"Accept": "application/vnd.github.v3+json"
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
if (!listResponse.ok) {
|
|
416
|
+
throw new Error(`Stack not found: ${registryPath}`);
|
|
417
|
+
}
|
|
418
|
+
const contents = await listResponse.json();
|
|
419
|
+
if (!Array.isArray(contents)) {
|
|
420
|
+
throw new Error(`Invalid stack directory: ${registryPath}`);
|
|
421
|
+
}
|
|
422
|
+
const existingItems = /* @__PURE__ */ new Map();
|
|
423
|
+
for (const item of contents) {
|
|
424
|
+
existingItems.set(item.name, item);
|
|
425
|
+
}
|
|
426
|
+
const manifestItem = existingItems.get("manifest.json");
|
|
427
|
+
if (!manifestItem) {
|
|
428
|
+
throw new Error(`Stack missing manifest.json: ${registryPath}`);
|
|
416
429
|
}
|
|
417
|
-
const
|
|
430
|
+
const manifestResponse = await fetch(manifestItem.download_url, {
|
|
431
|
+
headers: { "User-Agent": "rudi-cli/2.0" }
|
|
432
|
+
});
|
|
433
|
+
const manifest = await manifestResponse.json();
|
|
434
|
+
import_fs2.default.writeFileSync(import_path2.default.join(destPath, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
435
|
+
onProgress?.({ phase: "downloading", file: "manifest.json" });
|
|
436
|
+
const pkgJsonItem = existingItems.get("package.json");
|
|
437
|
+
if (pkgJsonItem) {
|
|
438
|
+
const pkgJsonResponse = await fetch(pkgJsonItem.download_url, {
|
|
439
|
+
headers: { "User-Agent": "rudi-cli/2.0" }
|
|
440
|
+
});
|
|
441
|
+
if (pkgJsonResponse.ok) {
|
|
442
|
+
const pkgJson = await pkgJsonResponse.text();
|
|
443
|
+
import_fs2.default.writeFileSync(import_path2.default.join(destPath, "package.json"), pkgJson);
|
|
444
|
+
onProgress?.({ phase: "downloading", file: "package.json" });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const envExampleItem = existingItems.get(".env.example");
|
|
448
|
+
if (envExampleItem) {
|
|
449
|
+
const envResponse = await fetch(envExampleItem.download_url, {
|
|
450
|
+
headers: { "User-Agent": "rudi-cli/2.0" }
|
|
451
|
+
});
|
|
452
|
+
if (envResponse.ok) {
|
|
453
|
+
const envContent = await envResponse.text();
|
|
454
|
+
import_fs2.default.writeFileSync(import_path2.default.join(destPath, ".env.example"), envContent);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const tsconfigItem = existingItems.get("tsconfig.json");
|
|
458
|
+
if (tsconfigItem) {
|
|
459
|
+
const tsconfigResponse = await fetch(tsconfigItem.download_url, {
|
|
460
|
+
headers: { "User-Agent": "rudi-cli/2.0" }
|
|
461
|
+
});
|
|
462
|
+
if (tsconfigResponse.ok) {
|
|
463
|
+
const tsconfig = await tsconfigResponse.text();
|
|
464
|
+
import_fs2.default.writeFileSync(import_path2.default.join(destPath, "tsconfig.json"), tsconfig);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const requirementsItem = existingItems.get("requirements.txt");
|
|
468
|
+
if (requirementsItem) {
|
|
469
|
+
const reqResponse = await fetch(requirementsItem.download_url, {
|
|
470
|
+
headers: { "User-Agent": "rudi-cli/2.0" }
|
|
471
|
+
});
|
|
472
|
+
if (reqResponse.ok) {
|
|
473
|
+
const requirements = await reqResponse.text();
|
|
474
|
+
import_fs2.default.writeFileSync(import_path2.default.join(destPath, "requirements.txt"), requirements);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const sourceDirs = ["src", "dist", "node", "python", "lib"];
|
|
478
|
+
for (const dirName of sourceDirs) {
|
|
479
|
+
const dirItem = existingItems.get(dirName);
|
|
480
|
+
if (dirItem && dirItem.type === "dir") {
|
|
481
|
+
onProgress?.({ phase: "downloading", directory: dirName });
|
|
482
|
+
await downloadDirectoryFromGitHub(
|
|
483
|
+
`${baseUrl}/${dirName}`,
|
|
484
|
+
import_path2.default.join(destPath, dirName),
|
|
485
|
+
onProgress
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async function downloadDirectoryFromGitHub(dirUrl, destDir, onProgress) {
|
|
491
|
+
const apiUrl = dirUrl.replace("https://raw.githubusercontent.com/", "https://api.github.com/repos/").replace("/main/", "/contents/");
|
|
418
492
|
try {
|
|
419
|
-
const response = await fetch(
|
|
493
|
+
const response = await fetch(apiUrl, {
|
|
420
494
|
headers: {
|
|
421
495
|
"User-Agent": "rudi-cli/2.0",
|
|
422
|
-
"Accept": "application/
|
|
496
|
+
"Accept": "application/vnd.github.v3+json"
|
|
423
497
|
}
|
|
424
498
|
});
|
|
425
499
|
if (!response.ok) {
|
|
426
|
-
|
|
500
|
+
return;
|
|
427
501
|
}
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (import_fs2.default.existsSync(destPath)) {
|
|
432
|
-
import_fs2.default.rmSync(destPath, { recursive: true });
|
|
502
|
+
const contents = await response.json();
|
|
503
|
+
if (!Array.isArray(contents)) {
|
|
504
|
+
return;
|
|
433
505
|
}
|
|
434
|
-
import_fs2.default.
|
|
435
|
-
|
|
436
|
-
execSync7(`tar -xzf "${tempFile}" -C "${destPath}"`, {
|
|
437
|
-
stdio: "pipe"
|
|
438
|
-
});
|
|
439
|
-
import_fs2.default.unlinkSync(tempFile);
|
|
440
|
-
onProgress?.({ phase: "complete", package: stackName, path: destPath });
|
|
441
|
-
} catch (error) {
|
|
442
|
-
if (import_fs2.default.existsSync(tempFile)) {
|
|
443
|
-
import_fs2.default.unlinkSync(tempFile);
|
|
506
|
+
if (!import_fs2.default.existsSync(destDir)) {
|
|
507
|
+
import_fs2.default.mkdirSync(destDir, { recursive: true });
|
|
444
508
|
}
|
|
445
|
-
|
|
509
|
+
for (const item of contents) {
|
|
510
|
+
if (item.type === "file") {
|
|
511
|
+
const fileResponse = await fetch(item.download_url, {
|
|
512
|
+
headers: { "User-Agent": "rudi-cli/2.0" }
|
|
513
|
+
});
|
|
514
|
+
if (fileResponse.ok) {
|
|
515
|
+
const content = await fileResponse.text();
|
|
516
|
+
import_fs2.default.writeFileSync(import_path2.default.join(destDir, item.name), content);
|
|
517
|
+
onProgress?.({ phase: "downloading", file: item.name });
|
|
518
|
+
}
|
|
519
|
+
} else if (item.type === "dir") {
|
|
520
|
+
await downloadDirectoryFromGitHub(
|
|
521
|
+
item.url.replace("https://api.github.com/repos/", "https://raw.githubusercontent.com/").replace("/contents/", "/main/"),
|
|
522
|
+
import_path2.default.join(destDir, item.name),
|
|
523
|
+
onProgress
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.error(`Warning: Could not download ${dirUrl}: ${error.message}`);
|
|
446
529
|
}
|
|
447
530
|
}
|
|
448
531
|
async function downloadRuntime(runtime, version, destPath, options = {}) {
|
|
@@ -720,7 +803,7 @@ async function computeHash(filePath) {
|
|
|
720
803
|
stream.on("error", reject);
|
|
721
804
|
});
|
|
722
805
|
}
|
|
723
|
-
var import_fs2, import_path2, import_crypto, DEFAULT_REGISTRY_URL, RUNTIMES_DOWNLOAD_BASE, CACHE_TTL, PACKAGE_KINDS2, KIND_PLURALS, GITHUB_RAW_BASE,
|
|
806
|
+
var import_fs2, import_path2, import_crypto, DEFAULT_REGISTRY_URL, RUNTIMES_DOWNLOAD_BASE, CACHE_TTL, PACKAGE_KINDS2, KIND_PLURALS, GITHUB_RAW_BASE, RUNTIMES_RELEASE_VERSION;
|
|
724
807
|
var init_src2 = __esm({
|
|
725
808
|
"../packages/registry-client/src/index.js"() {
|
|
726
809
|
import_fs2 = __toESM(require("fs"), 1);
|
|
@@ -735,8 +818,6 @@ var init_src2 = __esm({
|
|
|
735
818
|
binary: "binaries"
|
|
736
819
|
};
|
|
737
820
|
GITHUB_RAW_BASE = "https://raw.githubusercontent.com/learn-rudi/registry/main";
|
|
738
|
-
STACKS_DOWNLOAD_BASE = "https://github.com/learn-rudi/registry/releases/download";
|
|
739
|
-
STACKS_RELEASE_VERSION = "stacks-v1.0.0";
|
|
740
821
|
RUNTIMES_RELEASE_VERSION = "v1.0.0";
|
|
741
822
|
}
|
|
742
823
|
});
|
|
@@ -16303,8 +16384,25 @@ async function loadManifest(installPath) {
|
|
|
16303
16384
|
return null;
|
|
16304
16385
|
}
|
|
16305
16386
|
}
|
|
16387
|
+
function getBundledBinary(runtime, binary) {
|
|
16388
|
+
const platform = process.platform;
|
|
16389
|
+
const rudiHome = process.env.RUDI_HOME || path8.join(process.env.HOME || process.env.USERPROFILE, ".rudi");
|
|
16390
|
+
if (runtime === "node") {
|
|
16391
|
+
const npmPath = platform === "win32" ? path8.join(rudiHome, "runtimes", "node", "npm.cmd") : path8.join(rudiHome, "runtimes", "node", "bin", "npm");
|
|
16392
|
+
if (require("fs").existsSync(npmPath)) {
|
|
16393
|
+
return npmPath;
|
|
16394
|
+
}
|
|
16395
|
+
}
|
|
16396
|
+
if (runtime === "python") {
|
|
16397
|
+
const pipPath = platform === "win32" ? path8.join(rudiHome, "runtimes", "python", "Scripts", "pip.exe") : path8.join(rudiHome, "runtimes", "python", "bin", "pip3");
|
|
16398
|
+
if (require("fs").existsSync(pipPath)) {
|
|
16399
|
+
return pipPath;
|
|
16400
|
+
}
|
|
16401
|
+
}
|
|
16402
|
+
return binary;
|
|
16403
|
+
}
|
|
16306
16404
|
async function installDependencies(stackPath, manifest) {
|
|
16307
|
-
const runtime = manifest?.runtime || "node";
|
|
16405
|
+
const runtime = manifest?.runtime || manifest?.mcp?.runtime || "node";
|
|
16308
16406
|
try {
|
|
16309
16407
|
if (runtime === "node") {
|
|
16310
16408
|
const packageJsonPath = path8.join(stackPath, "package.json");
|
|
@@ -16319,11 +16417,11 @@ async function installDependencies(stackPath, manifest) {
|
|
|
16319
16417
|
return { installed: false, reason: "Dependencies already installed" };
|
|
16320
16418
|
} catch {
|
|
16321
16419
|
}
|
|
16420
|
+
const npmCmd = getBundledBinary("node", "npm");
|
|
16322
16421
|
console.log(` Installing npm dependencies...`);
|
|
16323
|
-
(0, import_child_process2.execSync)("
|
|
16422
|
+
(0, import_child_process2.execSync)(`"${npmCmd}" install --production`, {
|
|
16324
16423
|
cwd: stackPath,
|
|
16325
16424
|
stdio: "pipe"
|
|
16326
|
-
// Suppress output
|
|
16327
16425
|
});
|
|
16328
16426
|
return { installed: true };
|
|
16329
16427
|
} else if (runtime === "python") {
|
|
@@ -16333,8 +16431,9 @@ async function installDependencies(stackPath, manifest) {
|
|
|
16333
16431
|
} catch {
|
|
16334
16432
|
return { installed: false, reason: "No requirements.txt" };
|
|
16335
16433
|
}
|
|
16434
|
+
const pipCmd = getBundledBinary("python", "pip");
|
|
16336
16435
|
console.log(` Installing pip dependencies...`);
|
|
16337
|
-
(0, import_child_process2.execSync)("
|
|
16436
|
+
(0, import_child_process2.execSync)(`"${pipCmd}" install -r requirements.txt`, {
|
|
16338
16437
|
cwd: stackPath,
|
|
16339
16438
|
stdio: "pipe"
|
|
16340
16439
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learnrudi/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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
|
+
});
|