tingly-box-nightly 0.20260119.702-nightly

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 +463 -0
  2. package/package.json +35 -0
package/bin.js ADDED
@@ -0,0 +1,463 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync } from "child_process";
4
+ import { chmodSync, createWriteStream, existsSync, fsyncSync, mkdirSync, statSync } from "fs";
5
+ import { join } from "path";
6
+ import { Readable } from "stream";
7
+ import { ProxyAgent } from "undici";
8
+ import unzipper from "unzipper";
9
+
10
+ // Configuration for binary downloads
11
+ const BASE_URL = "https://github.com/tingly-dev/tingly-box/releases/download/";
12
+
13
+ // GitHub API endpoint for getting latest release info
14
+ const LATEST_RELEASE_API_URL = "https://github.com/tingly-dev/tingly-box/releases/download/";
15
+
16
+ // Default branch to use when not specified via transport version
17
+ // This will be replaced during the NPX build process
18
+ const BINARY_RELEASE_BRANCH = '0.20260119.0702-nightly+6f0bf9ae';
19
+
20
+ // Create proxy agent from environment variables (HTTP_PROXY, HTTPS_PROXY)
21
+ // Only create ProxyAgent if proxy is configured, otherwise use undefined (direct connection)
22
+ const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
23
+ const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
24
+ const proxyUri = httpsProxy || httpProxy;
25
+ const dispatcher = proxyUri ? new ProxyAgent(proxyUri) : undefined;
26
+
27
+ // Parse transport version from command line arguments
28
+ function parseTransportVersion() {
29
+ const args = process.argv.slice(2);
30
+ let transportVersion = "latest"; // Default to latest
31
+
32
+ // Find --transport-version argument
33
+ const versionArgIndex = args.findIndex((arg) => arg.startsWith("--transport-version"));
34
+
35
+ if (versionArgIndex !== -1) {
36
+ const versionArg = args[versionArgIndex];
37
+
38
+ if (versionArg.includes("=")) {
39
+ // Format: --transport-version=v1.2.3
40
+ transportVersion = versionArg.split("=")[1];
41
+ } else if (versionArgIndex + 1 < args.length) {
42
+ // Format: --transport-version v1.2.3
43
+ transportVersion = args[versionArgIndex + 1];
44
+ }
45
+
46
+ // Remove the transport-version arguments from args array so they don't get passed to the binary
47
+ if (versionArg.includes("=")) {
48
+ args.splice(versionArgIndex, 1);
49
+ } else {
50
+ args.splice(versionArgIndex, 2);
51
+ }
52
+ }
53
+
54
+ return { version: validateTransportVersion(transportVersion), remainingArgs: args };
55
+ }
56
+
57
+ // Validate transport version format
58
+ function validateTransportVersion(version) {
59
+ if (version === "latest") {
60
+ return version;
61
+ }
62
+
63
+ // Check if version matches v{x.x.x} format
64
+ const versionRegex = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
65
+ if (versionRegex.test(version)) {
66
+ return version;
67
+ }
68
+
69
+ console.error(`Invalid transport version format: ${version}`);
70
+ console.error(`Transport version must be either "latest", "v1.2.3", or "v1.2.3-prerelease1"`);
71
+ process.exit(1);
72
+ }
73
+
74
+ const { version: VERSION, remainingArgs } = parseTransportVersion();
75
+
76
+ // Default parameters to use when no arguments are provided
77
+ const DEFAULT_ARGS = [
78
+ // Add your default parameters here, e.g.:
79
+ "start",
80
+ "--daemon",
81
+ "--prompt-restart",
82
+ ];
83
+
84
+ async function getPlatformArchAndBinary() {
85
+ const platform = process.platform;
86
+ const arch = process.arch;
87
+
88
+ let platformDir;
89
+ let archDir;
90
+ let binaryName;
91
+ binaryName = "tingly-box";
92
+ let suffix = ""
93
+
94
+ if (platform === "darwin") {
95
+ platformDir = "macos";
96
+ if (arch === "arm64") archDir = "arm64";
97
+ else archDir = "amd64";
98
+ } else if (platform === "linux") {
99
+ platformDir = "linux";
100
+ if (arch === "x64") archDir = "amd64";
101
+ else if (arch === "ia32") archDir = "386";
102
+ else archDir = arch; // fallback
103
+ } else if (platform === "win32") {
104
+ platformDir = "windows";
105
+ if (arch === "x64") archDir = "amd64";
106
+ else if (arch === "ia32") archDir = "386";
107
+ else archDir = arch; // fallback
108
+ suffix = ".exe";
109
+ } else {
110
+ console.error(`Unsupported platform/arch: ${platform}/${arch}`);
111
+ process.exit(1);
112
+ }
113
+
114
+ return { platformDir, archDir, binaryName, suffix };
115
+ }
116
+
117
+ async function downloadBinary(url, dest) {
118
+ // console.log(`šŸ”„ Downloading binary from ${url}...`);
119
+
120
+ // Fetch with redirect following and optional proxy support
121
+ const fetchOptions = {
122
+ redirect: 'follow', // Automatically follow redirects
123
+ headers: {
124
+ 'User-Agent': 'tingly-box-npx'
125
+ }
126
+ };
127
+ if (dispatcher) {
128
+ fetchOptions.dispatcher = dispatcher;
129
+ }
130
+
131
+ const res = await fetch(url, fetchOptions);
132
+
133
+ if (!res.ok) {
134
+ console.error(`āŒ Download failed: ${res.status} ${res.statusText}`);
135
+ process.exit(1);
136
+ }
137
+
138
+ const contentLength = res.headers.get("content-length");
139
+ const totalSize = contentLength ? parseInt(contentLength, 10) : null;
140
+ let downloadedSize = 0;
141
+
142
+ const fileStream = createWriteStream(dest, { flags: "w" });
143
+ await new Promise((resolve, reject) => {
144
+ try {
145
+ // Convert the fetch response body to a Node.js readable stream
146
+ const nodeStream = Readable.fromWeb(res.body);
147
+
148
+ // Add progress tracking
149
+ nodeStream.on("data", (chunk) => {
150
+ downloadedSize += chunk.length;
151
+ if (totalSize) {
152
+ const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
153
+ process.stdout.write(`\rā±ļø Downloading Binary: ${progress}% (${formatBytes(downloadedSize)}/${formatBytes(totalSize)})`);
154
+ } else {
155
+ process.stdout.write(`\rā±ļø Downloaded: ${formatBytes(downloadedSize)}`);
156
+ }
157
+ });
158
+
159
+ nodeStream.pipe(fileStream);
160
+ fileStream.on("finish", () => {
161
+ process.stdout.write("\n");
162
+
163
+ // Ensure file is fully written to disk
164
+ try {
165
+ fsyncSync(fileStream.fd);
166
+ } catch (syncError) {
167
+ // fsync might fail on some systems, ignore
168
+ }
169
+
170
+ resolve();
171
+ });
172
+ fileStream.on("error", reject);
173
+ nodeStream.on("error", reject);
174
+ } catch (error) {
175
+ reject(error);
176
+ }
177
+ });
178
+
179
+ chmodSync(dest, 0o755);
180
+ }
181
+
182
+ async function downloadAndExtractZip(url, extractDir, binaryName) {
183
+ console.log(`šŸ”„ Downloading ZIP from ${url}...`);
184
+
185
+ // Fetch with redirect following and optional proxy support
186
+ const fetchOptions = {
187
+ redirect: 'follow',
188
+ headers: {
189
+ 'User-Agent': 'tingly-box-npx'
190
+ }
191
+ };
192
+ if (dispatcher) {
193
+ fetchOptions.dispatcher = dispatcher;
194
+ }
195
+
196
+ const res = await fetch(url, fetchOptions);
197
+
198
+ if (!res.ok) {
199
+ console.error(`āŒ Download failed: ${res.status} ${res.statusText}`);
200
+ process.exit(1);
201
+ }
202
+
203
+ const contentLength = res.headers.get("content-length");
204
+ const totalSize = contentLength ? parseInt(contentLength, 10) : null;
205
+ let downloadedSize = 0;
206
+
207
+ // Convert the fetch response body to a Node.js readable stream
208
+ const nodeStream = Readable.fromWeb(res.body);
209
+
210
+ // Collect the entire ZIP into a buffer
211
+ const chunks = [];
212
+ for await (const chunk of nodeStream) {
213
+ chunks.push(chunk);
214
+ downloadedSize += chunk.length;
215
+ if (totalSize) {
216
+ const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
217
+ process.stdout.write(`\rā±ļø Downloading: ${progress}% (${formatBytes(downloadedSize)}/${formatBytes(totalSize)})`);
218
+ } else {
219
+ process.stdout.write(`\rā±ļø Downloaded: ${formatBytes(downloadedSize)}`);
220
+ }
221
+ }
222
+ const zipBuffer = Buffer.concat(chunks);
223
+
224
+ // Extract ZIP from buffer using unzipper
225
+ try {
226
+ console.log(`\nšŸ“¦ Extracting ZIP to ${extractDir}...`);
227
+
228
+ const directory = await unzipper.Open.buffer(zipBuffer);
229
+
230
+ // Debug: List all entries in the ZIP
231
+ console.log(`šŸ“‹ ZIP contents (${directory.files.length} entries):`);
232
+ for (const file of directory.files) {
233
+ console.log(` ${file.type}: ${file.path} (permissions: ${file.unixPermissions?.toString(8)})`);
234
+ }
235
+
236
+ // Extract all files to the target directory
237
+ for (const file of directory.files) {
238
+ // Skip directory entries and __MACOSX metadata
239
+ if (file.type === 'Directory' || file.path.startsWith('__MACOSX/') || file.path.includes('.DS_Store')) {
240
+ console.log(`ā­ļø Skipping: ${file.path} (type: ${file.type})`);
241
+ continue;
242
+ }
243
+
244
+ const filePath = join(extractDir, file.path);
245
+ // Get parent directory of the file in the ZIP
246
+ const pathParts = file.path.split('/');
247
+ pathParts.pop(); // Remove the filename
248
+ const fileDir = pathParts.length > 0 ? join(extractDir, ...pathParts) : extractDir;
249
+
250
+ console.log(`šŸ“„ Extracting: ${file.path} -> ${filePath}`);
251
+
252
+ // Ensure parent directory exists
253
+ if (fileDir !== extractDir && !existsSync(fileDir)) {
254
+ mkdirSync(fileDir, { recursive: true });
255
+ }
256
+
257
+ // Remove existing directory if it exists (this was created incorrectly before)
258
+ if (existsSync(filePath) && statSync(filePath).isDirectory()) {
259
+ console.log(`🧹 Removing incorrect directory: ${filePath}`);
260
+ // Can't easily remove a directory in Node without fs.rm (Node 14.14+)
261
+ // Skip and let user clean up manually
262
+ console.log(`āš ļø Please manually remove: rm -rf "${filePath}"`);
263
+ continue;
264
+ }
265
+
266
+ // Extract file
267
+ const content = await file.buffer();
268
+ const fileStream = createWriteStream(filePath);
269
+ await new Promise((resolve, reject) => {
270
+ fileStream.write(content, (err) => {
271
+ if (err) reject(err);
272
+ else {
273
+ fileStream.end();
274
+ resolve();
275
+ }
276
+ });
277
+ });
278
+ // Set file permissions after writing
279
+ if (process.platform !== "win32") {
280
+ // Use ZIP permissions if available, otherwise default to 0o755 (executable)
281
+ const permissions = file.unixPermissions && file.unixPermissions > 0 ? file.unixPermissions : 0o755;
282
+ chmodSync(filePath, permissions);
283
+ }
284
+ }
285
+
286
+ console.log(`āœ… Extracted ZIP to ${extractDir}`);
287
+ } catch (error) {
288
+ console.error(`\nāŒ Failed to extract ZIP: ${error.message}`);
289
+ console.error(`Stack: ${error.stack}`);
290
+ process.exit(1);
291
+ }
292
+ }
293
+
294
+ // Returns the os cache directory path for storing binaries
295
+ // Linux: $XDG_CACHE_HOME or ~/.cache
296
+ // macOS: ~/Library/Caches
297
+ // Windows: %LOCALAPPDATA% or %USERPROFILE%\AppData\Local
298
+ function cacheDir() {
299
+ if (process.platform === "linux") {
300
+ return process.env.XDG_CACHE_HOME || join(process.env.HOME || "", ".cache");
301
+ }
302
+ if (process.platform === "darwin") {
303
+ return join(process.env.HOME || "", "Library", "Caches");
304
+ }
305
+ if (process.platform === "win32") {
306
+ return process.env.LOCALAPPDATA || join(process.env.USERPROFILE || "", "AppData", "Local");
307
+ }
308
+ console.error(`Unsupported platform/arch: ${process.platform}/${process.arch}`);
309
+ process.exit(1);
310
+ }
311
+
312
+ // gets the latest version number for transport
313
+ async function getLatestVersion() {
314
+ const releaseUrl = LATEST_RELEASE_API_URL;
315
+ const fetchOptions = {};
316
+ if (dispatcher) {
317
+ fetchOptions.dispatcher = dispatcher;
318
+ }
319
+ const res = await fetch(releaseUrl, fetchOptions);
320
+ if (!res.ok) {
321
+ return null;
322
+ }
323
+ const data = await res.json();
324
+ return data.name;
325
+ }
326
+
327
+ function formatBytes(bytes) {
328
+ if (bytes === 0) return "0 B";
329
+ const k = 1024;
330
+ const sizes = ["B", "KB", "MB", "GB"];
331
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
332
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
333
+ }
334
+
335
+ (async () => {
336
+ const platformInfo = await getPlatformArchAndBinary();
337
+ const { platformDir, archDir, binaryName, suffix } = platformInfo;
338
+
339
+ const namedVersion = VERSION === "latest" ? BINARY_RELEASE_BRANCH : VERSION;
340
+
341
+ // For the NPX package, we always use the configured branch or the specified version
342
+ const branchName = VERSION === "latest" ? BINARY_RELEASE_BRANCH : VERSION;
343
+
344
+ // Build ZIP download URL
345
+ const zipFileName = `${binaryName}-${platformDir}-${archDir}.zip`;
346
+ const downloadUrl = `${BASE_URL}/${branchName}/${zipFileName}`;
347
+
348
+ let lastError = null;
349
+ let binaryWorking = false;
350
+
351
+ // Use branch name for caching
352
+ const tinglyBinDir = join(cacheDir(), "tingly-box", branchName, "bin");
353
+
354
+ // Create the binary directory
355
+ try {
356
+ if (!existsSync(tinglyBinDir)) {
357
+ mkdirSync(tinglyBinDir, { recursive: true });
358
+ }
359
+ } catch (mkdirError) {
360
+ console.error(`āŒ Failed to create directory ${tinglyBinDir}:`, mkdirError.message);
361
+ process.exit(1);
362
+ }
363
+
364
+ // The extracted binary path
365
+ const binaryPath = join(tinglyBinDir, `${binaryName}-${platformDir}-${archDir}${suffix}`);
366
+
367
+ // If binary doesn't exist, download and extract ZIP
368
+ if (!existsSync(binaryPath)) {
369
+ await downloadAndExtractZip(downloadUrl, tinglyBinDir, binaryName);
370
+
371
+ // Make sure the binary is executable
372
+ if (process.platform !== "win32") {
373
+ chmodSync(binaryPath, 0o755);
374
+ }
375
+
376
+ console.log(`āœ… Downloaded and extracted to ${binaryPath}`);
377
+ }
378
+
379
+ // Test if the binary can execute
380
+ // Debug: Show binary location
381
+ console.log(`šŸ” Executing binary: ${binaryPath}`);
382
+
383
+ try {
384
+ // Use default args if no arguments provided
385
+ const argsToUse = remainingArgs.length > 0 ? remainingArgs : DEFAULT_ARGS;
386
+
387
+ execFileSync(binaryPath, argsToUse, {
388
+ stdio: "inherit",
389
+ encoding: 'utf8'
390
+ });
391
+
392
+ // If we reach here, the binary executed successfully
393
+ binaryWorking = true;
394
+
395
+ // If execFileSync completes without throwing, the binary exited with code 0
396
+ // No need to explicitly exit here, let the script continue
397
+ } catch (execError) {
398
+ lastError = execError;
399
+ binaryWorking = false;
400
+
401
+ // Extract detailed error information
402
+ const errorCode = execError.code;
403
+ const errorSignal = execError.signal;
404
+ const errorMessage = execError.message;
405
+ const errorStatus = execError.status;
406
+
407
+ // Create comprehensive error output
408
+ console.error(`\nāŒ Tingly-Box execution failed`);
409
+ console.error(`ā”Œā”€ Error Details:`);
410
+ console.error(`│ Message: ${errorMessage}`);
411
+
412
+ if (errorCode) {
413
+ console.error(`│ Code: ${errorCode}`);
414
+ // Provide specific guidance for common error codes
415
+ switch (errorCode) {
416
+ case 'ENOENT':
417
+ console.error(`│ └─ Binary not found at: ${binaryPath}`);
418
+ console.error(`│ Try removing the cached binary: rm -rf "${join(cacheDir(), 'tingly-box')}"`);
419
+ break;
420
+ case 'EACCES':
421
+ console.error(`│ └─ Permission denied. Check binary permissions.`);
422
+ break;
423
+ case 'ETXTBSY':
424
+ console.error(`│ └─ Binary file is busy or being modified.`);
425
+ break;
426
+ default:
427
+ console.error(`│ └─ System error occurred.`);
428
+ }
429
+ }
430
+
431
+ if (errorStatus !== null && errorStatus !== undefined) {
432
+ console.error(`│ Exit Code: ${errorStatus}`);
433
+ console.error(`│ └─ The binary exited with non-zero status code.`);
434
+ }
435
+
436
+ if (errorSignal) {
437
+ console.error(`│ Signal: ${errorSignal}`);
438
+ console.error(`│ └─ The binary was terminated by a signal.`);
439
+ }
440
+
441
+ console.error(`└─ Binary Path: ${binaryPath}`);
442
+ console.error(` Platform: ${process.platform} (${process.arch})`);
443
+
444
+ // Provide additional help for common scenarios
445
+ if (process.platform === "linux") {
446
+ console.error(`\nšŸ’” Linux Troubleshooting:`);
447
+ console.error(` • Check if required libraries are installed:`);
448
+ console.error(` - For glibc issues: try on a different Linux distribution`);
449
+ console.error(` - For missing dependencies: install required system packages`);
450
+ console.error(` • Try running with strace: strace -o trace.log "${binaryPath}"`);
451
+ }
452
+
453
+ // Suggest retry
454
+ console.error(`\nšŸ”„ To retry, run: npx tingly-box ${remainingArgs.join(' ')}`);
455
+ console.error(` Or clear cache first: rm -rf "${join(cacheDir(), 'tingly-box')}"`);
456
+ }
457
+
458
+ if (!binaryWorking) {
459
+ // Exit with the binary's exit code if available, otherwise default to 1
460
+ const exitCode = lastError.status !== undefined ? lastError.status : 1;
461
+ process.exit(exitCode);
462
+ }
463
+ })();
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "tingly-box-nightly",
3
+ "version": "0.20260119.702-nightly",
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-nightly",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/tingly-dev/tingly-box-nightly.git"
18
+ },
19
+ "license": "MPL-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-nightly": "bin.js"
29
+ },
30
+ "type": "module",
31
+ "dependencies": {
32
+ "undici": "^7.0.0",
33
+ "unzipper": "^0.12.3"
34
+ }
35
+ }