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.
Files changed (2) hide show
  1. package/bin.js +398 -0
  2. 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
+ }