@ivybiosciences/ivybloom-cli 0.8.20
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 +76 -0
- package/package.json +52 -0
- package/scripts/install.js +334 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @ivybiosciences/ivybloom-cli
|
|
2
|
+
|
|
3
|
+
CLI/TUI for computational biology and drug discovery on the .bloom* platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @ivybiosciences/ivybloom-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with npx (no install):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @ivybiosciences/ivybloom-cli --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Authenticate with .bloom* platform
|
|
21
|
+
ivybloom auth login
|
|
22
|
+
|
|
23
|
+
# Launch interactive TUI
|
|
24
|
+
ivybloom tui
|
|
25
|
+
|
|
26
|
+
# Run protein structure prediction
|
|
27
|
+
ivybloom tools run esmfold --sequence "MKFLILLFN..."
|
|
28
|
+
|
|
29
|
+
# List available tools
|
|
30
|
+
ivybloom tools list
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Protein Structure Prediction** - ESMFold, AlphaFold integration
|
|
36
|
+
- **Drug Molecule Generation** - REINVENT, molecular design
|
|
37
|
+
- **Property Prediction** - ADMET, toxicity, solubility
|
|
38
|
+
- **Molecular Docking** - Protein-ligand binding analysis
|
|
39
|
+
- **Interactive TUI** - Full terminal interface with Charmbracelet
|
|
40
|
+
|
|
41
|
+
## Platform Support
|
|
42
|
+
|
|
43
|
+
| Platform | Architecture |
|
|
44
|
+
|----------|--------------|
|
|
45
|
+
| macOS | x64, arm64 |
|
|
46
|
+
| Linux | x64, arm64 |
|
|
47
|
+
| Windows | x64 |
|
|
48
|
+
|
|
49
|
+
## Environment Variables
|
|
50
|
+
|
|
51
|
+
| Variable | Description |
|
|
52
|
+
|----------|-------------|
|
|
53
|
+
| `IVYBLOOM_API_KEY` | API key for authentication |
|
|
54
|
+
| `IVYBLOOM_SKIP_INSTALL` | Set to `1` to skip binary download |
|
|
55
|
+
|
|
56
|
+
## Alternative Installation
|
|
57
|
+
|
|
58
|
+
### Go
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
go install github.com/ivybiosciences/ivybloom-cli/go@latest
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Direct Download
|
|
65
|
+
|
|
66
|
+
Download binaries from [GitHub Releases](https://github.com/ivybiosciences/ivybloom-cli/releases).
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
- [Full Documentation](https://docs.ivybloom.com)
|
|
71
|
+
- [API Reference](https://docs.ivybloom.com/api)
|
|
72
|
+
- [GitHub Repository](https://github.com/ivybiosciences/ivybloom-cli)
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT - Ivy Biosciences
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ivybiosciences/ivybloom-cli",
|
|
3
|
+
"version": "0.8.20",
|
|
4
|
+
"description": "CLI/TUI for computational biology and drug discovery on the .bloom* platform",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"tui",
|
|
8
|
+
"biology",
|
|
9
|
+
"bioinformatics",
|
|
10
|
+
"drug-discovery",
|
|
11
|
+
"protein",
|
|
12
|
+
"science",
|
|
13
|
+
"ivybloom",
|
|
14
|
+
"computational-biology",
|
|
15
|
+
"esmfold",
|
|
16
|
+
"molecular-design"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://ivybloom.com",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/ivybiosciences/ivybloom-cli/issues"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/ivybiosciences/ivybloom-cli.git"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "Ivy Biosciences <admin@ivybiosciences.com>",
|
|
28
|
+
"bin": {
|
|
29
|
+
"ivybloom": "bin/ivybloom"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin/",
|
|
33
|
+
"scripts/",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"postinstall": "node scripts/install.js",
|
|
38
|
+
"test": "node -e \"require('child_process').execSync('./bin/ivybloom --version', {stdio: 'inherit'})\""
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=16"
|
|
42
|
+
},
|
|
43
|
+
"os": [
|
|
44
|
+
"darwin",
|
|
45
|
+
"linux",
|
|
46
|
+
"win32"
|
|
47
|
+
],
|
|
48
|
+
"cpu": [
|
|
49
|
+
"x64",
|
|
50
|
+
"arm64"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync, spawnSync } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const { createWriteStream, chmodSync } = require("fs");
|
|
8
|
+
|
|
9
|
+
const BIN_NAME = "ivybloom";
|
|
10
|
+
const RELEASES_BASE_URL = "https://releases.ivybloom.com/releases";
|
|
11
|
+
|
|
12
|
+
// ANSI color codes for terminal output
|
|
13
|
+
const colors = {
|
|
14
|
+
reset: "\x1b[0m",
|
|
15
|
+
bright: "\x1b[1m",
|
|
16
|
+
dim: "\x1b[2m",
|
|
17
|
+
green: "\x1b[32m",
|
|
18
|
+
yellow: "\x1b[33m",
|
|
19
|
+
blue: "\x1b[34m",
|
|
20
|
+
magenta: "\x1b[35m",
|
|
21
|
+
cyan: "\x1b[36m",
|
|
22
|
+
red: "\x1b[31m",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Brand colors (approximated for terminal)
|
|
26
|
+
const brand = {
|
|
27
|
+
primary: colors.green,
|
|
28
|
+
accent: colors.cyan,
|
|
29
|
+
muted: colors.dim,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function log(message, color = colors.reset) {
|
|
33
|
+
console.log(`${color}${message}${colors.reset}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function logStep(step, message) {
|
|
37
|
+
console.log(`${brand.muted}[${step}]${colors.reset} ${message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function logSuccess(message) {
|
|
41
|
+
console.log(`${colors.green}✓${colors.reset} ${message}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function logError(message) {
|
|
45
|
+
console.log(`${colors.red}✗${colors.reset} ${message}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Map Node.js platform/arch to release asset names
|
|
49
|
+
const PLATFORM_MAP = {
|
|
50
|
+
darwin: "darwin",
|
|
51
|
+
linux: "linux",
|
|
52
|
+
win32: "windows",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const ARCH_MAP = {
|
|
56
|
+
x64: "amd64",
|
|
57
|
+
arm64: "arm64",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const PLATFORM_DISPLAY = {
|
|
61
|
+
darwin: "macOS",
|
|
62
|
+
linux: "Linux",
|
|
63
|
+
win32: "Windows",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function getPlatformInfo() {
|
|
67
|
+
const platform = PLATFORM_MAP[process.platform];
|
|
68
|
+
const arch = ARCH_MAP[process.arch];
|
|
69
|
+
|
|
70
|
+
if (!platform || !arch) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Unsupported platform: ${process.platform}-${process.arch}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Windows doesn't support arm64 in our builds
|
|
77
|
+
if (platform === "windows" && arch === "arm64") {
|
|
78
|
+
throw new Error("Windows ARM64 is not supported. Please use x64.");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { platform, arch };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getAssetName(version, platform, arch) {
|
|
85
|
+
const ext = platform === "windows" ? ".zip" : ".tar.gz";
|
|
86
|
+
return `ivybloom_${version}_${platform}_${arch}${ext}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function getLatestVersion() {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
// Fetch latest version from releases CDN
|
|
92
|
+
const url = `${RELEASES_BASE_URL}/latest.txt`;
|
|
93
|
+
|
|
94
|
+
https
|
|
95
|
+
.get(url, { headers: { "User-Agent": "ivybloom-npm-installer" } }, (res) => {
|
|
96
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
97
|
+
// Follow redirect
|
|
98
|
+
https.get(res.headers.location, { headers: { "User-Agent": "ivybloom-npm-installer" } }, handleResponse);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
handleResponse(res);
|
|
102
|
+
|
|
103
|
+
function handleResponse(res) {
|
|
104
|
+
let data = "";
|
|
105
|
+
res.on("data", (chunk) => (data += chunk));
|
|
106
|
+
res.on("end", () => {
|
|
107
|
+
const version = data.trim().replace(/^v/, "");
|
|
108
|
+
if (version && /^\d+\.\d+\.\d+/.test(version)) {
|
|
109
|
+
resolve(version);
|
|
110
|
+
} else {
|
|
111
|
+
reject(new Error("No releases found"));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
.on("error", reject);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function downloadFile(url, dest, onProgress) {
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
const follow = (url) => {
|
|
123
|
+
https
|
|
124
|
+
.get(url, { headers: { "User-Agent": "ivybloom-npm-installer" } }, (res) => {
|
|
125
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
126
|
+
follow(res.headers.location);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (res.statusCode !== 200) {
|
|
131
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode}`));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const totalSize = parseInt(res.headers["content-length"], 10);
|
|
136
|
+
let downloadedSize = 0;
|
|
137
|
+
const file = createWriteStream(dest);
|
|
138
|
+
|
|
139
|
+
res.on("data", (chunk) => {
|
|
140
|
+
downloadedSize += chunk.length;
|
|
141
|
+
if (onProgress && totalSize) {
|
|
142
|
+
onProgress(downloadedSize, totalSize);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
res.pipe(file);
|
|
147
|
+
file.on("finish", () => {
|
|
148
|
+
file.close();
|
|
149
|
+
resolve();
|
|
150
|
+
});
|
|
151
|
+
file.on("error", (err) => {
|
|
152
|
+
fs.unlink(dest, () => {}); // Delete partial file
|
|
153
|
+
reject(err);
|
|
154
|
+
});
|
|
155
|
+
})
|
|
156
|
+
.on("error", reject);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
follow(url);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function formatBytes(bytes) {
|
|
164
|
+
if (bytes < 1024) return bytes + " B";
|
|
165
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
166
|
+
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function renderProgressBar(current, total, width = 30) {
|
|
170
|
+
const percent = Math.round((current / total) * 100);
|
|
171
|
+
const filled = Math.round((current / total) * width);
|
|
172
|
+
const empty = width - filled;
|
|
173
|
+
const bar = "█".repeat(filled) + "░".repeat(empty);
|
|
174
|
+
const size = `${formatBytes(current)}/${formatBytes(total)}`;
|
|
175
|
+
|
|
176
|
+
process.stdout.write(`\r ${brand.primary}${bar}${colors.reset} ${percent}% ${colors.dim}(${size})${colors.reset}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function extractArchive(archivePath, destDir, platform) {
|
|
180
|
+
if (platform === "windows") {
|
|
181
|
+
execSync(
|
|
182
|
+
`powershell -command "Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force"`,
|
|
183
|
+
{ stdio: "pipe" }
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, { stdio: "pipe" });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function verifyInstallation(binPath) {
|
|
191
|
+
try {
|
|
192
|
+
const result = spawnSync(binPath, ["--version"], {
|
|
193
|
+
encoding: "utf8",
|
|
194
|
+
timeout: 5000,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (result.status === 0) {
|
|
198
|
+
return result.stdout.trim() || result.stderr.trim();
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function printBanner() {
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(`${brand.primary}${colors.bright} ╭─────────────────────────────────────╮${colors.reset}`);
|
|
209
|
+
console.log(`${brand.primary}${colors.bright} │${colors.reset} ${brand.primary}IvyBloom CLI${colors.reset} ${brand.primary}${colors.bright}│${colors.reset}`);
|
|
210
|
+
console.log(`${brand.primary}${colors.bright} │${colors.reset} ${colors.dim}Computational Biology Platform${colors.reset} ${brand.primary}${colors.bright}│${colors.reset}`);
|
|
211
|
+
console.log(`${brand.primary}${colors.bright} ╰─────────────────────────────────────╯${colors.reset}`);
|
|
212
|
+
console.log();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function printSuccessBanner(version, binPath) {
|
|
216
|
+
console.log();
|
|
217
|
+
console.log(`${brand.primary}${colors.bright} ╭─────────────────────────────────────╮${colors.reset}`);
|
|
218
|
+
console.log(`${brand.primary}${colors.bright} │${colors.reset} ${colors.green}✓${colors.reset} Installation Complete ${brand.primary}${colors.bright}│${colors.reset}`);
|
|
219
|
+
console.log(`${brand.primary}${colors.bright} ╰─────────────────────────────────────╯${colors.reset}`);
|
|
220
|
+
console.log();
|
|
221
|
+
console.log(` ${colors.dim}Version:${colors.reset} ${version}`);
|
|
222
|
+
console.log(` ${colors.dim}Binary:${colors.reset} ${binPath}`);
|
|
223
|
+
console.log();
|
|
224
|
+
console.log(` ${colors.dim}Get started:${colors.reset}`);
|
|
225
|
+
console.log(` ${brand.accent}ivybloom --help${colors.reset} Show available commands`);
|
|
226
|
+
console.log(` ${brand.accent}ivybloom auth login${colors.reset} Authenticate with .bloom*`);
|
|
227
|
+
console.log(` ${brand.accent}ivybloom tui${colors.reset} Launch interactive interface`);
|
|
228
|
+
console.log();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function main() {
|
|
232
|
+
// Skip if IVYBLOOM_SKIP_INSTALL is set (for CI or manual installs)
|
|
233
|
+
if (process.env.IVYBLOOM_SKIP_INSTALL === "1") {
|
|
234
|
+
console.log("Skipping binary download (IVYBLOOM_SKIP_INSTALL=1)");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
printBanner();
|
|
240
|
+
|
|
241
|
+
const { platform, arch } = getPlatformInfo();
|
|
242
|
+
const platformDisplay = PLATFORM_DISPLAY[process.platform] || process.platform;
|
|
243
|
+
|
|
244
|
+
logStep("1/4", `Detected ${colors.cyan}${platformDisplay}${colors.reset} (${arch})`);
|
|
245
|
+
|
|
246
|
+
// Get version from package.json or fetch latest
|
|
247
|
+
const pkg = require("../package.json");
|
|
248
|
+
let version = pkg.version;
|
|
249
|
+
|
|
250
|
+
if (version === "0.0.0") {
|
|
251
|
+
logStep("2/4", "Fetching latest release...");
|
|
252
|
+
version = await getLatestVersion();
|
|
253
|
+
} else {
|
|
254
|
+
logStep("2/4", `Using version ${colors.cyan}v${version}${colors.reset}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const assetName = getAssetName(version, platform, arch);
|
|
258
|
+
const downloadUrl = `${RELEASES_BASE_URL}/${version}/${assetName}`;
|
|
259
|
+
|
|
260
|
+
const binDir = path.join(__dirname, "..", "bin");
|
|
261
|
+
const tmpDir = path.join(__dirname, "..", ".tmp");
|
|
262
|
+
const archivePath = path.join(tmpDir, assetName);
|
|
263
|
+
|
|
264
|
+
// Create directories
|
|
265
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
266
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
267
|
+
|
|
268
|
+
logStep("3/4", `Downloading ${colors.dim}${assetName}${colors.reset}`);
|
|
269
|
+
|
|
270
|
+
await downloadFile(downloadUrl, archivePath, (current, total) => {
|
|
271
|
+
renderProgressBar(current, total);
|
|
272
|
+
});
|
|
273
|
+
console.log(); // New line after progress bar
|
|
274
|
+
|
|
275
|
+
logStep("4/4", "Extracting and installing...");
|
|
276
|
+
extractArchive(archivePath, tmpDir, platform);
|
|
277
|
+
|
|
278
|
+
// Move binary to bin directory
|
|
279
|
+
const binName = platform === "windows" ? `${BIN_NAME}.exe` : BIN_NAME;
|
|
280
|
+
const srcBin = path.join(tmpDir, binName);
|
|
281
|
+
const destBin = path.join(binDir, binName);
|
|
282
|
+
|
|
283
|
+
// Check if extracted binary exists
|
|
284
|
+
if (!fs.existsSync(srcBin)) {
|
|
285
|
+
// Sometimes the binary is in a subdirectory
|
|
286
|
+
const extracted = fs.readdirSync(tmpDir);
|
|
287
|
+
for (const item of extracted) {
|
|
288
|
+
const itemPath = path.join(tmpDir, item);
|
|
289
|
+
if (fs.statSync(itemPath).isDirectory()) {
|
|
290
|
+
const nestedBin = path.join(itemPath, binName);
|
|
291
|
+
if (fs.existsSync(nestedBin)) {
|
|
292
|
+
fs.copyFileSync(nestedBin, destBin);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
fs.copyFileSync(srcBin, destBin);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!fs.existsSync(destBin)) {
|
|
302
|
+
throw new Error("Binary not found in archive");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (platform !== "windows") {
|
|
306
|
+
chmodSync(destBin, 0o755);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Cleanup temp directory
|
|
310
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
311
|
+
|
|
312
|
+
// Verify installation
|
|
313
|
+
const versionOutput = verifyInstallation(destBin);
|
|
314
|
+
if (versionOutput) {
|
|
315
|
+
logSuccess(`Binary verified: ${colors.dim}${versionOutput}${colors.reset}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
printSuccessBanner(version, destBin);
|
|
319
|
+
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.log();
|
|
322
|
+
logError(`Installation failed: ${error.message}`);
|
|
323
|
+
console.log();
|
|
324
|
+
console.log(` ${colors.dim}Manual installation:${colors.reset}`);
|
|
325
|
+
console.log(` ${brand.accent}https://ivybloom.com/downloads${colors.reset}`);
|
|
326
|
+
console.log();
|
|
327
|
+
console.log(` ${colors.dim}Or install via curl:${colors.reset}`);
|
|
328
|
+
console.log(` ${brand.accent}curl -fsSL https://ivybloom.com/install.sh | bash${colors.reset}`);
|
|
329
|
+
console.log();
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
main();
|