tingly-box-bundle 0.0.37-dev

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 ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Tingly-Box Bundle Package
5
+ *
6
+ * This package has platform-specific packages as optionalDependencies.
7
+ * npm will automatically install the correct platform package when you install this.
8
+ *
9
+ * The platform package (@tingly-box/darwin-arm64, etc.) contains the pre-built binary.
10
+ * If the platform package is installed, we use it directly.
11
+ * Otherwise, we fall back to downloading from GitHub (same as CLI).
12
+ */
13
+
14
+ import { run } from "@tingly-box/shared/cli.js";
15
+
16
+ // Set release tag from package.json or environment
17
+ const releaseTag = process.env.TINGLY_RELEASE_TAG || "latest";
18
+
19
+ run("bundle", { releaseTag }).catch((error) => {
20
+ console.error(`❌ Fatal error: ${error.message}`);
21
+ process.exit(1);
22
+ });
package/lib/cache.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Cache directory management
3
+ */
4
+
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+
8
+ /**
9
+ * Get cache directory
10
+ * @param {string} flavor - Flavor name (cli, gui, bundle)
11
+ * @param {string} version - Version string
12
+ * @returns {string} Cache directory path
13
+ */
14
+ export function getCacheDir(flavor, version) {
15
+ let baseDir;
16
+
17
+ if (process.platform === "linux") {
18
+ baseDir = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
19
+ } else if (process.platform === "darwin") {
20
+ baseDir = join(homedir(), "Library", "Caches");
21
+ } else if (process.platform === "win32") {
22
+ baseDir = process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local");
23
+ }
24
+
25
+ return join(baseDir, "tingly-box", flavor, version);
26
+ }
27
+
28
+ /**
29
+ * Get binary cache path
30
+ * @param {string} flavor - Flavor name
31
+ * @param {string} version - Version string
32
+ * @param {string} binaryName - Binary name
33
+ * @returns {string} Binary cache path
34
+ */
35
+ export function getBinaryCachePath(flavor, version, binaryName) {
36
+ const cacheDir = getCacheDir(flavor, version);
37
+ const suffix = process.platform === "win32" ? ".exe" : "";
38
+ return join(cacheDir, binaryName + suffix);
39
+ }
40
+
41
+ /**
42
+ * Get app bundle cache path (macOS GUI)
43
+ * @param {string} version - Version string
44
+ * @returns {string} App bundle path
45
+ */
46
+ export function getAppCachePath(version) {
47
+ const cacheDir = getCacheDir("gui", version);
48
+ return join(cacheDir, "TinglyBox.app");
49
+ }
package/lib/cli.js ADDED
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Main CLI runner - shared logic for all flavor packages
3
+ */
4
+
5
+ import { existsSync, mkdirSync } from "fs";
6
+ import { join, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { createReadStream } from "fs";
9
+ import { mkdir } from "fs/promises";
10
+ import { pipeline } from "stream/promises";
11
+ import unzipper from "unzipper";
12
+
13
+ import {
14
+ getPlatformInfo,
15
+ getPlatformIdentifier,
16
+ getPlatformPackageName,
17
+ getBinaryName,
18
+ isPlatformSupported,
19
+ } from "./platform.js";
20
+ import {
21
+ getCacheDir,
22
+ getBinaryCachePath,
23
+ getAppCachePath,
24
+ } from "./cache.js";
25
+ import {
26
+ downloadAndExtractZip,
27
+ findBinary,
28
+ ensureExecutable,
29
+ } from "./downloader.js";
30
+ import {
31
+ executeBinary,
32
+ launchApp,
33
+ ensureAppExecutable,
34
+ } from "./executor.js";
35
+ import {
36
+ getFlavorConfig,
37
+ getReleaseBranch,
38
+ getBaseUrl,
39
+ parseTransportVersion,
40
+ } from "./config.js";
41
+
42
+ const __filename = fileURLToPath(import.meta.url);
43
+ const __dirname = dirname(__filename);
44
+
45
+ /**
46
+ * Check if platform-specific package is installed
47
+ * @returns {boolean}
48
+ */
49
+ async function isPlatformPackageInstalled() {
50
+ try {
51
+ const pkgName = getPlatformPackageName();
52
+ const pkgPath = join(process.cwd(), "node_modules", pkgName);
53
+ return existsSync(pkgPath);
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Get binary from platform-specific package
61
+ * @returns {string|null} Binary path or null
62
+ */
63
+ async function getPlatformPackageBinary() {
64
+ try {
65
+ const pkgName = getPlatformPackageName();
66
+ const pkgPath = join(process.cwd(), "node_modules", pkgName);
67
+ const binaryName = getBinaryName();
68
+ const binaryPath = join(pkgPath, binaryName);
69
+
70
+ if (existsSync(binaryPath)) {
71
+ return binaryPath;
72
+ }
73
+ return null;
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Run CLI for a flavor
81
+ * @param {string} flavor - Flavor name (cli, gui, bundle)
82
+ * @param {Object} options - Options
83
+ * @param {string} [options.releaseTag] - Override release tag
84
+ * @param {string} [options.zipDir] - Local zip directory (for bundle)
85
+ */
86
+ export async function run(flavor, options = {}) {
87
+ const config = getFlavorConfig(flavor);
88
+ const platformInfo = getPlatformInfo();
89
+ const { version, remainingArgs } = parseTransportVersion(process.argv.slice(2));
90
+
91
+ // Use provided release tag or default
92
+ const releaseTag = options.releaseTag || getReleaseBranch();
93
+
94
+ // Validate platform support
95
+ if (!isPlatformSupported(config.platforms)) {
96
+ console.error(`\n❌ This package does not support ${platformInfo.platform}`);
97
+ console.error(`┌─ Status:`);
98
+ console.error(`│ Supported platforms: ${config.platforms.join(", ")}`);
99
+ console.error(`│ Your platform: ${platformInfo.platform} (${platformInfo.arch})`);
100
+ console.error(`└─ Try: npx tingly-box ${remainingArgs.join(" ")}`);
101
+ process.exit(1);
102
+ }
103
+
104
+ // Try platform-specific package first
105
+ const platformBinary = await getPlatformPackageBinary();
106
+ if (platformBinary) {
107
+ console.log(`✅ Using platform package: ${getPlatformPackageName()}`);
108
+ executeBinary(platformBinary, remainingArgs, config.defaultArgs);
109
+ return;
110
+ }
111
+
112
+ // Otherwise, use remote download or local bundle
113
+ const cacheDir = getCacheDir(flavor, releaseTag);
114
+ mkdirSync(cacheDir, { recursive: true });
115
+
116
+ if (flavor === "gui") {
117
+ await runGui(releaseTag, remainingArgs);
118
+ } else if (flavor === "bundle" || options.zipDir) {
119
+ await runBundle(releaseTag, remainingArgs, options.zipDir);
120
+ } else {
121
+ await runCli(releaseTag, remainingArgs, config);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Run CLI flavor
127
+ */
128
+ async function runCli(version, args, config) {
129
+ const platformInfo = getPlatformInfo();
130
+ const cacheDir = getCacheDir("cli", version);
131
+ const binaryName = getBinaryName(config.binaryName);
132
+ const binaryPath = getBinaryCachePath("cli", version, config.binaryName);
133
+
134
+ if (!existsSync(binaryPath)) {
135
+ const zipFileName = `${config.binaryName}-${platformInfo.platformDir}-${platformInfo.archDir}.zip`;
136
+ const downloadUrl = `${getBaseUrl()}${version}/${zipFileName}`;
137
+ await downloadAndExtractZip(downloadUrl, cacheDir, "Downloading binary");
138
+ ensureExecutable(binaryPath);
139
+ console.log(`✅ Downloaded to ${binaryPath}`);
140
+ }
141
+
142
+ executeBinary(binaryPath, args, config.defaultArgs);
143
+ }
144
+
145
+ /**
146
+ * Run GUI flavor
147
+ */
148
+ async function runGui(version, args) {
149
+ const cacheDir = getCacheDir("gui", version);
150
+ const appPath = getAppCachePath(version);
151
+
152
+ if (!existsSync(appPath)) {
153
+ const platformInfo = getPlatformInfo();
154
+ const zipFileName = `tingly-box-gui-${platformInfo.platformDir}-${platformInfo.archDir}.zip`;
155
+ const downloadUrl = `${getBaseUrl()}${version}/${zipFileName}`;
156
+ await downloadAndExtractZip(downloadUrl, cacheDir, "Downloading GUI");
157
+ ensureAppExecutable(appPath);
158
+ console.log(`✅ Downloaded to ${appPath}`);
159
+ }
160
+
161
+ launchApp(appPath);
162
+ }
163
+
164
+ /**
165
+ * Run Bundle flavor
166
+ * Bundle is just a router - it tries to use platform package first,
167
+ * then falls back to GitHub download like CLI
168
+ */
169
+ async function runBundle(version, args, localZipDir) {
170
+ // Bundle = CLI with better defaults for platform packages
171
+ // The platform package is installed as optionalDependency
172
+ await runCli(version, args, getFlavorConfig("bundle"));
173
+ }
174
+
175
+ export default { run };
package/lib/config.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Configuration management
3
+ */
4
+
5
+ import { readFileSync } from "fs";
6
+ import { join, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+
11
+ // Load flavor configurations
12
+ let flavorsConfig = null;
13
+
14
+ try {
15
+ const flavorsPath = join(__dirname, "..", "flavors");
16
+ const cliConfig = JSON.parse(readFileSync(join(flavorsPath, "cli.json"), "utf8"));
17
+ const guiConfig = JSON.parse(readFileSync(join(flavorsPath, "gui.json"), "utf8"));
18
+ const bundleConfig = JSON.parse(readFileSync(join(flavorsPath, "bundle.json"), "utf8"));
19
+
20
+ flavorsConfig = {
21
+ cli: cliConfig,
22
+ gui: guiConfig,
23
+ bundle: bundleConfig,
24
+ };
25
+ } catch (error) {
26
+ // Fallback configurations
27
+ flavorsConfig = {
28
+ cli: {
29
+ binaryName: "tingly-box",
30
+ platforms: ["darwin", "linux", "win32"],
31
+ defaultArgs: ["restart", "--daemon"],
32
+ action: "execute",
33
+ },
34
+ gui: {
35
+ binaryName: "tingly-box-gui",
36
+ platforms: ["darwin"],
37
+ defaultArgs: [],
38
+ action: "launch",
39
+ },
40
+ bundle: {
41
+ binaryName: "tingly-box",
42
+ platforms: ["darwin", "linux", "win32"],
43
+ defaultArgs: ["restart", "--daemon"],
44
+ action: "execute",
45
+ },
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Get flavor configuration
51
+ * @param {string} flavor - Flavor name (cli, gui, bundle)
52
+ * @returns {Object} Flavor configuration
53
+ */
54
+ export function getFlavorConfig(flavor) {
55
+ return flavorsConfig[flavor];
56
+ }
57
+
58
+ /**
59
+ * Get release branch/tag
60
+ * @returns {string} Release branch (e.g., "latest" or "v1.6.1")
61
+ */
62
+ export function getReleaseBranch() {
63
+ // This can be replaced during build
64
+ return process.env.TINGLY_RELEASE_BRANCH || "latest";
65
+ }
66
+
67
+ /**
68
+ * Get base download URL
69
+ * @returns {string} Base URL for downloads
70
+ */
71
+ export function getBaseUrl() {
72
+ return "https://github.com/tingly-dev/tingly-box/releases/download/";
73
+ }
74
+
75
+ /**
76
+ * Parse transport version from arguments
77
+ * @param {string[]} args - Command line arguments
78
+ * @returns {Object} { version, remainingArgs }
79
+ */
80
+ export function parseTransportVersion(args) {
81
+ let transportVersion = "latest";
82
+ const remainingArgs = [...args];
83
+
84
+ const versionArgIndex = args.findIndex((arg) => arg.startsWith("--transport-version"));
85
+
86
+ if (versionArgIndex !== -1) {
87
+ const versionArg = args[versionArgIndex];
88
+
89
+ if (versionArg.includes("=")) {
90
+ transportVersion = versionArg.split("=")[1];
91
+ remainingArgs.splice(versionArgIndex, 1);
92
+ } else if (versionArgIndex + 1 < args.length) {
93
+ transportVersion = args[versionArgIndex + 1];
94
+ remainingArgs.splice(versionArgIndex, 2);
95
+ }
96
+ }
97
+
98
+ return {
99
+ version: validateVersion(transportVersion),
100
+ remainingArgs,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Validate version format
106
+ * @param {string} version - Version string
107
+ * @returns {string} Validated version
108
+ */
109
+ export function validateVersion(version) {
110
+ if (version === "latest") {
111
+ return version;
112
+ }
113
+
114
+ const versionRegex = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
115
+ if (versionRegex.test(version)) {
116
+ return version;
117
+ }
118
+
119
+ console.error(`Invalid version format: ${version}`);
120
+ console.error(`Version must be either "latest" or "v{x.x.x}"`);
121
+ throw new Error(`Invalid version: ${version}`);
122
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Download and extraction utilities
3
+ */
4
+
5
+ import { chmodSync, mkdirSync, createWriteStream } from "fs";
6
+ import { join } from "path";
7
+ import { Readable } from "stream";
8
+ import { ProxyAgent } from "undici";
9
+ import unzipper from "unzipper";
10
+
11
+ /**
12
+ * Get proxy dispatcher
13
+ * @returns {ProxyAgent|undefined}
14
+ */
15
+ export function getDispatcher() {
16
+ const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
17
+ const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
18
+ const proxyUri = httpsProxy || httpProxy;
19
+ return proxyUri ? new ProxyAgent(proxyUri) : undefined;
20
+ }
21
+
22
+ /**
23
+ * Download and extract a ZIP file
24
+ * @param {string} url - Download URL
25
+ * @param {string} extractDir - Destination directory
26
+ * @param {string} [progressPrefix] - Prefix for progress messages
27
+ */
28
+ export async function downloadAndExtractZip(url, extractDir, progressPrefix = "Downloading") {
29
+ const dispatcher = getDispatcher();
30
+ const fetchOptions = {
31
+ redirect: "follow",
32
+ headers: { "User-Agent": "tingly-box-npx" },
33
+ };
34
+ if (dispatcher) {
35
+ fetchOptions.dispatcher = dispatcher;
36
+ }
37
+
38
+ const res = await fetch(url, fetchOptions);
39
+
40
+ if (!res.ok) {
41
+ console.error(`❌ Download failed: ${res.status} ${res.statusText}`);
42
+ console.error(` URL: ${url}`);
43
+ throw new Error(`Download failed: ${res.status}`);
44
+ }
45
+
46
+ const contentLength = res.headers.get("content-length");
47
+ const totalSize = contentLength ? parseInt(contentLength, 10) : null;
48
+ let downloadedSize = 0;
49
+
50
+ const nodeStream = Readable.fromWeb(res.body);
51
+ const chunks = [];
52
+
53
+ for await (const chunk of nodeStream) {
54
+ chunks.push(chunk);
55
+ downloadedSize += chunk.length;
56
+ if (totalSize) {
57
+ const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
58
+ process.stdout.write(`\r⏱️ ${progressPrefix}: ${progress}% (${formatBytes(downloadedSize)}/${formatBytes(totalSize)})`);
59
+ } else {
60
+ process.stdout.write(`\r⏱️ ${progressPrefix}: ${formatBytes(downloadedSize)}`);
61
+ }
62
+ }
63
+
64
+ const zipBuffer = Buffer.concat(chunks);
65
+
66
+ await extractZipBuffer(zipBuffer, extractDir);
67
+ }
68
+
69
+ /**
70
+ * Extract ZIP buffer to directory
71
+ * @param {Buffer} zipBuffer - ZIP data
72
+ * @param {string} extractDir - Destination directory
73
+ */
74
+ export async function extractZipBuffer(zipBuffer, extractDir) {
75
+ console.log(`\n📦 Extracting to ${extractDir}...`);
76
+
77
+ const directory = await unzipper.Open.buffer(zipBuffer);
78
+
79
+ for (const file of directory.files) {
80
+ // Skip metadata
81
+ if (file.type === "Directory" || file.path.startsWith("__MACOSX/") || file.path.includes(".DS_Store")) {
82
+ continue;
83
+ }
84
+
85
+ const filePath = join(extractDir, file.path);
86
+ const pathParts = file.path.split("/");
87
+ pathParts.pop();
88
+ const fileDir = pathParts.length > 0 ? join(extractDir, ...pathParts) : extractDir;
89
+
90
+ // Create parent directory if needed
91
+ if (fileDir !== extractDir) {
92
+ mkdirSync(fileDir, { recursive: true });
93
+ }
94
+
95
+ // Extract file
96
+ const content = await file.buffer();
97
+ const fileStream = createWriteStream(filePath);
98
+ await new Promise((resolve, reject) => {
99
+ fileStream.write(content, (err) => {
100
+ if (err) reject(err);
101
+ else {
102
+ fileStream.end();
103
+ resolve();
104
+ }
105
+ });
106
+ });
107
+
108
+ // Set permissions
109
+ if (process.platform !== "win32" && file.unixPermissions && file.unixPermissions > 0) {
110
+ chmodSync(filePath, file.unixPermissions);
111
+ }
112
+ }
113
+
114
+ console.log(`✅ Extracted successfully`);
115
+ }
116
+
117
+ /**
118
+ * Recursively find binary in directory
119
+ * @param {string} dir - Directory to search
120
+ * @param {string} binaryName - Binary name to find
121
+ * @returns {string|null} Binary path or null
122
+ */
123
+ export function findBinary(dir, binaryName) {
124
+ const { readdirSync, statSync } = await import("fs");
125
+ const entries = readdirSync(dir);
126
+
127
+ for (const entry of entries) {
128
+ const fullPath = join(dir, entry);
129
+ try {
130
+ const stat = statSync(fullPath);
131
+ if (stat.isFile() && entry === binaryName) {
132
+ return fullPath;
133
+ }
134
+ if (stat.isDirectory()) {
135
+ const found = findBinary(fullPath, binaryName);
136
+ if (found) return found;
137
+ }
138
+ } catch {
139
+ continue;
140
+ }
141
+ }
142
+ return null;
143
+ }
144
+
145
+ /**
146
+ * Format bytes to human-readable string
147
+ * @param {number} bytes - Number of bytes
148
+ * @returns {string} Formatted string
149
+ */
150
+ export function formatBytes(bytes) {
151
+ if (bytes === 0) return "0 B";
152
+ const k = 1024;
153
+ const sizes = ["B", "KB", "MB", "GB"];
154
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
155
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
156
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Binary and app execution
3
+ */
4
+
5
+ import { execFileSync } from "child_process";
6
+ import { existsSync, chmodSync } from "fs";
7
+ import { join } from "path";
8
+
9
+ /**
10
+ * Execute binary with arguments
11
+ * @param {string} binaryPath - Path to binary
12
+ * @param {string[]} args - Command line arguments
13
+ * @param {string[]} defaultArgs - Default arguments if none provided
14
+ */
15
+ export function executeBinary(binaryPath, args = [], defaultArgs = []) {
16
+ console.log(`🔍 Executing: ${binaryPath}`);
17
+
18
+ const argsToUse = args.length > 0 ? args : defaultArgs;
19
+
20
+ try {
21
+ execFileSync(binaryPath, argsToUse, {
22
+ stdio: "inherit",
23
+ encoding: "utf8",
24
+ });
25
+ } catch (execError) {
26
+ handleExecutionError(execError, binaryPath);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Launch macOS app bundle
32
+ * @param {string} appPath - Path to .app bundle
33
+ */
34
+ export function launchApp(appPath) {
35
+ console.log(`🔍 Launching: ${appPath}`);
36
+
37
+ try {
38
+ // Sign the app first
39
+ console.log(`🔐 Signing app...`);
40
+ execFileSync("codesign", ["--force", "--deep", "--sign", "-", appPath], {
41
+ stdio: "inherit",
42
+ });
43
+
44
+ console.log(`🚀 Launching app...`);
45
+ execFileSync("open", ["-a", appPath], {
46
+ stdio: "inherit",
47
+ });
48
+ console.log(`✅ App launched!`);
49
+ } catch (execError) {
50
+ handleExecutionError(execError, appPath);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Handle execution errors
56
+ * @param {Error} execError - Execution error
57
+ * @param {string} binaryPath - Binary or app path
58
+ */
59
+ function handleExecutionError(execError, binaryPath) {
60
+ console.error(`\n❌ Execution failed`);
61
+ console.error(`┌─ Error Details:`);
62
+ console.error(`│ Message: ${execError.message}`);
63
+
64
+ if (execError.code) {
65
+ console.error(`│ Code: ${execError.code}`);
66
+ switch (execError.code) {
67
+ case "ENOENT":
68
+ console.error(`│ └─ Binary not found at: ${binaryPath}`);
69
+ console.error(`│ Try clearing cache and reinstalling`);
70
+ break;
71
+ case "EACCES":
72
+ console.error(`│ └─ Permission denied`);
73
+ console.error(`│ Try: chmod +x "${binaryPath}"`);
74
+ break;
75
+ case "ETXTBSY":
76
+ console.error(`│ └─ Binary file is busy`);
77
+ break;
78
+ }
79
+ }
80
+
81
+ if (execError.status !== null && execError.status !== undefined) {
82
+ console.error(`│ Exit Code: ${execError.status}`);
83
+ }
84
+
85
+ if (execError.signal) {
86
+ console.error(`│ Signal: ${execError.signal}`);
87
+ }
88
+
89
+ console.error(`└─ Path: ${binaryPath}`);
90
+ console.error(` Platform: ${process.platform} (${process.arch})`);
91
+
92
+ const exitCode = execError.status !== undefined ? execError.status : 1;
93
+ process.exit(exitCode);
94
+ }
95
+
96
+ /**
97
+ * Ensure binary is executable
98
+ * @param {string} binaryPath - Path to binary
99
+ */
100
+ export function ensureExecutable(binaryPath) {
101
+ if (process.platform !== "win32" && existsSync(binaryPath)) {
102
+ try {
103
+ chmodSync(binaryPath, 0o755);
104
+ } catch (e) {
105
+ console.warn(`⚠️ Failed to set executable permission: ${e.message}`);
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Ensure app bundle is properly signed and executable
112
+ * @param {string} appPath - Path to .app bundle
113
+ */
114
+ export function ensureAppExecutable(appPath) {
115
+ if (!existsSync(appPath)) {
116
+ return;
117
+ }
118
+
119
+ // Ensure binary inside app is executable
120
+ const appBinaryPath = join(appPath, "Contents", "MacOS", "tingly-box");
121
+ ensureExecutable(appBinaryPath);
122
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "binaryName": "tingly-box",
3
+ "platforms": ["darwin", "linux", "win32"],
4
+ "defaultArgs": ["restart", "--daemon"],
5
+ "action": "execute"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "binaryName": "tingly-box",
3
+ "platforms": ["darwin", "linux", "win32"],
4
+ "defaultArgs": ["restart", "--daemon"],
5
+ "action": "execute"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "binaryName": "tingly-box-gui",
3
+ "platforms": ["darwin"],
4
+ "defaultArgs": [],
5
+ "action": "launch"
6
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Platform detection and utilities
3
+ */
4
+
5
+ /**
6
+ * Get platform information
7
+ * @returns {Object} Platform info
8
+ */
9
+ export function getPlatformInfo() {
10
+ const platform = process.platform;
11
+ const arch = process.arch;
12
+
13
+ let platformDir;
14
+ let archDir;
15
+ let suffix = "";
16
+
17
+ if (platform === "darwin") {
18
+ platformDir = "macos";
19
+ if (arch === "arm64") archDir = "arm64";
20
+ else archDir = "amd64";
21
+ } else if (platform === "linux") {
22
+ platformDir = "linux";
23
+ if (arch === "x64") archDir = "amd64";
24
+ else if (arch === "arm64") archDir = "arm64";
25
+ else archDir = arch;
26
+ } else if (platform === "win32") {
27
+ platformDir = "windows";
28
+ if (arch === "x64") archDir = "amd64";
29
+ else if (arch === "ia32") archDir = "386";
30
+ else archDir = arch;
31
+ suffix = ".exe";
32
+ } else {
33
+ throw new Error(`Unsupported platform: ${platform}/${arch}`);
34
+ }
35
+
36
+ return { platform, arch, platformDir, archDir, suffix };
37
+ }
38
+
39
+ /**
40
+ * Get platform identifier for package naming
41
+ * @returns {string} Platform identifier (e.g., "darwin-arm64")
42
+ */
43
+ export function getPlatformIdentifier() {
44
+ const info = getPlatformInfo();
45
+ const npmArch = info.arch === "x64" ? "amd64" : info.arch;
46
+ return `${info.platform}-${npmArch}`;
47
+ }
48
+
49
+ /**
50
+ * Get npm package name for platform
51
+ * @returns {string} Package name (e.g., "tingly-box-darwin")
52
+ */
53
+ export function getPlatformPackageName() {
54
+ const info = getPlatformInfo();
55
+ return `tingly-box-${info.platform}`;
56
+ }
57
+
58
+ /**
59
+ * Get binary name for current platform
60
+ * @param {string} baseName - Base binary name
61
+ * @returns {string} Full binary name with extension
62
+ */
63
+ export function getBinaryName(baseName = "tingly-box") {
64
+ return baseName + (process.platform === "win32" ? ".exe" : "");
65
+ }
66
+
67
+ /**
68
+ * Check if platform is supported
69
+ * @param {string[]} supportedPlatforms - List of supported platforms
70
+ * @returns {boolean}
71
+ */
72
+ export function isPlatformSupported(supportedPlatforms) {
73
+ const info = getPlatformInfo();
74
+ return supportedPlatforms.includes(info.platform);
75
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "tingly-box-bundle",
3
+ "version": "0.0.37-dev",
4
+ "description": "Tingly-Box AI gateway CLI with bundled binaries (offline-ready)",
5
+ "keywords": [
6
+ "ai",
7
+ "gateway",
8
+ "openai",
9
+ "anthropic",
10
+ "claude",
11
+ "cli",
12
+ "tingly",
13
+ "offline"
14
+ ],
15
+ "homepage": "https://github.com/tingly-dev/tingly-box",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/tingly-dev/tingly-box.git"
19
+ },
20
+ "license": "MPL-2.0",
21
+ "author": "Tingly Dev",
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "bin": {
29
+ "tingly-box-bundle": "bin.js"
30
+ },
31
+ "type": "module",
32
+ "files": [
33
+ "bin.js",
34
+ "lib/"
35
+ ],
36
+ "os": [
37
+ "darwin",
38
+ "linux",
39
+ "win32"
40
+ ],
41
+ "cpu": [
42
+ "x64",
43
+ "arm64"
44
+ ],
45
+ "dependencies": {
46
+ "@tingly-box/shared": "workspace:*"
47
+ },
48
+ "optionalDependencies": {
49
+ "tingly-box-darwin": "^1.6.1",
50
+ "tingly-box-linux": "^1.6.1",
51
+ "tingly-box-win32": "^1.6.1"
52
+ }
53
+ }