@paiart/clipal 0.17.0

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/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @paiart/clipal
2
+
3
+ Install the Clipal CLI from npm:
4
+
5
+ ```bash
6
+ npm install -g @paiart/clipal
7
+ clipal --version
8
+ ```
9
+
10
+ This npm package installs the platform-specific Clipal binary from the official GitHub Releases for the matching version.
11
+
12
+ Project documentation:
13
+
14
+ - Repository: https://github.com/PAIArtCom/Clipal
15
+ - Releases: https://github.com/PAIArtCom/Clipal/releases
package/bin/clipal.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { spawn } = require("node:child_process");
6
+
7
+ const binaryName = process.platform === "win32" ? "clipal.exe" : "clipal";
8
+ const binaryPath = path.join(__dirname, "..", "vendor", binaryName);
9
+
10
+ if (!fs.existsSync(binaryPath)) {
11
+ console.error(
12
+ "clipal: bundled binary is missing. Reinstall the package or run `npm rebuild @paiart/clipal`."
13
+ );
14
+ process.exit(1);
15
+ }
16
+
17
+ const child = spawn(binaryPath, process.argv.slice(2), {
18
+ stdio: "inherit"
19
+ });
20
+
21
+ child.on("exit", (code, signal) => {
22
+ if (signal) {
23
+ process.kill(process.pid, signal);
24
+ return;
25
+ }
26
+ process.exit(code ?? 1);
27
+ });
28
+
29
+ child.on("error", (err) => {
30
+ console.error(`clipal: failed to start bundled binary: ${err.message}`);
31
+ process.exit(1);
32
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@paiart/clipal",
3
+ "version": "0.17.0",
4
+ "description": "Clipal CLI installer for the local LLM API gateway",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/PAIArtCom/Clipal",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/PAIArtCom/Clipal.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/PAIArtCom/Clipal/issues"
13
+ },
14
+ "bin": {
15
+ "clipal": "bin/clipal.js"
16
+ },
17
+ "files": [
18
+ "bin/clipal.js",
19
+ "scripts/postinstall.js",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "postinstall": "node ./scripts/postinstall.js"
24
+ },
25
+ "keywords": [
26
+ "clipal",
27
+ "llm",
28
+ "proxy",
29
+ "gateway",
30
+ "openai",
31
+ "claude",
32
+ "gemini",
33
+ "cli"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "registry": "https://registry.npmjs.org/"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+
3
+ const crypto = require("node:crypto");
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+ const https = require("node:https");
8
+
9
+ const packageRoot = path.join(__dirname, "..");
10
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
11
+ const vendorDir = path.join(packageRoot, "vendor");
12
+
13
+ const repoBaseUrl =
14
+ process.env.CLIPAL_NPM_BASE_URL || "https://github.com/PAIArtCom/Clipal/releases/download";
15
+ const versionTag = `v${pkg.version}`;
16
+ const checksumsUrl = `${repoBaseUrl}/${versionTag}/checksums.txt`;
17
+
18
+ function resolveAssetName() {
19
+ switch (process.platform) {
20
+ case "darwin":
21
+ if (process.arch === "arm64") return "clipal-darwin-arm64";
22
+ if (process.arch === "x64") return "clipal-darwin-amd64";
23
+ break;
24
+ case "linux":
25
+ if (process.arch === "arm64") return "clipal-linux-arm64";
26
+ if (process.arch === "x64") return "clipal-linux-amd64";
27
+ break;
28
+ case "win32":
29
+ if (process.arch === "arm64") return "clipal-windows-arm64.exe";
30
+ if (process.arch === "x64") return "clipal-windows-amd64.exe";
31
+ break;
32
+ default:
33
+ break;
34
+ }
35
+ throw new Error(`unsupported platform ${process.platform}/${process.arch}`);
36
+ }
37
+
38
+ function download(url, destination) {
39
+ return new Promise((resolve, reject) => {
40
+ const request = https.get(
41
+ url,
42
+ {
43
+ headers: {
44
+ "user-agent": `clipal-npm/${pkg.version}`
45
+ }
46
+ },
47
+ (response) => {
48
+ if (
49
+ response.statusCode &&
50
+ response.statusCode >= 300 &&
51
+ response.statusCode < 400 &&
52
+ response.headers.location
53
+ ) {
54
+ response.resume();
55
+ download(response.headers.location, destination).then(resolve, reject);
56
+ return;
57
+ }
58
+
59
+ if (response.statusCode !== 200) {
60
+ response.resume();
61
+ reject(new Error(`download failed for ${url}: HTTP ${response.statusCode}`));
62
+ return;
63
+ }
64
+
65
+ const out = fs.createWriteStream(destination, { mode: 0o755 });
66
+ response.pipe(out);
67
+ out.on("finish", () => out.close(resolve));
68
+ out.on("error", reject);
69
+ }
70
+ );
71
+
72
+ request.on("error", reject);
73
+ });
74
+ }
75
+
76
+ function parseChecksums(text) {
77
+ const map = new Map();
78
+ for (const line of text.split(/\r?\n/)) {
79
+ const trimmed = line.trim();
80
+ if (!trimmed) continue;
81
+ const match = trimmed.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
82
+ if (!match) {
83
+ throw new Error(`invalid checksums line: ${line}`);
84
+ }
85
+ map.set(match[2], match[1].toLowerCase());
86
+ }
87
+ return map;
88
+ }
89
+
90
+ function sha256(filePath) {
91
+ const hash = crypto.createHash("sha256");
92
+ hash.update(fs.readFileSync(filePath));
93
+ return hash.digest("hex");
94
+ }
95
+
96
+ async function main() {
97
+ const assetName = resolveAssetName();
98
+ const binaryName = process.platform === "win32" ? "clipal.exe" : "clipal";
99
+ const targetPath = path.join(vendorDir, binaryName);
100
+
101
+ fs.mkdirSync(vendorDir, { recursive: true });
102
+
103
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "clipal-npm-"));
104
+ const checksumsPath = path.join(tempDir, "checksums.txt");
105
+ const downloadPath = path.join(tempDir, assetName);
106
+
107
+ try {
108
+ console.log(`clipal: downloading ${assetName} for ${process.platform}/${process.arch}`);
109
+ await download(checksumsUrl, checksumsPath);
110
+ const checksums = parseChecksums(fs.readFileSync(checksumsPath, "utf8"));
111
+ const expectedSha = checksums.get(assetName);
112
+ if (!expectedSha) {
113
+ throw new Error(`checksums.txt does not contain ${assetName}`);
114
+ }
115
+
116
+ await download(`${repoBaseUrl}/${versionTag}/${assetName}`, downloadPath);
117
+ const actualSha = sha256(downloadPath);
118
+ if (actualSha !== expectedSha) {
119
+ throw new Error(`checksum mismatch for ${assetName}`);
120
+ }
121
+
122
+ fs.copyFileSync(downloadPath, targetPath);
123
+ if (process.platform !== "win32") {
124
+ fs.chmodSync(targetPath, 0o755);
125
+ }
126
+ console.log(`clipal: installed bundled binary to ${path.relative(packageRoot, targetPath)}`);
127
+ } finally {
128
+ fs.rmSync(tempDir, { recursive: true, force: true });
129
+ }
130
+ }
131
+
132
+ main().catch((err) => {
133
+ console.error(`clipal: postinstall failed: ${err.message}`);
134
+ process.exit(1);
135
+ });