pallium 0.9.1

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,25 @@
1
+ # pallium
2
+
3
+ NPM installer for the Pallium CLI.
4
+
5
+ ```bash
6
+ npm install -g pallium
7
+ pallium doctor
8
+ ```
9
+
10
+ The package installs the matching Pallium release binary from GitHub. If a
11
+ prebuilt binary is unavailable for the current platform, it falls back to:
12
+
13
+ ```bash
14
+ go install github.com/tszaks/pallium@v0.9.1
15
+ ```
16
+
17
+ Supported prebuilt platforms:
18
+
19
+ - macOS arm64
20
+ - macOS x64
21
+ - Linux arm64
22
+ - Linux x64
23
+
24
+ Pallium itself stores local data in `~/.pallium/` and repo-local `.pallium/`
25
+ databases.
package/bin/pallium.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { spawn } = require("node:child_process");
5
+ const { ensureBinary } = require("../scripts/lib");
6
+
7
+ async function main() {
8
+ const binPath = await ensureBinary({ quiet: true });
9
+ const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
10
+
11
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
12
+ process.on(signal, () => {
13
+ child.kill(signal);
14
+ });
15
+ }
16
+
17
+ child.on("error", (error) => {
18
+ console.error(`pallium: failed to start CLI: ${error.message}`);
19
+ process.exit(1);
20
+ });
21
+
22
+ child.on("exit", (code, signal) => {
23
+ if (signal) {
24
+ process.kill(process.pid, signal);
25
+ return;
26
+ }
27
+ process.exit(code ?? 1);
28
+ });
29
+ }
30
+
31
+ main().catch((error) => {
32
+ console.error(`pallium: ${error.message}`);
33
+ process.exit(1);
34
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "pallium",
3
+ "version": "0.9.1",
4
+ "description": "Local-first CLI for AI-powered coding workflows.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/tszaks/pallium#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tszaks/pallium.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/tszaks/pallium/issues"
13
+ },
14
+ "bin": {
15
+ "pallium": "bin/pallium.js"
16
+ },
17
+ "files": [
18
+ "bin/",
19
+ "scripts/",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "postinstall": "node scripts/install.js",
24
+ "test": "node scripts/test.js"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { installBinary } = require("./lib");
5
+
6
+ installBinary({ quiet: false }).catch((error) => {
7
+ console.error(`pallium install failed: ${error.message}`);
8
+ process.exit(1);
9
+ });
package/scripts/lib.js ADDED
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const os = require("node:os");
5
+ const path = require("node:path");
6
+ const https = require("node:https");
7
+ const crypto = require("node:crypto");
8
+ const { spawnSync } = require("node:child_process");
9
+
10
+ const packageJson = require("../package.json");
11
+
12
+ const REPO = "tszaks/pallium";
13
+ const VERSION = packageJson.version;
14
+ const TAG = `v${VERSION}`;
15
+ const BINARY_NAME = process.platform === "win32" ? "pallium.exe" : "pallium";
16
+
17
+ function platformKey() {
18
+ const arch = process.arch === "x64" ? "amd64" : process.arch;
19
+ return `${process.platform}-${arch}`;
20
+ }
21
+
22
+ function assetName() {
23
+ const assets = {
24
+ "darwin-arm64": `pallium_${VERSION}_darwin_arm64.tar.gz`,
25
+ "darwin-amd64": `pallium_${VERSION}_darwin_amd64.tar.gz`,
26
+ "linux-arm64": `pallium_${VERSION}_linux_arm64.tar.gz`,
27
+ "linux-amd64": `pallium_${VERSION}_linux_amd64.tar.gz`
28
+ };
29
+ return assets[platformKey()] || null;
30
+ }
31
+
32
+ function installDir() {
33
+ if (process.env.PALLIUM_INSTALL_DIR) {
34
+ return path.resolve(process.env.PALLIUM_INSTALL_DIR);
35
+ }
36
+ return path.join(os.homedir(), ".pallium", "npm", TAG);
37
+ }
38
+
39
+ function binaryPath() {
40
+ return path.join(installDir(), BINARY_NAME);
41
+ }
42
+
43
+ async function ensureBinary(options = {}) {
44
+ if (!process.env.PALLIUM_FORCE_INSTALL && isExecutable(binaryPath())) {
45
+ return binaryPath();
46
+ }
47
+ return installBinary(options);
48
+ }
49
+
50
+ async function installBinary(options = {}) {
51
+ fs.mkdirSync(installDir(), { recursive: true });
52
+ const asset = assetName();
53
+ if (asset) {
54
+ try {
55
+ await installFromRelease(asset, options);
56
+ return binaryPath();
57
+ } catch (error) {
58
+ log(options, `release binary unavailable, trying go install (${error.message})`);
59
+ }
60
+ } else {
61
+ log(options, `no prebuilt binary for ${platformKey()}, trying go install`);
62
+ }
63
+
64
+ installWithGo();
65
+ return binaryPath();
66
+ }
67
+
68
+ async function installFromRelease(asset, options) {
69
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pallium-npm-"));
70
+ const archivePath = path.join(tmpDir, asset);
71
+ try {
72
+ const baseUrl = `https://github.com/${REPO}/releases/download/${TAG}`;
73
+ log(options, `downloading ${baseUrl}/${asset}`);
74
+ await download(`${baseUrl}/${asset}`, archivePath);
75
+ await verifyChecksum(baseUrl, asset, archivePath, options);
76
+
77
+ const tar = spawnSync("tar", ["-xzf", archivePath, "-C", tmpDir], { stdio: "pipe" });
78
+ if (tar.status !== 0) {
79
+ throw new Error(`tar failed: ${String(tar.stderr || tar.stdout).trim()}`);
80
+ }
81
+
82
+ const extracted = findFile(tmpDir, BINARY_NAME);
83
+ if (!extracted) {
84
+ throw new Error(`archive did not contain ${BINARY_NAME}`);
85
+ }
86
+ fs.copyFileSync(extracted, binaryPath());
87
+ fs.chmodSync(binaryPath(), 0o755);
88
+ } finally {
89
+ fs.rmSync(tmpDir, { recursive: true, force: true });
90
+ }
91
+ }
92
+
93
+ async function verifyChecksum(baseUrl, asset, archivePath, options) {
94
+ const checksumsPath = path.join(path.dirname(archivePath), "checksums.txt");
95
+ await download(`${baseUrl}/checksums.txt`, checksumsPath);
96
+ const expected = parseChecksum(fs.readFileSync(checksumsPath, "utf8"), asset);
97
+ if (!expected) {
98
+ throw new Error(`checksums.txt did not include ${asset}`);
99
+ }
100
+ const actual = crypto.createHash("sha256").update(fs.readFileSync(archivePath)).digest("hex");
101
+ if (actual !== expected) {
102
+ throw new Error(`checksum mismatch for ${asset}`);
103
+ }
104
+ log(options, "checksum verified");
105
+ }
106
+
107
+ function installWithGo() {
108
+ const go = spawnSync("go", ["install", `github.com/${REPO}@${TAG}`], {
109
+ env: { ...process.env, GOBIN: installDir() },
110
+ stdio: "inherit"
111
+ });
112
+ if (go.error) {
113
+ throw new Error(`go install failed to start: ${go.error.message}`);
114
+ }
115
+ if (go.status !== 0) {
116
+ throw new Error(`go install exited with ${go.status}`);
117
+ }
118
+ if (!isExecutable(binaryPath())) {
119
+ throw new Error(`go install did not create ${binaryPath()}`);
120
+ }
121
+ }
122
+
123
+ function download(url, destination, redirects = 0) {
124
+ return new Promise((resolve, reject) => {
125
+ const request = https.get(
126
+ url,
127
+ {
128
+ headers: {
129
+ "User-Agent": `pallium-npm/${VERSION}`,
130
+ "Accept": "application/octet-stream"
131
+ }
132
+ },
133
+ (response) => {
134
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
135
+ response.resume();
136
+ if (!response.headers.location || redirects >= 5) {
137
+ reject(new Error(`redirect failed for ${url}`));
138
+ return;
139
+ }
140
+ download(response.headers.location, destination, redirects + 1).then(resolve, reject);
141
+ return;
142
+ }
143
+
144
+ if (response.statusCode !== 200) {
145
+ response.resume();
146
+ reject(new Error(`HTTP ${response.statusCode} for ${url}`));
147
+ return;
148
+ }
149
+
150
+ const file = fs.createWriteStream(destination, { mode: 0o644 });
151
+ response.pipe(file);
152
+ file.on("finish", () => file.close(resolve));
153
+ file.on("error", reject);
154
+ }
155
+ );
156
+ request.on("error", reject);
157
+ });
158
+ }
159
+
160
+ function parseChecksum(contents, asset) {
161
+ for (const line of contents.split(/\r?\n/)) {
162
+ const trimmed = line.trim();
163
+ if (!trimmed) continue;
164
+ const parts = trimmed.split(/\s+/);
165
+ if (parts.length >= 2 && path.basename(parts[parts.length - 1]) === asset) {
166
+ return parts[0];
167
+ }
168
+ }
169
+ return "";
170
+ }
171
+
172
+ function findFile(root, filename) {
173
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
174
+ const fullPath = path.join(root, entry.name);
175
+ if (entry.isFile() && entry.name === filename) {
176
+ return fullPath;
177
+ }
178
+ if (entry.isDirectory()) {
179
+ const found = findFile(fullPath, filename);
180
+ if (found) return found;
181
+ }
182
+ }
183
+ return "";
184
+ }
185
+
186
+ function isExecutable(filePath) {
187
+ try {
188
+ fs.accessSync(filePath, fs.constants.X_OK);
189
+ return true;
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+
195
+ function log(options, message) {
196
+ if (!options.quiet) {
197
+ console.log(`pallium: ${message}`);
198
+ }
199
+ }
200
+
201
+ module.exports = {
202
+ VERSION,
203
+ TAG,
204
+ assetName,
205
+ binaryPath,
206
+ ensureBinary,
207
+ installBinary,
208
+ installDir,
209
+ parseChecksum,
210
+ platformKey
211
+ };
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const assert = require("node:assert/strict");
5
+ const { assetName, parseChecksum, platformKey } = require("./lib");
6
+
7
+ assert.match(platformKey(), /^(darwin|linux|win32|freebsd|openbsd|aix|sunos)-/);
8
+ if (["darwin-arm64", "darwin-amd64", "linux-arm64", "linux-amd64"].includes(platformKey())) {
9
+ assert.match(assetName(), /^pallium_0\.9\.1_(darwin|linux)_(arm64|amd64)\.tar\.gz$/);
10
+ }
11
+ assert.equal(parseChecksum("abc123 pallium_0.9.1_darwin_arm64.tar.gz\n", "pallium_0.9.1_darwin_arm64.tar.gz"), "abc123");
12
+
13
+ console.log("pallium npm wrapper tests passed");