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