@learnrudi/cli 1.9.7 → 1.9.9
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/README.md +145 -53
- package/dist/index.cjs +21094 -721
- package/dist/packages-manifest.json +189 -0
- package/package.json +20 -19
- package/scripts/generate-manifest.js +195 -0
- package/scripts/postinstall.js +165 -25
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"generated": "2026-01-09T16:21:26.473Z",
|
|
4
|
+
"packages": {
|
|
5
|
+
"runtimes": [
|
|
6
|
+
{
|
|
7
|
+
"id": "bun",
|
|
8
|
+
"name": "Bun",
|
|
9
|
+
"kind": "runtime",
|
|
10
|
+
"installDir": "bun",
|
|
11
|
+
"basePath": "runtimes",
|
|
12
|
+
"installType": "binary",
|
|
13
|
+
"commands": [
|
|
14
|
+
{
|
|
15
|
+
"name": "bun",
|
|
16
|
+
"bin": "bun",
|
|
17
|
+
"args": null
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "bunx",
|
|
21
|
+
"bin": "bun",
|
|
22
|
+
"args": [
|
|
23
|
+
"x"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "deno",
|
|
30
|
+
"name": "Deno",
|
|
31
|
+
"kind": "runtime",
|
|
32
|
+
"installDir": "deno",
|
|
33
|
+
"basePath": "runtimes",
|
|
34
|
+
"installType": "binary",
|
|
35
|
+
"commands": [
|
|
36
|
+
{
|
|
37
|
+
"name": "deno",
|
|
38
|
+
"bin": "deno",
|
|
39
|
+
"args": null
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "node",
|
|
45
|
+
"name": "Node.js",
|
|
46
|
+
"kind": "runtime",
|
|
47
|
+
"installDir": "node",
|
|
48
|
+
"basePath": "runtimes",
|
|
49
|
+
"installType": "binary",
|
|
50
|
+
"commands": [
|
|
51
|
+
{
|
|
52
|
+
"name": "node",
|
|
53
|
+
"bin": "bin/node",
|
|
54
|
+
"args": null
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "npm",
|
|
58
|
+
"bin": "bin/npm",
|
|
59
|
+
"args": null
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "npx",
|
|
63
|
+
"bin": "bin/npx",
|
|
64
|
+
"args": null
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "python",
|
|
70
|
+
"name": "Python",
|
|
71
|
+
"kind": "runtime",
|
|
72
|
+
"installDir": "python",
|
|
73
|
+
"basePath": "runtimes",
|
|
74
|
+
"installType": "binary",
|
|
75
|
+
"commands": [
|
|
76
|
+
{
|
|
77
|
+
"name": "python",
|
|
78
|
+
"bin": "bin/python3",
|
|
79
|
+
"args": null
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "python3",
|
|
83
|
+
"bin": "bin/python3",
|
|
84
|
+
"args": null
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "pip",
|
|
88
|
+
"bin": "bin/python3",
|
|
89
|
+
"args": [
|
|
90
|
+
"-m",
|
|
91
|
+
"pip"
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "pip3",
|
|
96
|
+
"bin": "bin/python3",
|
|
97
|
+
"args": [
|
|
98
|
+
"-m",
|
|
99
|
+
"pip"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"agents": [
|
|
106
|
+
{
|
|
107
|
+
"id": "claude",
|
|
108
|
+
"name": "Claude Code",
|
|
109
|
+
"kind": "agent",
|
|
110
|
+
"installDir": "claude",
|
|
111
|
+
"basePath": "agents",
|
|
112
|
+
"installType": "npm",
|
|
113
|
+
"commands": [
|
|
114
|
+
{
|
|
115
|
+
"name": "claude",
|
|
116
|
+
"bin": "node_modules/.bin/claude",
|
|
117
|
+
"args": null
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "codex",
|
|
123
|
+
"name": "OpenAI Codex",
|
|
124
|
+
"kind": "agent",
|
|
125
|
+
"installDir": "codex",
|
|
126
|
+
"basePath": "agents",
|
|
127
|
+
"installType": "npm",
|
|
128
|
+
"commands": [
|
|
129
|
+
{
|
|
130
|
+
"name": "codex",
|
|
131
|
+
"bin": "node_modules/.bin/codex",
|
|
132
|
+
"args": null
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "copilot",
|
|
138
|
+
"name": "GitHub Copilot",
|
|
139
|
+
"kind": "agent",
|
|
140
|
+
"installDir": "copilot",
|
|
141
|
+
"basePath": "agents",
|
|
142
|
+
"installType": "npm",
|
|
143
|
+
"commands": [
|
|
144
|
+
{
|
|
145
|
+
"name": "copilot",
|
|
146
|
+
"bin": "node_modules/.bin/github-copilot-cli",
|
|
147
|
+
"args": null
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "github-copilot-cli",
|
|
151
|
+
"bin": "node_modules/.bin/github-copilot-cli",
|
|
152
|
+
"args": null
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"id": "gemini",
|
|
158
|
+
"name": "Gemini CLI",
|
|
159
|
+
"kind": "agent",
|
|
160
|
+
"installDir": "gemini",
|
|
161
|
+
"basePath": "agents",
|
|
162
|
+
"installType": "npm",
|
|
163
|
+
"commands": [
|
|
164
|
+
{
|
|
165
|
+
"name": "gemini",
|
|
166
|
+
"bin": "node_modules/.bin/gemini",
|
|
167
|
+
"args": null
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"id": "ollama",
|
|
173
|
+
"name": "Ollama",
|
|
174
|
+
"kind": "agent",
|
|
175
|
+
"installDir": "ollama",
|
|
176
|
+
"basePath": "agents",
|
|
177
|
+
"installType": "binary",
|
|
178
|
+
"commands": [
|
|
179
|
+
{
|
|
180
|
+
"name": "ollama",
|
|
181
|
+
"bin": "ollama",
|
|
182
|
+
"args": null
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
"binaries": []
|
|
188
|
+
}
|
|
189
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learnrudi/cli",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.9",
|
|
4
4
|
"description": "RUDI CLI - Install and manage MCP stacks, runtimes, and AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -12,23 +12,17 @@
|
|
|
12
12
|
"scripts",
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"start": "node src/index.js",
|
|
17
|
-
"build": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.cjs --external:better-sqlite3 --external:@learnrudi/core --external:@learnrudi/db --external:@learnrudi/env --external:@learnrudi/manifest --external:@learnrudi/mcp --external:@learnrudi/registry-client --external:@learnrudi/runner --external:@learnrudi/secrets --external:@learnrudi/utils && cp src/router-mcp.js dist/router-mcp.js",
|
|
18
|
-
"postinstall": "node scripts/postinstall.js",
|
|
19
|
-
"prepublishOnly": "npm run build",
|
|
20
|
-
"test": "node --test src/__tests__/"
|
|
21
|
-
},
|
|
22
15
|
"dependencies": {
|
|
23
|
-
"
|
|
24
|
-
"@learnrudi/
|
|
25
|
-
"@learnrudi/
|
|
26
|
-
"@learnrudi/
|
|
27
|
-
"@learnrudi/
|
|
28
|
-
"@learnrudi/
|
|
29
|
-
"@learnrudi/
|
|
30
|
-
"@learnrudi/
|
|
31
|
-
"@learnrudi/
|
|
16
|
+
"better-sqlite3": "^12.5.0",
|
|
17
|
+
"@learnrudi/core": "1.0.5",
|
|
18
|
+
"@learnrudi/db": "1.0.2",
|
|
19
|
+
"@learnrudi/mcp": "1.0.0",
|
|
20
|
+
"@learnrudi/env": "1.0.1",
|
|
21
|
+
"@learnrudi/manifest": "1.0.0",
|
|
22
|
+
"@learnrudi/registry-client": "1.0.3",
|
|
23
|
+
"@learnrudi/runner": "1.0.1",
|
|
24
|
+
"@learnrudi/secrets": "1.0.1",
|
|
25
|
+
"@learnrudi/utils": "1.0.0"
|
|
32
26
|
},
|
|
33
27
|
"devDependencies": {
|
|
34
28
|
"esbuild": "^0.27.2"
|
|
@@ -54,5 +48,12 @@
|
|
|
54
48
|
"publishConfig": {
|
|
55
49
|
"access": "public"
|
|
56
50
|
},
|
|
57
|
-
"license": "MIT"
|
|
58
|
-
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"scripts": {
|
|
53
|
+
"start": "node src/index.js",
|
|
54
|
+
"prebuild": "node scripts/generate-manifest.js",
|
|
55
|
+
"build": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.cjs --external:better-sqlite3 && cp src/router-mcp.js dist/router-mcp.js && cp src/packages-manifest.json dist/packages-manifest.json",
|
|
56
|
+
"postinstall": "node scripts/postinstall.js",
|
|
57
|
+
"test": "node --test src/__tests__/"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate packages-manifest.json from registry catalog
|
|
4
|
+
*
|
|
5
|
+
* This script reads all package definitions from the registry catalog
|
|
6
|
+
* and generates a unified manifest for shim generation.
|
|
7
|
+
*
|
|
8
|
+
* Run at build time: node scripts/generate-manifest.js
|
|
9
|
+
* Output: src/packages-manifest.json (bundled with CLI)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
// Registry catalog paths - relative to CLI or absolute
|
|
19
|
+
const REGISTRY_PATHS = [
|
|
20
|
+
path.resolve(__dirname, '../../registry/catalog'), // Monorepo structure
|
|
21
|
+
path.resolve(__dirname, '../../../registry/catalog'), // Alternative layout
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function findRegistryPath() {
|
|
25
|
+
for (const p of REGISTRY_PATHS) {
|
|
26
|
+
if (fs.existsSync(p)) {
|
|
27
|
+
return p;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Registry catalog not found. Tried: ${REGISTRY_PATHS.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read all JSON files from a directory
|
|
35
|
+
*/
|
|
36
|
+
function readCatalogDir(dirPath) {
|
|
37
|
+
if (!fs.existsSync(dirPath)) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.json'));
|
|
42
|
+
return files.map(f => {
|
|
43
|
+
const content = fs.readFileSync(path.join(dirPath, f), 'utf-8');
|
|
44
|
+
return JSON.parse(content);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract commands from a package definition
|
|
50
|
+
* Handles multiple formats:
|
|
51
|
+
* - commands: [{ name, bin, args? }] - new explicit format
|
|
52
|
+
* - binary: "name" - single binary
|
|
53
|
+
* - binaries: ["a", "b"] - multiple binaries with same name
|
|
54
|
+
*/
|
|
55
|
+
function extractCommands(pkg, kind) {
|
|
56
|
+
// New format: explicit commands array
|
|
57
|
+
if (pkg.commands && Array.isArray(pkg.commands)) {
|
|
58
|
+
return pkg.commands.map(cmd => ({
|
|
59
|
+
name: cmd.name,
|
|
60
|
+
bin: cmd.bin,
|
|
61
|
+
args: cmd.args || null,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Legacy format: binaries array (ffmpeg has ["ffmpeg", "ffprobe"])
|
|
66
|
+
if (pkg.binaries && Array.isArray(pkg.binaries)) {
|
|
67
|
+
return pkg.binaries.map(name => ({
|
|
68
|
+
name,
|
|
69
|
+
bin: name,
|
|
70
|
+
args: null,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Legacy format: single binary field
|
|
75
|
+
if (pkg.binary) {
|
|
76
|
+
const id = pkg.id.replace(/^(runtime|binary|agent):/, '');
|
|
77
|
+
return [{
|
|
78
|
+
name: id,
|
|
79
|
+
bin: pkg.binary,
|
|
80
|
+
args: null,
|
|
81
|
+
}];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Fallback: use id as command name
|
|
85
|
+
const id = pkg.id.replace(/^(runtime|binary|agent):/, '');
|
|
86
|
+
return [{
|
|
87
|
+
name: id,
|
|
88
|
+
bin: id,
|
|
89
|
+
args: null,
|
|
90
|
+
}];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get install directory for a package
|
|
95
|
+
*/
|
|
96
|
+
function getInstallDir(pkg, kind) {
|
|
97
|
+
if (pkg.installDir) {
|
|
98
|
+
return pkg.installDir;
|
|
99
|
+
}
|
|
100
|
+
const id = pkg.id.replace(/^(runtime|binary|agent):/, '');
|
|
101
|
+
return id;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the base path where this kind of package is installed
|
|
106
|
+
*/
|
|
107
|
+
function getKindBasePath(kind) {
|
|
108
|
+
switch (kind) {
|
|
109
|
+
case 'runtime': return 'runtimes';
|
|
110
|
+
case 'agent': return 'agents';
|
|
111
|
+
case 'binary': return 'binaries';
|
|
112
|
+
default: return kind + 's';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Process packages from a catalog directory
|
|
118
|
+
*/
|
|
119
|
+
function processPackages(catalogPath, kind) {
|
|
120
|
+
const dirPath = path.join(catalogPath, kind + 's'); // runtimes, agents, binaries
|
|
121
|
+
const packages = readCatalogDir(dirPath);
|
|
122
|
+
|
|
123
|
+
return packages.map(pkg => {
|
|
124
|
+
const id = pkg.id.replace(/^(runtime|binary|agent):/, '');
|
|
125
|
+
const installDir = getInstallDir(pkg, kind);
|
|
126
|
+
const basePath = getKindBasePath(kind);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
id,
|
|
130
|
+
name: pkg.name,
|
|
131
|
+
kind,
|
|
132
|
+
installDir,
|
|
133
|
+
basePath,
|
|
134
|
+
installType: pkg.installType || 'binary',
|
|
135
|
+
commands: extractCommands(pkg, kind),
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate the manifest
|
|
142
|
+
*/
|
|
143
|
+
function generateManifest() {
|
|
144
|
+
const catalogPath = findRegistryPath();
|
|
145
|
+
console.log(`Reading catalog from: ${catalogPath}`);
|
|
146
|
+
|
|
147
|
+
const manifest = {
|
|
148
|
+
version: '1.0.0',
|
|
149
|
+
generated: new Date().toISOString(),
|
|
150
|
+
packages: {
|
|
151
|
+
runtimes: processPackages(catalogPath, 'runtime'),
|
|
152
|
+
agents: processPackages(catalogPath, 'agent'),
|
|
153
|
+
binaries: processPackages(catalogPath, 'binary'),
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Summary
|
|
158
|
+
const counts = {
|
|
159
|
+
runtimes: manifest.packages.runtimes.length,
|
|
160
|
+
agents: manifest.packages.agents.length,
|
|
161
|
+
binaries: manifest.packages.binaries.length,
|
|
162
|
+
};
|
|
163
|
+
console.log(`Found: ${counts.runtimes} runtimes, ${counts.agents} agents, ${counts.binaries} binaries`);
|
|
164
|
+
|
|
165
|
+
// Count total commands
|
|
166
|
+
let totalCommands = 0;
|
|
167
|
+
for (const kind of Object.values(manifest.packages)) {
|
|
168
|
+
for (const pkg of kind) {
|
|
169
|
+
totalCommands += pkg.commands.length;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
console.log(`Total commands: ${totalCommands}`);
|
|
173
|
+
|
|
174
|
+
return manifest;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Main
|
|
179
|
+
*/
|
|
180
|
+
function main() {
|
|
181
|
+
try {
|
|
182
|
+
const manifest = generateManifest();
|
|
183
|
+
|
|
184
|
+
// Write to src directory (will be bundled with CLI)
|
|
185
|
+
const outputPath = path.resolve(__dirname, '../src/packages-manifest.json');
|
|
186
|
+
fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
187
|
+
console.log(`\nWrote manifest to: ${outputPath}`);
|
|
188
|
+
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error('Error generating manifest:', err.message);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
main();
|
package/scripts/postinstall.js
CHANGED
|
@@ -19,6 +19,9 @@ const RUDI_JSON_PATH = path.join(RUDI_HOME, 'rudi.json');
|
|
|
19
19
|
const RUDI_JSON_TMP = path.join(RUDI_HOME, 'rudi.json.tmp');
|
|
20
20
|
const REGISTRY_BASE = 'https://raw.githubusercontent.com/learn-rudi/registry/main';
|
|
21
21
|
|
|
22
|
+
// Use 'bins' (consistent with @learnrudi/env) not 'shims'
|
|
23
|
+
const BINS_DIR = path.join(RUDI_HOME, 'bins');
|
|
24
|
+
|
|
22
25
|
// =============================================================================
|
|
23
26
|
// RUDI.JSON CONFIG MANAGEMENT
|
|
24
27
|
// =============================================================================
|
|
@@ -162,8 +165,10 @@ function ensureDirectories() {
|
|
|
162
165
|
path.join(RUDI_HOME, 'runtimes'),
|
|
163
166
|
path.join(RUDI_HOME, 'stacks'),
|
|
164
167
|
path.join(RUDI_HOME, 'binaries'),
|
|
165
|
-
path.join(RUDI_HOME, '
|
|
168
|
+
path.join(RUDI_HOME, 'agents'),
|
|
169
|
+
BINS_DIR, // Use bins/ (consistent with @learnrudi/env)
|
|
166
170
|
path.join(RUDI_HOME, 'cache'),
|
|
171
|
+
path.join(RUDI_HOME, 'router'),
|
|
167
172
|
];
|
|
168
173
|
|
|
169
174
|
for (const dir of dirs) {
|
|
@@ -251,46 +256,181 @@ async function downloadRuntime(runtimeId, platformArch) {
|
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
258
|
|
|
254
|
-
//
|
|
259
|
+
// =============================================================================
|
|
260
|
+
// SHIM GENERATION
|
|
261
|
+
// =============================================================================
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Load packages manifest (generated at build time from registry catalog)
|
|
265
|
+
* Falls back to empty manifest if not found
|
|
266
|
+
*/
|
|
267
|
+
function loadPackagesManifest() {
|
|
268
|
+
const possiblePaths = [
|
|
269
|
+
// When running from npm install (dist directory)
|
|
270
|
+
path.join(path.dirname(process.argv[1]), '..', 'dist', 'packages-manifest.json'),
|
|
271
|
+
// When running from source/dev
|
|
272
|
+
path.join(path.dirname(process.argv[1]), '..', 'src', 'packages-manifest.json'),
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
for (const manifestPath of possiblePaths) {
|
|
276
|
+
try {
|
|
277
|
+
if (fs.existsSync(manifestPath)) {
|
|
278
|
+
const content = fs.readFileSync(manifestPath, 'utf-8');
|
|
279
|
+
return JSON.parse(content);
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
// Try next path
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log(' ⚠ packages-manifest.json not found, using minimal shims');
|
|
287
|
+
return { packages: { runtimes: [], agents: [], binaries: [] } };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Build a shim script that executes a target binary
|
|
292
|
+
* Shows helpful error if binary not installed
|
|
293
|
+
*/
|
|
294
|
+
function buildExecShim(targetPath, notInstalledMessage) {
|
|
295
|
+
const target = targetPath.replace(/"/g, '\\"');
|
|
296
|
+
const message = notInstalledMessage.replace(/"/g, '\\"');
|
|
297
|
+
return `#!/bin/sh
|
|
298
|
+
TARGET="${target}"
|
|
299
|
+
if [ ! -x "$TARGET" ]; then
|
|
300
|
+
echo "RUDI: ${message}" 1>&2
|
|
301
|
+
exit 127
|
|
302
|
+
fi
|
|
303
|
+
exec "$TARGET" "$@"
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Build a shim script that runs a binary with extra args prepended
|
|
309
|
+
* Used for things like "pip" which runs "python3 -m pip"
|
|
310
|
+
*/
|
|
311
|
+
function buildArgsShim(targetPath, args, notInstalledMessage) {
|
|
312
|
+
const target = targetPath.replace(/"/g, '\\"');
|
|
313
|
+
const argsStr = args.map(a => `"${a.replace(/"/g, '\\"')}"`).join(' ');
|
|
314
|
+
const message = notInstalledMessage.replace(/"/g, '\\"');
|
|
315
|
+
return `#!/bin/sh
|
|
316
|
+
TARGET="${target}"
|
|
317
|
+
if [ ! -x "$TARGET" ]; then
|
|
318
|
+
echo "RUDI: ${message}" 1>&2
|
|
319
|
+
exit 127
|
|
320
|
+
fi
|
|
321
|
+
exec "$TARGET" ${argsStr} "$@"
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Create all shims for runtimes, agents, and binaries
|
|
327
|
+
* Reads from packages-manifest.json (generated from registry catalog)
|
|
328
|
+
*/
|
|
255
329
|
function createShims() {
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
330
|
+
const platform = process.platform;
|
|
331
|
+
const isWindows = platform === 'win32';
|
|
332
|
+
|
|
333
|
+
// Skip shim creation on Windows (uses different mechanism)
|
|
334
|
+
if (isWindows) {
|
|
335
|
+
console.log(` ⚠ Shim creation skipped on Windows`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const manifest = loadPackagesManifest();
|
|
340
|
+
const shimDefs = [];
|
|
341
|
+
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// GENERATE SHIMS FROM MANIFEST (only for installed packages)
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
const allPackages = [
|
|
347
|
+
...manifest.packages.runtimes,
|
|
348
|
+
...manifest.packages.agents,
|
|
349
|
+
...manifest.packages.binaries,
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
for (const pkg of allPackages) {
|
|
353
|
+
const installPath = path.join(RUDI_HOME, pkg.basePath, pkg.installDir);
|
|
354
|
+
|
|
355
|
+
// Only create shims for packages that are actually installed
|
|
356
|
+
// Check if the install directory exists
|
|
357
|
+
if (!fs.existsSync(installPath)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const cmd of pkg.commands) {
|
|
362
|
+
const binPath = path.join(installPath, cmd.bin);
|
|
363
|
+
|
|
364
|
+
// Only create shim if the target binary exists
|
|
365
|
+
if (!fs.existsSync(binPath)) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (cmd.args && cmd.args.length > 0) {
|
|
370
|
+
// Command with prepended args (like pip -> python3 -m pip)
|
|
371
|
+
shimDefs.push({
|
|
372
|
+
name: cmd.name,
|
|
373
|
+
script: buildArgsShim(binPath, cmd.args, `${pkg.name} binary not found`),
|
|
374
|
+
});
|
|
375
|
+
} else {
|
|
376
|
+
// Simple exec shim
|
|
377
|
+
shimDefs.push({
|
|
378
|
+
name: cmd.name,
|
|
379
|
+
script: buildExecShim(binPath, `${pkg.name} binary not found`),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ---------------------------------------------------------------------------
|
|
386
|
+
// RUDI SHIMS (router, mcp) - always included
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
// rudi-mcp shim for direct stack access
|
|
390
|
+
shimDefs.push({
|
|
391
|
+
name: 'rudi-mcp',
|
|
392
|
+
script: `#!/bin/bash
|
|
259
393
|
# RUDI MCP Shim - Routes agent calls to rudi mcp command
|
|
260
394
|
# Usage: rudi-mcp <stack-name>
|
|
261
395
|
exec rudi mcp "$@"
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
fs.chmodSync(mcpShimPath, 0o755);
|
|
265
|
-
console.log(` ✓ Created rudi-mcp shim`);
|
|
396
|
+
`
|
|
397
|
+
});
|
|
266
398
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
399
|
+
// rudi-router shim for aggregated MCP server
|
|
400
|
+
shimDefs.push({
|
|
401
|
+
name: 'rudi-router',
|
|
402
|
+
script: `#!/bin/bash
|
|
270
403
|
# RUDI Router - Master MCP server for all installed stacks
|
|
271
404
|
# Reads ~/.rudi/rudi.json and proxies tool calls to correct stack
|
|
272
|
-
# Usage: Point agent config to this shim (no args needed)
|
|
273
|
-
|
|
274
405
|
RUDI_HOME="$HOME/.rudi"
|
|
275
|
-
|
|
276
|
-
# Use bundled Node if available
|
|
277
406
|
if [ -x "$RUDI_HOME/runtimes/node/bin/node" ]; then
|
|
278
407
|
exec "$RUDI_HOME/runtimes/node/bin/node" "$RUDI_HOME/router/router-mcp.js" "$@"
|
|
279
408
|
else
|
|
280
409
|
exec node "$RUDI_HOME/router/router-mcp.js" "$@"
|
|
281
410
|
fi
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
411
|
+
`
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
// WRITE ALL SHIMS
|
|
416
|
+
// ---------------------------------------------------------------------------
|
|
417
|
+
|
|
418
|
+
let created = 0;
|
|
419
|
+
for (const { name, script } of shimDefs) {
|
|
420
|
+
const shimPath = path.join(BINS_DIR, name);
|
|
421
|
+
fs.writeFileSync(shimPath, script, { encoding: 'utf-8' });
|
|
422
|
+
fs.chmodSync(shimPath, 0o755);
|
|
423
|
+
created++;
|
|
291
424
|
}
|
|
292
425
|
|
|
426
|
+
console.log(` ✓ Created ${created} shims in ~/.rudi/bins/`);
|
|
427
|
+
|
|
428
|
+
// ---------------------------------------------------------------------------
|
|
429
|
+
// ROUTER SETUP
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
|
|
293
432
|
// Create package.json for ES module support
|
|
433
|
+
const routerDir = path.join(RUDI_HOME, 'router');
|
|
294
434
|
const routerPackageJson = path.join(routerDir, 'package.json');
|
|
295
435
|
fs.writeFileSync(routerPackageJson, JSON.stringify({
|
|
296
436
|
name: 'rudi-router',
|