beltsh 1.14.19 → 1.15.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.
Files changed (3) hide show
  1. package/install.js +80 -35
  2. package/package.json +5 -2
  3. package/run.js +11 -5
package/install.js CHANGED
@@ -4,7 +4,7 @@ const https = require("https");
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  const os = require("os");
7
- const { execSync } = require("child_process");
7
+ const zlib = require("zlib");
8
8
  const crypto = require("crypto");
9
9
 
10
10
  const MANIFEST_URL = "https://dist.inference.sh/cli/manifest.json";
@@ -22,7 +22,6 @@ if (!PLATFORM || !ARCH) {
22
22
  const KEY = `${PLATFORM}-${ARCH}`;
23
23
 
24
24
  function getInstallDir() {
25
- // Same logic as the curl installer
26
25
  if (process.platform === "win32") {
27
26
  return path.join(os.homedir(), ".local", "bin");
28
27
  }
@@ -51,6 +50,74 @@ function fetch(url) {
51
50
  });
52
51
  }
53
52
 
53
+ // Extract a single file matching `prefix` from a .zip buffer.
54
+ // ZIP central directory is at the end of the file — parse it to find entries.
55
+ function extractFromZip(buf, prefix) {
56
+ // Find End of Central Directory record (signature 0x06054b50)
57
+ let eocdOffset = -1;
58
+ for (let i = buf.length - 22; i >= 0; i--) {
59
+ if (buf.readUInt32LE(i) === 0x06054b50) {
60
+ eocdOffset = i;
61
+ break;
62
+ }
63
+ }
64
+ if (eocdOffset === -1) throw new Error("invalid zip: EOCD not found");
65
+
66
+ const cdOffset = buf.readUInt32LE(eocdOffset + 16);
67
+ const cdEntries = buf.readUInt16LE(eocdOffset + 10);
68
+
69
+ let pos = cdOffset;
70
+ for (let i = 0; i < cdEntries; i++) {
71
+ if (buf.readUInt32LE(pos) !== 0x02014b50) throw new Error("invalid zip: bad central dir");
72
+ const nameLen = buf.readUInt16LE(pos + 28);
73
+ const extraLen = buf.readUInt16LE(pos + 30);
74
+ const commentLen = buf.readUInt16LE(pos + 32);
75
+ const localOffset = buf.readUInt32LE(pos + 42);
76
+ const name = buf.toString("utf8", pos + 46, pos + 46 + nameLen);
77
+
78
+ if (name.startsWith(prefix)) {
79
+ // Read local file header at localOffset (signature 0x04034b50)
80
+ const lh = localOffset;
81
+ const lhNameLen = buf.readUInt16LE(lh + 26);
82
+ const lhExtraLen = buf.readUInt16LE(lh + 28);
83
+ const compMethod = buf.readUInt16LE(lh + 8);
84
+ const compSize = buf.readUInt32LE(lh + 18);
85
+ const dataStart = lh + 30 + lhNameLen + lhExtraLen;
86
+ const compressed = buf.subarray(dataStart, dataStart + compSize);
87
+
88
+ if (compMethod === 0) return { name, data: compressed };
89
+ if (compMethod === 8) return { name, data: zlib.inflateRawSync(compressed) };
90
+ throw new Error(`unsupported zip compression method: ${compMethod}`);
91
+ }
92
+
93
+ pos += 46 + nameLen + extraLen + commentLen;
94
+ }
95
+ return null;
96
+ }
97
+
98
+ // Extract a single file matching `prefix` from a .tar.gz buffer.
99
+ function extractFromTarGz(buf, prefix) {
100
+ const tar = zlib.gunzipSync(buf);
101
+ let offset = 0;
102
+ while (offset < tar.length - 512) {
103
+ const header = tar.subarray(offset, offset + 512);
104
+ if (header[0] === 0) break; // end of archive
105
+
106
+ const name = header.toString("utf8", 0, 100).replace(/\0.*/, "");
107
+ const sizeStr = header.toString("utf8", 124, 136).replace(/\0.*/, "").trim();
108
+ const size = parseInt(sizeStr, 8) || 0;
109
+ const dataStart = offset + 512;
110
+
111
+ if (name.startsWith(prefix)) {
112
+ return { name, data: tar.subarray(dataStart, dataStart + size) };
113
+ }
114
+
115
+ // Advance past header + data (rounded up to 512-byte blocks)
116
+ offset = dataStart + Math.ceil(size / 512) * 512;
117
+ }
118
+ return null;
119
+ }
120
+
54
121
  async function main() {
55
122
  const installDir = getInstallDir();
56
123
  console.log(`Downloading belt for ${PLATFORM}/${ARCH}...`);
@@ -71,42 +138,25 @@ async function main() {
71
138
  process.exit(1);
72
139
  }
73
140
 
74
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "belt-"));
75
- const archivePath = path.join(tmpDir, path.basename(build.url));
76
- fs.writeFileSync(archivePath, archive);
77
-
78
- // Extract
79
- if (archivePath.endsWith(".zip")) {
80
- if (process.platform === "win32") {
81
- // bsdtar ships with Windows 10+ and extracts zip natively; unzip does not exist on Windows
82
- execSync(`tar -xf "${archivePath}" -C "${tmpDir}"`, { stdio: "pipe" });
83
- } else {
84
- execSync(`unzip -o "${archivePath}" -d "${tmpDir}"`, { stdio: "pipe" });
85
- }
86
- } else {
87
- execSync(`tar -xzf "${archivePath}" -C "${tmpDir}"`, { stdio: "pipe" });
88
- }
141
+ // Extract binary — pure Node, no shell commands, no MSYS path mangling
142
+ const entry = build.url.endsWith(".zip")
143
+ ? extractFromZip(archive, "inferencesh-cli")
144
+ : extractFromTarGz(archive, "inferencesh-cli");
89
145
 
90
- // Find the extracted binary
91
- const ext = PLATFORM === "windows" ? ".exe" : "";
92
- const files = fs.readdirSync(tmpDir).filter((f) => f.startsWith("inferencesh-cli"));
93
- if (files.length === 0) {
146
+ if (!entry) {
94
147
  console.error("Binary not found in archive");
95
148
  process.exit(1);
96
149
  }
97
150
 
98
151
  // Install to persistent location
152
+ const ext = PLATFORM === "windows" ? ".exe" : "";
99
153
  fs.mkdirSync(installDir, { recursive: true });
100
- const src = path.join(tmpDir, files[0]);
101
154
  const dest = path.join(installDir, `belt${ext}`);
102
155
 
103
156
  // Remove any existing file/symlink first — copyFileSync follows symlinks,
104
157
  // which corrupts installs where an older installer left belt as a symlink.
105
158
  try { fs.unlinkSync(dest); } catch {}
106
- fs.copyFileSync(src, dest);
107
- if (PLATFORM !== "windows") {
108
- fs.chmodSync(dest, 0o755);
109
- }
159
+ fs.writeFileSync(dest, entry.data, { mode: PLATFORM === "windows" ? undefined : 0o755 });
110
160
 
111
161
  // Create aliases (same as curl installer). Symlinks require elevation on
112
162
  // Windows, so fall back to copying the binary there.
@@ -120,21 +170,16 @@ async function main() {
120
170
  }
121
171
  }
122
172
 
123
- // Clean up temp
124
- fs.rmSync(tmpDir, { recursive: true });
125
-
126
- // Also install into npm bin/ so run.js works for npm install -g
173
+ // Also install into npm bin/ so run.js works for npx / npm install -g
127
174
  const npmBin = path.join(__dirname, "bin");
128
175
  fs.mkdirSync(npmBin, { recursive: true });
129
- fs.copyFileSync(dest, path.join(npmBin, `belt${ext}`));
130
- if (PLATFORM !== "windows") {
131
- fs.chmodSync(path.join(npmBin, `belt${ext}`), 0o755);
132
- }
176
+ const npmDest = path.join(npmBin, `belt${ext}`);
177
+ fs.writeFileSync(npmDest, entry.data, { mode: PLATFORM === "windows" ? undefined : 0o755 });
133
178
 
134
179
  console.log(`Installed belt ${manifest.version} to ${installDir}`);
135
180
  }
136
181
 
137
182
  main().catch((err) => {
138
- console.error("Installation failed:", err.message);
183
+ console.error("belt installation failed:", err.message);
139
184
  process.exit(1);
140
185
  });
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "beltsh",
3
- "version": "1.14.19",
3
+ "version": "1.15.0",
4
4
  "description": "inference.sh CLI — run AI apps, manage skills, connect MCP servers",
5
5
  "homepage": "https://inference.sh",
6
- "repository": "https://github.com/inference-sh/cli",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/inference-sh/cli.git"
9
+ },
7
10
  "license": "MIT",
8
11
  "bin": {
9
12
  "belt": "run.js",
package/run.js CHANGED
@@ -1,18 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { spawn } = require("child_process");
4
+ const { existsSync } = require("fs");
4
5
  const path = require("path");
5
6
 
6
7
  const ext = process.platform === "win32" ? ".exe" : "";
7
8
  const bin = path.join(__dirname, "bin", `belt${ext}`);
8
9
 
10
+ if (!existsSync(bin)) {
11
+ console.error(
12
+ "belt binary not found at " + bin + "\n" +
13
+ "This can happen on Git Bash/MSYS — install directly instead:\n" +
14
+ " curl -fsSL https://dist.inference.sh/install.sh | sh"
15
+ );
16
+ process.exit(1);
17
+ }
18
+
9
19
  const child = spawn(bin, process.argv.slice(2), { stdio: "inherit" });
10
20
  child.on("error", (err) => {
11
- if (err.code === "ENOENT") {
12
- console.error("belt binary not found. Try reinstalling: npm install @inferencesh/belt");
13
- } else {
14
- console.error(err.message);
15
- }
21
+ console.error(err.message);
16
22
  process.exit(1);
17
23
  });
18
24
  child.on("exit", (code) => process.exit(code ?? 1));