@tomagranate/toolui 0.2.6 → 0.3.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/bin/toolui-linux-x64 +0 -0
- package/package.json +1 -1
- package/scripts/postinstall.cjs +214 -72
package/bin/toolui-linux-x64
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/scripts/postinstall.cjs
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* Downloads the appropriate binary for the current platform.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const https = require("node:https");
|
|
9
8
|
const fs = require("node:fs");
|
|
10
9
|
const path = require("node:path");
|
|
11
10
|
const { execSync } = require("node:child_process");
|
|
@@ -13,6 +12,7 @@ const zlib = require("node:zlib");
|
|
|
13
12
|
|
|
14
13
|
// Configuration
|
|
15
14
|
const REPO = "tomagranate/toolui";
|
|
15
|
+
const R2_CDN = "https://toolui-releases.jetsail.xyz";
|
|
16
16
|
const GITHUB_RELEASES = `https://github.com/${REPO}/releases`;
|
|
17
17
|
|
|
18
18
|
// Platform mappings
|
|
@@ -27,44 +27,143 @@ const ARCH_MAP = {
|
|
|
27
27
|
x64: "x64",
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
// Colors (respects NO_COLOR env var)
|
|
31
|
+
const useColor = !process.env.NO_COLOR && process.stdout.isTTY;
|
|
32
|
+
const colors = {
|
|
33
|
+
reset: useColor ? "\x1b[0m" : "",
|
|
34
|
+
bold: useColor ? "\x1b[1m" : "",
|
|
35
|
+
dim: useColor ? "\x1b[2m" : "",
|
|
36
|
+
cyan: useColor ? "\x1b[36m" : "",
|
|
37
|
+
green: useColor ? "\x1b[32m" : "",
|
|
38
|
+
yellow: useColor ? "\x1b[33m" : "",
|
|
39
|
+
red: useColor ? "\x1b[31m" : "",
|
|
40
|
+
};
|
|
41
|
+
|
|
30
42
|
/**
|
|
31
|
-
*
|
|
43
|
+
* Print styled header
|
|
32
44
|
*/
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
res.statusCode >= 300 &&
|
|
48
|
-
res.statusCode < 400 &&
|
|
49
|
-
res.headers.location
|
|
50
|
-
) {
|
|
51
|
-
return httpsGet(res.headers.location, options)
|
|
52
|
-
.then(resolve)
|
|
53
|
-
.catch(reject);
|
|
54
|
-
}
|
|
45
|
+
function printHeader() {
|
|
46
|
+
console.log();
|
|
47
|
+
console.log(
|
|
48
|
+
`${colors.cyan}${colors.bold} ╭─────────────────────────────────╮${colors.reset}`,
|
|
49
|
+
);
|
|
50
|
+
console.log(
|
|
51
|
+
`${colors.cyan}${colors.bold} │ toolui postinstall │${colors.reset}`,
|
|
52
|
+
);
|
|
53
|
+
console.log(
|
|
54
|
+
`${colors.cyan}${colors.bold} ╰─────────────────────────────────╯${colors.reset}`,
|
|
55
|
+
);
|
|
56
|
+
console.log();
|
|
57
|
+
}
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Print step message
|
|
61
|
+
*/
|
|
62
|
+
function step(msg) {
|
|
63
|
+
console.log(` ${colors.cyan}▸${colors.reset} ${msg}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Print substep message
|
|
68
|
+
*/
|
|
69
|
+
function substep(msg) {
|
|
70
|
+
console.log(` ${colors.dim}${msg}${colors.reset}`);
|
|
71
|
+
}
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Print success message
|
|
75
|
+
*/
|
|
76
|
+
function success(msg) {
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(
|
|
79
|
+
`${colors.green}${colors.bold} ╭─────────────────────────────────╮${colors.reset}`,
|
|
80
|
+
);
|
|
81
|
+
console.log(
|
|
82
|
+
`${colors.green}${colors.bold} │${colors.reset} ${colors.green}✓${colors.reset} ${msg.padEnd(27)} ${colors.green}${colors.bold}│${colors.reset}`,
|
|
83
|
+
);
|
|
84
|
+
console.log(
|
|
85
|
+
`${colors.green}${colors.bold} ╰─────────────────────────────────╯${colors.reset}`,
|
|
86
|
+
);
|
|
87
|
+
console.log();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Print error message
|
|
92
|
+
*/
|
|
93
|
+
function printError(msg) {
|
|
94
|
+
console.error(` ${colors.red}✗${colors.reset} ${msg}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Format bytes as human-readable string
|
|
99
|
+
*/
|
|
100
|
+
function formatBytes(bytes) {
|
|
101
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
102
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
103
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Show download progress bar
|
|
108
|
+
*/
|
|
109
|
+
function showProgress(downloaded, total) {
|
|
110
|
+
if (!process.stdout.isTTY) return;
|
|
111
|
+
|
|
112
|
+
const width = 24;
|
|
113
|
+
if (total) {
|
|
114
|
+
const percent = Math.min(100, (downloaded / total) * 100);
|
|
115
|
+
const filled = Math.floor((percent / 100) * width);
|
|
116
|
+
const empty = width - filled;
|
|
117
|
+
const bar = "█".repeat(filled) + "░".repeat(empty);
|
|
118
|
+
const percentStr = percent.toFixed(0).padStart(3);
|
|
119
|
+
process.stdout.write(
|
|
120
|
+
`\r ${colors.dim}${bar}${colors.reset} ${percentStr}% ${colors.dim}(${formatBytes(downloaded)})${colors.reset}`,
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
process.stdout.write(
|
|
124
|
+
`\r ${colors.dim}Downloading: ${formatBytes(downloaded)}${colors.reset}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Clear progress line
|
|
131
|
+
*/
|
|
132
|
+
function clearProgress() {
|
|
133
|
+
if (!process.stdout.isTTY) return;
|
|
134
|
+
process.stdout.write("\r" + " ".repeat(70) + "\r");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Download file with streaming and progress
|
|
139
|
+
*/
|
|
140
|
+
async function downloadWithProgress(url) {
|
|
141
|
+
const response = await fetch(url, {
|
|
142
|
+
headers: { "User-Agent": "toolui-postinstall" },
|
|
67
143
|
});
|
|
144
|
+
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
throw new Error(`HTTP ${response.status}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const contentLength = response.headers.get("content-length");
|
|
150
|
+
const total = contentLength ? parseInt(contentLength, 10) : null;
|
|
151
|
+
let downloaded = 0;
|
|
152
|
+
|
|
153
|
+
const chunks = [];
|
|
154
|
+
const reader = response.body.getReader();
|
|
155
|
+
|
|
156
|
+
while (true) {
|
|
157
|
+
const { done, value } = await reader.read();
|
|
158
|
+
if (done) break;
|
|
159
|
+
|
|
160
|
+
chunks.push(value);
|
|
161
|
+
downloaded += value.length;
|
|
162
|
+
showProgress(downloaded, total);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
clearProgress();
|
|
166
|
+
return Buffer.concat(chunks);
|
|
68
167
|
}
|
|
69
168
|
|
|
70
169
|
/**
|
|
@@ -78,13 +177,9 @@ function getVersion() {
|
|
|
78
177
|
}
|
|
79
178
|
|
|
80
179
|
/**
|
|
81
|
-
*
|
|
180
|
+
* Extract tar.gz archive to get the binary
|
|
82
181
|
*/
|
|
83
|
-
|
|
84
|
-
console.log(`Downloading from ${url}...`);
|
|
85
|
-
const data = await httpsGet(url);
|
|
86
|
-
|
|
87
|
-
// Extract tar.gz
|
|
182
|
+
function extractTarGz(data, destPath) {
|
|
88
183
|
return new Promise((resolve, reject) => {
|
|
89
184
|
const gunzip = zlib.createGunzip();
|
|
90
185
|
const chunks = [];
|
|
@@ -92,20 +187,16 @@ async function downloadBinary(url, destPath) {
|
|
|
92
187
|
gunzip.on("data", (chunk) => chunks.push(chunk));
|
|
93
188
|
gunzip.on("end", () => {
|
|
94
189
|
const tarData = Buffer.concat(chunks);
|
|
95
|
-
// Simple tar extraction - find the file content
|
|
96
|
-
// tar format: 512-byte header blocks followed by file content
|
|
97
190
|
let offset = 0;
|
|
98
191
|
while (offset < tarData.length) {
|
|
99
192
|
const header = tarData.slice(offset, offset + 512);
|
|
100
|
-
if (header[0] === 0) break;
|
|
193
|
+
if (header[0] === 0) break;
|
|
101
194
|
|
|
102
|
-
// Get filename (bytes 0-99)
|
|
103
195
|
const filename = header
|
|
104
196
|
.slice(0, 100)
|
|
105
197
|
.toString("utf-8")
|
|
106
198
|
.replace(/\0/g, "");
|
|
107
199
|
|
|
108
|
-
// Get file size (bytes 124-135, octal)
|
|
109
200
|
const sizeStr = header
|
|
110
201
|
.slice(124, 136)
|
|
111
202
|
.toString("utf-8")
|
|
@@ -113,7 +204,7 @@ async function downloadBinary(url, destPath) {
|
|
|
113
204
|
.trim();
|
|
114
205
|
const size = parseInt(sizeStr, 8) || 0;
|
|
115
206
|
|
|
116
|
-
offset += 512;
|
|
207
|
+
offset += 512;
|
|
117
208
|
|
|
118
209
|
if (filename && size > 0 && filename.startsWith("toolui")) {
|
|
119
210
|
const content = tarData.slice(offset, offset + size);
|
|
@@ -123,10 +214,9 @@ async function downloadBinary(url, destPath) {
|
|
|
123
214
|
return;
|
|
124
215
|
}
|
|
125
216
|
|
|
126
|
-
// Move to next file (content is padded to 512-byte blocks)
|
|
127
217
|
offset += Math.ceil(size / 512) * 512;
|
|
128
218
|
}
|
|
129
|
-
reject(new Error("
|
|
219
|
+
reject(new Error("Binary not found in archive"));
|
|
130
220
|
});
|
|
131
221
|
gunzip.on("error", reject);
|
|
132
222
|
|
|
@@ -135,17 +225,22 @@ async function downloadBinary(url, destPath) {
|
|
|
135
225
|
}
|
|
136
226
|
|
|
137
227
|
/**
|
|
138
|
-
* Download
|
|
228
|
+
* Download and extract Unix binary
|
|
229
|
+
*/
|
|
230
|
+
async function downloadBinary(url, destPath) {
|
|
231
|
+
const data = await downloadWithProgress(url);
|
|
232
|
+
await extractTarGz(data, destPath);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Download and extract Windows binary
|
|
139
237
|
*/
|
|
140
238
|
async function downloadWindowsBinary(url, destPath) {
|
|
141
|
-
|
|
142
|
-
const data = await httpsGet(url);
|
|
239
|
+
const data = await downloadWithProgress(url);
|
|
143
240
|
|
|
144
|
-
// Write zip to temp file
|
|
145
241
|
const zipPath = `${destPath}.zip`;
|
|
146
242
|
fs.writeFileSync(zipPath, data);
|
|
147
243
|
|
|
148
|
-
// Use unzip if available, otherwise try PowerShell
|
|
149
244
|
try {
|
|
150
245
|
if (process.platform === "win32") {
|
|
151
246
|
execSync(
|
|
@@ -162,14 +257,45 @@ async function downloadWindowsBinary(url, destPath) {
|
|
|
162
257
|
}
|
|
163
258
|
}
|
|
164
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Try downloading from multiple URLs with fallback
|
|
262
|
+
*/
|
|
263
|
+
async function downloadWithFallback(urls, destPath, isWindows) {
|
|
264
|
+
let lastError;
|
|
265
|
+
|
|
266
|
+
for (let i = 0; i < urls.length; i++) {
|
|
267
|
+
const url = urls[i];
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
if (isWindows) {
|
|
271
|
+
await downloadWindowsBinary(url, destPath);
|
|
272
|
+
} else {
|
|
273
|
+
await downloadBinary(url, destPath);
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
lastError = error;
|
|
278
|
+
if (i < urls.length - 1) {
|
|
279
|
+
substep("Falling back to GitHub releases...");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
throw lastError;
|
|
285
|
+
}
|
|
286
|
+
|
|
165
287
|
async function main() {
|
|
166
288
|
const platform = PLATFORM_MAP[process.platform];
|
|
167
289
|
const arch = ARCH_MAP[process.arch];
|
|
168
290
|
|
|
169
291
|
if (!platform || !arch) {
|
|
170
|
-
console.log(
|
|
171
|
-
console.log(
|
|
172
|
-
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(
|
|
294
|
+
` ${colors.yellow}!${colors.reset} Unsupported platform: ${process.platform}-${process.arch}`,
|
|
295
|
+
);
|
|
296
|
+
console.log(` Build from source or download manually.`);
|
|
297
|
+
console.log();
|
|
298
|
+
process.exit(0);
|
|
173
299
|
}
|
|
174
300
|
|
|
175
301
|
const version = getVersion();
|
|
@@ -182,36 +308,52 @@ async function main() {
|
|
|
182
308
|
|
|
183
309
|
// Skip if binary already exists
|
|
184
310
|
if (fs.existsSync(destPath)) {
|
|
185
|
-
console.log(
|
|
311
|
+
console.log();
|
|
312
|
+
console.log(` ${colors.dim}Binary already installed${colors.reset}`);
|
|
313
|
+
console.log();
|
|
186
314
|
return;
|
|
187
315
|
}
|
|
188
316
|
|
|
317
|
+
printHeader();
|
|
318
|
+
|
|
319
|
+
step(`Platform: ${colors.bold}${platform}-${arch}${colors.reset}`);
|
|
320
|
+
step(`Version: ${colors.bold}v${version}${colors.reset}`);
|
|
321
|
+
console.log();
|
|
322
|
+
|
|
189
323
|
// Ensure bin directory exists
|
|
190
324
|
if (!fs.existsSync(binDir)) {
|
|
191
325
|
fs.mkdirSync(binDir, { recursive: true });
|
|
192
326
|
}
|
|
193
327
|
|
|
194
328
|
const archiveExt = platform === "windows" ? "zip" : "tar.gz";
|
|
195
|
-
|
|
329
|
+
|
|
330
|
+
const urls = [
|
|
331
|
+
`${R2_CDN}/v${version}/${binaryName}.${archiveExt}`,
|
|
332
|
+
`${GITHUB_RELEASES}/download/v${version}/${binaryName}.${archiveExt}`,
|
|
333
|
+
];
|
|
196
334
|
|
|
197
335
|
try {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
336
|
+
step("Downloading binary...");
|
|
337
|
+
await downloadWithFallback(urls, destPath, platform === "windows");
|
|
338
|
+
|
|
339
|
+
step("Extracting...");
|
|
340
|
+
success(`toolui v${version} ready`);
|
|
341
|
+
|
|
342
|
+
console.log(` ${colors.dim}Get started:${colors.reset}`);
|
|
343
|
+
console.log(` ${colors.cyan}$${colors.reset} toolui init`);
|
|
344
|
+
console.log(` ${colors.cyan}$${colors.reset} toolui`);
|
|
345
|
+
console.log();
|
|
204
346
|
} catch (error) {
|
|
205
|
-
|
|
206
|
-
console.
|
|
207
|
-
console.
|
|
208
|
-
console.
|
|
209
|
-
console.
|
|
210
|
-
console.
|
|
211
|
-
console.
|
|
347
|
+
printError(`Download failed: ${error.message}`);
|
|
348
|
+
console.log();
|
|
349
|
+
console.log(` ${colors.dim}Manual download:${colors.reset}`);
|
|
350
|
+
console.log(` ${GITHUB_RELEASES}/latest`);
|
|
351
|
+
console.log();
|
|
352
|
+
console.log(` ${colors.dim}Or use install script:${colors.reset}`);
|
|
353
|
+
console.log(
|
|
212
354
|
` curl -fsSL https://raw.githubusercontent.com/${REPO}/main/install.sh | bash`,
|
|
213
355
|
);
|
|
214
|
-
|
|
356
|
+
console.log();
|
|
215
357
|
process.exit(0);
|
|
216
358
|
}
|
|
217
359
|
}
|