@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 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();