tingly-box 0.25.12201200-beta
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.js +398 -0
- package/package.json +32 -0
package/bin.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "child_process";
|
|
4
|
+
import { chmodSync, createWriteStream, existsSync, fsyncSync, mkdirSync } from "fs";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { Readable } from "stream";
|
|
8
|
+
|
|
9
|
+
// Configuration for binary downloads
|
|
10
|
+
const BASE_URL = "https://github.com/tingly-dev/tingly-box/releases/download/";
|
|
11
|
+
|
|
12
|
+
// GitHub API endpoint for getting latest release info
|
|
13
|
+
const LATEST_RELEASE_API_URL = "https://github.com/tingly-dev/tingly-box/releases/download/";
|
|
14
|
+
|
|
15
|
+
// Default branch to use when not specified via transport version
|
|
16
|
+
// This will be replaced during the NPX build process
|
|
17
|
+
const BINARY_RELEASE_BRANCH = 'v0.25.12201200-beta';
|
|
18
|
+
|
|
19
|
+
// Parse transport version from command line arguments
|
|
20
|
+
function parseTransportVersion() {
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
let transportVersion = "latest"; // Default to latest
|
|
23
|
+
|
|
24
|
+
// Find --transport-version argument
|
|
25
|
+
const versionArgIndex = args.findIndex((arg) => arg.startsWith("--transport-version"));
|
|
26
|
+
|
|
27
|
+
if (versionArgIndex !== -1) {
|
|
28
|
+
const versionArg = args[versionArgIndex];
|
|
29
|
+
|
|
30
|
+
if (versionArg.includes("=")) {
|
|
31
|
+
// Format: --transport-version=v1.2.3
|
|
32
|
+
transportVersion = versionArg.split("=")[1];
|
|
33
|
+
} else if (versionArgIndex + 1 < args.length) {
|
|
34
|
+
// Format: --transport-version v1.2.3
|
|
35
|
+
transportVersion = args[versionArgIndex + 1];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Remove the transport-version arguments from args array so they don't get passed to the binary
|
|
39
|
+
if (versionArg.includes("=")) {
|
|
40
|
+
args.splice(versionArgIndex, 1);
|
|
41
|
+
} else {
|
|
42
|
+
args.splice(versionArgIndex, 2);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { version: validateTransportVersion(transportVersion), remainingArgs: args };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validate transport version format
|
|
50
|
+
function validateTransportVersion(version) {
|
|
51
|
+
if (version === "latest") {
|
|
52
|
+
return version;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check if version matches v{x.x.x} format
|
|
56
|
+
const versionRegex = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
57
|
+
if (versionRegex.test(version)) {
|
|
58
|
+
return version;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.error(`Invalid transport version format: ${version}`);
|
|
62
|
+
console.error(`Transport version must be either "latest", "v1.2.3", or "v1.2.3-prerelease1"`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { version: VERSION, remainingArgs } = parseTransportVersion();
|
|
67
|
+
|
|
68
|
+
async function getPlatformArchAndBinary() {
|
|
69
|
+
const platform = process.platform;
|
|
70
|
+
const arch = process.arch;
|
|
71
|
+
|
|
72
|
+
let platformDir;
|
|
73
|
+
let archDir;
|
|
74
|
+
let binaryName;
|
|
75
|
+
binaryName = "tingly-box";
|
|
76
|
+
let suffix = ""
|
|
77
|
+
|
|
78
|
+
if (platform === "darwin") {
|
|
79
|
+
platformDir = "macos";
|
|
80
|
+
if (arch === "arm64") archDir = "arm64";
|
|
81
|
+
else archDir = "amd64";
|
|
82
|
+
} else if (platform === "linux") {
|
|
83
|
+
platformDir = "linux";
|
|
84
|
+
if (arch === "x64") archDir = "amd64";
|
|
85
|
+
else if (arch === "ia32") archDir = "386";
|
|
86
|
+
else archDir = arch; // fallback
|
|
87
|
+
} else if (platform === "win32") {
|
|
88
|
+
platformDir = "windows";
|
|
89
|
+
if (arch === "x64") archDir = "amd64";
|
|
90
|
+
else if (arch === "ia32") archDir = "386";
|
|
91
|
+
else archDir = arch; // fallback
|
|
92
|
+
suffix = ".exe";
|
|
93
|
+
} else {
|
|
94
|
+
console.error(`Unsupported platform/arch: ${platform}/${arch}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { platformDir, archDir, binaryName, suffix };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function downloadBinary(url, dest) {
|
|
102
|
+
// console.log(`🔄 Downloading binary from ${url}...`);
|
|
103
|
+
|
|
104
|
+
// Fetch with redirect following
|
|
105
|
+
const res = await fetch(url, {
|
|
106
|
+
redirect: 'follow', // Automatically follow redirects
|
|
107
|
+
headers: {
|
|
108
|
+
'User-Agent': 'tingly-box-npx'
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
console.error(`❌ Download failed: ${res.status} ${res.statusText}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const contentLength = res.headers.get("content-length");
|
|
118
|
+
const totalSize = contentLength ? parseInt(contentLength, 10) : null;
|
|
119
|
+
let downloadedSize = 0;
|
|
120
|
+
|
|
121
|
+
const fileStream = createWriteStream(dest, { flags: "w" });
|
|
122
|
+
await new Promise((resolve, reject) => {
|
|
123
|
+
try {
|
|
124
|
+
// Convert the fetch response body to a Node.js readable stream
|
|
125
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
126
|
+
|
|
127
|
+
// Add progress tracking
|
|
128
|
+
nodeStream.on("data", (chunk) => {
|
|
129
|
+
downloadedSize += chunk.length;
|
|
130
|
+
if (totalSize) {
|
|
131
|
+
const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
132
|
+
process.stdout.write(`\r⏱️ Downloading Binary: ${progress}% (${formatBytes(downloadedSize)}/${formatBytes(totalSize)})`);
|
|
133
|
+
} else {
|
|
134
|
+
process.stdout.write(`\r⏱️ Downloaded: ${formatBytes(downloadedSize)}`);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
nodeStream.pipe(fileStream);
|
|
139
|
+
fileStream.on("finish", () => {
|
|
140
|
+
process.stdout.write("\n");
|
|
141
|
+
|
|
142
|
+
// Ensure file is fully written to disk
|
|
143
|
+
try {
|
|
144
|
+
fsyncSync(fileStream.fd);
|
|
145
|
+
} catch (syncError) {
|
|
146
|
+
// fsync might fail on some systems, ignore
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
resolve();
|
|
150
|
+
});
|
|
151
|
+
fileStream.on("error", reject);
|
|
152
|
+
nodeStream.on("error", reject);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
reject(error);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
chmodSync(dest, 0o755);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function downloadAndExtractZip(url, extractDir, binaryName) {
|
|
162
|
+
console.log(`🔄 Downloading ZIP from ${url}...`);
|
|
163
|
+
|
|
164
|
+
// Fetch with redirect following
|
|
165
|
+
const res = await fetch(url, {
|
|
166
|
+
redirect: 'follow',
|
|
167
|
+
headers: {
|
|
168
|
+
'User-Agent': 'tingly-box-npx'
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!res.ok) {
|
|
173
|
+
console.error(`❌ Download failed: ${res.status} ${res.statusText}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Create a temporary file for the ZIP
|
|
178
|
+
const zipPath = join(tmpdir(), `tingly-box-${Date.now()}.zip`);
|
|
179
|
+
const fileStream = createWriteStream(zipPath, { flags: "w" });
|
|
180
|
+
|
|
181
|
+
const contentLength = res.headers.get("content-length");
|
|
182
|
+
const totalSize = contentLength ? parseInt(contentLength, 10) : null;
|
|
183
|
+
let downloadedSize = 0;
|
|
184
|
+
|
|
185
|
+
await new Promise((resolve, reject) => {
|
|
186
|
+
try {
|
|
187
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
188
|
+
|
|
189
|
+
nodeStream.on("data", (chunk) => {
|
|
190
|
+
downloadedSize += chunk.length;
|
|
191
|
+
if (totalSize) {
|
|
192
|
+
const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
193
|
+
process.stdout.write(`\r⏱️ Downloading ZIP: ${progress}% (${formatBytes(downloadedSize)}/${formatBytes(totalSize)})`);
|
|
194
|
+
} else {
|
|
195
|
+
process.stdout.write(`\r⏱️ Downloaded: ${formatBytes(downloadedSize)}`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
nodeStream.pipe(fileStream);
|
|
200
|
+
fileStream.on("finish", () => {
|
|
201
|
+
process.stdout.write("\n");
|
|
202
|
+
resolve();
|
|
203
|
+
});
|
|
204
|
+
fileStream.on("error", reject);
|
|
205
|
+
nodeStream.on("error", reject);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
reject(error);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Extract the ZIP file using system unzip command
|
|
212
|
+
try {
|
|
213
|
+
console.log(`📦 Extracting ZIP...`);
|
|
214
|
+
execFileSync("unzip", ["-q", "-o", zipPath, "-d", extractDir]);
|
|
215
|
+
console.log(`✅ Extracted ZIP to ${extractDir}`);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(`❌ Failed to extract ZIP: ${error.message}`);
|
|
218
|
+
// Fallback: try using Python to extract
|
|
219
|
+
try {
|
|
220
|
+
execFileSync("python3", ["-m", "zipfile", "-e", zipPath, extractDir]);
|
|
221
|
+
console.log(`✅ Extracted ZIP using Python`);
|
|
222
|
+
} catch (pythonError) {
|
|
223
|
+
console.error(`❌ Failed to extract ZIP with Python too: ${pythonError.message}`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Clean up the ZIP file
|
|
229
|
+
try {
|
|
230
|
+
execFileSync("rm", ["-f", zipPath]);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
// Ignore cleanup errors
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Returns the os cache directory path for storing binaries
|
|
237
|
+
// Linux: $XDG_CACHE_HOME or ~/.cache
|
|
238
|
+
// macOS: ~/Library/Caches
|
|
239
|
+
// Windows: %LOCALAPPDATA% or %USERPROFILE%\AppData\Local
|
|
240
|
+
function cacheDir() {
|
|
241
|
+
if (process.platform === "linux") {
|
|
242
|
+
return process.env.XDG_CACHE_HOME || join(process.env.HOME || "", ".cache");
|
|
243
|
+
}
|
|
244
|
+
if (process.platform === "darwin") {
|
|
245
|
+
return join(process.env.HOME || "", "Library", "Caches");
|
|
246
|
+
}
|
|
247
|
+
if (process.platform === "win32") {
|
|
248
|
+
return process.env.LOCALAPPDATA || join(process.env.USERPROFILE || "", "AppData", "Local");
|
|
249
|
+
}
|
|
250
|
+
console.error(`Unsupported platform/arch: ${process.platform}/${process.arch}`);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// gets the latest version number for transport
|
|
255
|
+
async function getLatestVersion() {
|
|
256
|
+
const releaseUrl = LATEST_RELEASE_API_URL;
|
|
257
|
+
const res = await fetch(releaseUrl);
|
|
258
|
+
if (!res.ok) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const data = await res.json();
|
|
262
|
+
return data.name;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function formatBytes(bytes) {
|
|
266
|
+
if (bytes === 0) return "0 B";
|
|
267
|
+
const k = 1024;
|
|
268
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
269
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
270
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
(async () => {
|
|
274
|
+
const platformInfo = await getPlatformArchAndBinary();
|
|
275
|
+
const { platformDir, archDir, binaryName, suffix } = platformInfo;
|
|
276
|
+
|
|
277
|
+
const namedVersion = VERSION === "latest" ? BINARY_RELEASE_BRANCH : VERSION;
|
|
278
|
+
|
|
279
|
+
// For the NPX package, we always use the configured branch or the specified version
|
|
280
|
+
const branchName = VERSION === "latest" ? BINARY_RELEASE_BRANCH : VERSION;
|
|
281
|
+
|
|
282
|
+
// Build ZIP download URL
|
|
283
|
+
const zipFileName = `${binaryName}-${platformDir}-${archDir}.zip`;
|
|
284
|
+
const downloadUrl = `${BASE_URL}/${branchName}/${zipFileName}`;
|
|
285
|
+
|
|
286
|
+
let lastError = null;
|
|
287
|
+
let binaryWorking = false;
|
|
288
|
+
|
|
289
|
+
// Use branch name for caching
|
|
290
|
+
const tinglyBinDir = join(cacheDir(), "tingly-box", branchName, "bin");
|
|
291
|
+
|
|
292
|
+
// Create the binary directory
|
|
293
|
+
try {
|
|
294
|
+
if (!existsSync(tinglyBinDir)) {
|
|
295
|
+
mkdirSync(tinglyBinDir, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
} catch (mkdirError) {
|
|
298
|
+
console.error(`❌ Failed to create directory ${tinglyBinDir}:`, mkdirError.message);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// The extracted binary path
|
|
303
|
+
const binaryPath = join(tinglyBinDir, `${binaryName}-${platformDir}-${archDir}${suffix}`);
|
|
304
|
+
|
|
305
|
+
// If binary doesn't exist, download and extract ZIP
|
|
306
|
+
if (!existsSync(binaryPath)) {
|
|
307
|
+
await downloadAndExtractZip(downloadUrl, tinglyBinDir, binaryName);
|
|
308
|
+
|
|
309
|
+
// Make sure the binary is executable
|
|
310
|
+
if (process.platform !== "win32") {
|
|
311
|
+
chmodSync(binaryPath, 0o755);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log(`✅ Downloaded and extracted to ${binaryPath}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Test if the binary can execute
|
|
318
|
+
// Debug: Show binary location
|
|
319
|
+
console.log(`🔍 Executing binary: ${binaryPath}`);
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
execFileSync(binaryPath, remainingArgs, {
|
|
323
|
+
stdio: "inherit",
|
|
324
|
+
encoding: 'utf8'
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// If we reach here, the binary executed successfully
|
|
328
|
+
binaryWorking = true;
|
|
329
|
+
|
|
330
|
+
// If execFileSync completes without throwing, the binary exited with code 0
|
|
331
|
+
// No need to explicitly exit here, let the script continue
|
|
332
|
+
} catch (execError) {
|
|
333
|
+
lastError = execError;
|
|
334
|
+
binaryWorking = false;
|
|
335
|
+
|
|
336
|
+
// Extract detailed error information
|
|
337
|
+
const errorCode = execError.code;
|
|
338
|
+
const errorSignal = execError.signal;
|
|
339
|
+
const errorMessage = execError.message;
|
|
340
|
+
const errorStatus = execError.status;
|
|
341
|
+
|
|
342
|
+
// Create comprehensive error output
|
|
343
|
+
console.error(`\n❌ Tingly-Box execution failed`);
|
|
344
|
+
console.error(`┌─ Error Details:`);
|
|
345
|
+
console.error(`│ Message: ${errorMessage}`);
|
|
346
|
+
|
|
347
|
+
if (errorCode) {
|
|
348
|
+
console.error(`│ Code: ${errorCode}`);
|
|
349
|
+
// Provide specific guidance for common error codes
|
|
350
|
+
switch (errorCode) {
|
|
351
|
+
case 'ENOENT':
|
|
352
|
+
console.error(`│ └─ Binary not found at: ${binaryPath}`);
|
|
353
|
+
console.error(`│ Try removing the cached binary: rm -rf "${join(cacheDir(), 'tingly-box')}"`);
|
|
354
|
+
break;
|
|
355
|
+
case 'EACCES':
|
|
356
|
+
console.error(`│ └─ Permission denied. Check binary permissions.`);
|
|
357
|
+
break;
|
|
358
|
+
case 'ETXTBSY':
|
|
359
|
+
console.error(`│ └─ Binary file is busy or being modified.`);
|
|
360
|
+
break;
|
|
361
|
+
default:
|
|
362
|
+
console.error(`│ └─ System error occurred.`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (errorStatus !== null && errorStatus !== undefined) {
|
|
367
|
+
console.error(`│ Exit Code: ${errorStatus}`);
|
|
368
|
+
console.error(`│ └─ The binary exited with non-zero status code.`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (errorSignal) {
|
|
372
|
+
console.error(`│ Signal: ${errorSignal}`);
|
|
373
|
+
console.error(`│ └─ The binary was terminated by a signal.`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
console.error(`└─ Binary Path: ${binaryPath}`);
|
|
377
|
+
console.error(` Platform: ${process.platform} (${process.arch})`);
|
|
378
|
+
|
|
379
|
+
// Provide additional help for common scenarios
|
|
380
|
+
if (process.platform === "linux") {
|
|
381
|
+
console.error(`\n💡 Linux Troubleshooting:`);
|
|
382
|
+
console.error(` • Check if required libraries are installed:`);
|
|
383
|
+
console.error(` - For glibc issues: try on a different Linux distribution`);
|
|
384
|
+
console.error(` - For missing dependencies: install required system packages`);
|
|
385
|
+
console.error(` • Try running with strace: strace -o trace.log "${binaryPath}"`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Suggest retry
|
|
389
|
+
console.error(`\n🔄 To retry, run: npx tingly-box ${remainingArgs.join(' ')}`);
|
|
390
|
+
console.error(` Or clear cache first: rm -rf "${join(cacheDir(), 'tingly-box')}"`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!binaryWorking) {
|
|
394
|
+
// Exit with the binary's exit code if available, otherwise default to 1
|
|
395
|
+
const exitCode = lastError.status !== undefined ? lastError.status : 1;
|
|
396
|
+
process.exit(exitCode);
|
|
397
|
+
}
|
|
398
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tingly-box",
|
|
3
|
+
"version": "0.25.12201200-beta",
|
|
4
|
+
"description": "High-performance AI gateway CLI - connect to multiple AI providers through a single API",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"gateway",
|
|
8
|
+
"openai",
|
|
9
|
+
"anthropic",
|
|
10
|
+
"claude",
|
|
11
|
+
"cli",
|
|
12
|
+
"tingly"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/tingly-dev/tingly-box",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/tingly-dev/tingly-box.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"author": "Tingly Dev",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"tingly-box": "bin.js"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"dependencies": {}
|
|
32
|
+
}
|