cld-pi 0.0.1 → 0.0.2
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/claude-p.js +15 -5
- package/package.json +1 -1
- package/scripts/install.js +126 -47
package/bin/claude-p.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Thin shim that execs the platform-specific prebuilt `claude-p` binary
|
|
3
|
-
// downloaded
|
|
3
|
+
// downloaded into ../prebuilt/.
|
|
4
|
+
//
|
|
5
|
+
// The download normally happens in `postinstall`, but npm 11.19+ gates
|
|
6
|
+
// dependency install scripts behind its allow-scripts allowlist and `npx`
|
|
7
|
+
// skips them silently, so the shim also ensures the binary itself. That is
|
|
8
|
+
// what makes `npx cld-pi` work on a fresh machine.
|
|
4
9
|
|
|
5
10
|
const { spawnSync } = require("node:child_process");
|
|
6
11
|
const fs = require("node:fs");
|
|
7
12
|
const path = require("node:path");
|
|
13
|
+
const { ensureInstalled } = require("../scripts/install.js");
|
|
8
14
|
|
|
9
15
|
function platformDir() {
|
|
10
16
|
const platform = process.platform;
|
|
@@ -18,12 +24,13 @@ function binaryPath() {
|
|
|
18
24
|
return path.join(dir, name);
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
function main() {
|
|
27
|
+
async function main() {
|
|
22
28
|
const bin = binaryPath();
|
|
23
|
-
if (!fs.existsSync(bin)) {
|
|
29
|
+
if (!fs.existsSync(bin) && !(await ensureInstalled())) {
|
|
24
30
|
console.error(
|
|
25
31
|
`claude-p: prebuilt binary not found at ${bin}\n` +
|
|
26
|
-
`
|
|
32
|
+
`The download failed (see above). If you are behind a proxy, check ` +
|
|
33
|
+
`HTTPS_PROXY/NO_PROXY, or build from source with \`zig build\`.`,
|
|
27
34
|
);
|
|
28
35
|
process.exit(2);
|
|
29
36
|
}
|
|
@@ -44,4 +51,7 @@ function main() {
|
|
|
44
51
|
process.exit(result.status ?? 0);
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
main()
|
|
54
|
+
main().catch((err) => {
|
|
55
|
+
console.error("claude-p:", err.stack || err.message);
|
|
56
|
+
process.exit(2);
|
|
57
|
+
});
|
package/package.json
CHANGED
package/scripts/install.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// Download the prebuilt `claude-p` binary for the current platform/arch from
|
|
3
|
+
// a GitHub release and drop it into prebuilt/<plat>-<arch>/.
|
|
4
|
+
//
|
|
5
|
+
// Used both as the npm `postinstall` script and from `bin/claude-p.js`, so
|
|
6
|
+
// `npx cld-pi` still works when npm skips install scripts (npm 11.19+ gates
|
|
7
|
+
// dependency scripts behind its allow-scripts allowlist and npx skips them
|
|
8
|
+
// silently by default).
|
|
4
9
|
//
|
|
5
10
|
// Strategy:
|
|
6
11
|
// 1. If a binary already exists at the expected path (e.g. installed from
|
|
@@ -11,14 +16,20 @@
|
|
|
11
16
|
// https://github.com/<repository>/releases/download/v<version>/claude-p-<plat>-<arch>.gz
|
|
12
17
|
// (repo taken from package.json), gunzip, and chmod +x.
|
|
13
18
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
19
|
+
// Proxy: Node's https client ignores HTTPS_PROXY, so when a proxy is
|
|
20
|
+
// configured (and curl is available) the download goes through curl, which
|
|
21
|
+
// honours the standard proxy variables. A failed direct request also falls
|
|
22
|
+
// back to curl.
|
|
23
|
+
//
|
|
24
|
+
// If `CLAUDE_P_SKIP_DOWNLOAD=1` is set, do nothing (useful for monorepo
|
|
25
|
+
// bootstraps where the binary is provided out-of-band).
|
|
16
26
|
|
|
17
27
|
const fs = require("node:fs");
|
|
28
|
+
const os = require("node:os");
|
|
18
29
|
const path = require("node:path");
|
|
19
30
|
const https = require("node:https");
|
|
20
31
|
const zlib = require("node:zlib");
|
|
21
|
-
const {
|
|
32
|
+
const { spawnSync } = require("node:child_process");
|
|
22
33
|
|
|
23
34
|
const pkg = require("../package.json");
|
|
24
35
|
|
|
@@ -57,33 +68,95 @@ function isSupported() {
|
|
|
57
68
|
return okPlatform && okArch;
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
function
|
|
71
|
+
function proxyConfigured() {
|
|
72
|
+
return Boolean(
|
|
73
|
+
process.env.HTTPS_PROXY ||
|
|
74
|
+
process.env.https_proxy ||
|
|
75
|
+
process.env.HTTP_PROXY ||
|
|
76
|
+
process.env.http_proxy ||
|
|
77
|
+
process.env.ALL_PROXY ||
|
|
78
|
+
process.env.all_proxy,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let curlChecked = null;
|
|
83
|
+
function hasCurl() {
|
|
84
|
+
if (curlChecked === null) {
|
|
85
|
+
const r = spawnSync("curl", ["--version"], { stdio: "ignore" });
|
|
86
|
+
curlChecked = !r.error && r.status === 0;
|
|
87
|
+
}
|
|
88
|
+
return curlChecked;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getHttps(url) {
|
|
61
92
|
return new Promise((resolve, reject) => {
|
|
62
|
-
https
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
const req = https.get(url, { timeout: 30_000 }, (res) => {
|
|
94
|
+
if (
|
|
95
|
+
res.statusCode &&
|
|
96
|
+
res.statusCode >= 300 &&
|
|
97
|
+
res.statusCode < 400 &&
|
|
98
|
+
res.headers.location
|
|
99
|
+
) {
|
|
100
|
+
// Follow redirect (GitHub release assets do this).
|
|
101
|
+
res.resume();
|
|
102
|
+
resolve(getHttps(res.headers.location));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (res.statusCode !== 200) {
|
|
106
|
+
res.resume();
|
|
107
|
+
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const chunks = [];
|
|
111
|
+
res.on("data", (c) => chunks.push(c));
|
|
112
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
113
|
+
res.on("error", reject);
|
|
114
|
+
});
|
|
115
|
+
req.on("timeout", () => req.destroy(new Error(`timed out fetching ${url}`)));
|
|
116
|
+
req.on("error", reject);
|
|
84
117
|
});
|
|
85
118
|
}
|
|
86
119
|
|
|
120
|
+
function getCurl(url) {
|
|
121
|
+
const tmp = path.join(os.tmpdir(), `claude-p-${process.pid}-${Date.now()}.gz`);
|
|
122
|
+
try {
|
|
123
|
+
const r = spawnSync(
|
|
124
|
+
"curl",
|
|
125
|
+
[
|
|
126
|
+
"-fsSL",
|
|
127
|
+
"--retry",
|
|
128
|
+
"3",
|
|
129
|
+
"--connect-timeout",
|
|
130
|
+
"20",
|
|
131
|
+
"--max-time",
|
|
132
|
+
"300",
|
|
133
|
+
"-o",
|
|
134
|
+
tmp,
|
|
135
|
+
url,
|
|
136
|
+
],
|
|
137
|
+
{ stdio: ["ignore", "ignore", "inherit"] },
|
|
138
|
+
);
|
|
139
|
+
if (r.error) throw r.error;
|
|
140
|
+
if (r.status !== 0) throw new Error(`curl exited ${r.status} for ${url}`);
|
|
141
|
+
return fs.readFileSync(tmp);
|
|
142
|
+
} finally {
|
|
143
|
+
fs.rmSync(tmp, { force: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function get(url) {
|
|
148
|
+
if (proxyConfigured() && hasCurl()) return getCurl(url);
|
|
149
|
+
try {
|
|
150
|
+
return await getHttps(url);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (hasCurl()) {
|
|
153
|
+
log(`direct download failed (${err.message}); retrying with curl`);
|
|
154
|
+
return getCurl(url);
|
|
155
|
+
}
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
87
160
|
// GitHub repo the release assets live on, from package.json `repository`.
|
|
88
161
|
function releaseRepo() {
|
|
89
162
|
const url = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
|
|
@@ -91,43 +164,49 @@ function releaseRepo() {
|
|
|
91
164
|
return m ? m[1] : null;
|
|
92
165
|
}
|
|
93
166
|
|
|
94
|
-
async function download() {
|
|
167
|
+
async function download(say) {
|
|
95
168
|
const repo = releaseRepo();
|
|
96
169
|
if (!repo) throw new Error("package.json has no GitHub `repository`; no release to download from");
|
|
97
170
|
const url = `https://github.com/${repo}/releases/download/v${pkg.version}/claude-p-${platformDir()}.gz`;
|
|
98
|
-
|
|
171
|
+
say(`downloading ${url}`);
|
|
99
172
|
const gz = await get(url);
|
|
100
173
|
const bin = zlib.gunzipSync(gz);
|
|
101
174
|
const dest = targetPath();
|
|
102
175
|
ensureDir(path.dirname(dest));
|
|
103
176
|
fs.writeFileSync(dest, bin, { mode: 0o755 });
|
|
104
|
-
|
|
177
|
+
say(`installed ${dest} (${bin.length} bytes)`);
|
|
105
178
|
}
|
|
106
179
|
|
|
107
|
-
|
|
180
|
+
// Returns true when the binary is present afterwards. Never throws.
|
|
181
|
+
async function ensureInstalled({ quiet = false } = {}) {
|
|
182
|
+
const say = quiet ? () => {} : log;
|
|
108
183
|
if (process.env.CLAUDE_P_SKIP_DOWNLOAD === "1") {
|
|
109
|
-
|
|
110
|
-
return;
|
|
184
|
+
say("CLAUDE_P_SKIP_DOWNLOAD set; skipping");
|
|
185
|
+
return alreadyInstalled();
|
|
111
186
|
}
|
|
112
187
|
if (!isSupported()) {
|
|
113
|
-
|
|
114
|
-
return;
|
|
188
|
+
say(`platform ${platformDir()} not supported; skipping`);
|
|
189
|
+
return false;
|
|
115
190
|
}
|
|
116
191
|
if (alreadyInstalled()) {
|
|
117
|
-
|
|
118
|
-
return;
|
|
192
|
+
say(`already installed at ${targetPath()}`);
|
|
193
|
+
return true;
|
|
119
194
|
}
|
|
120
195
|
try {
|
|
121
|
-
await download();
|
|
196
|
+
await download(say);
|
|
197
|
+
return true;
|
|
122
198
|
} catch (err) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// print a clear error if the user runs `claude-p` later.
|
|
199
|
+
say(`download failed: ${err.message}`);
|
|
200
|
+
say("you can build from source with: zig build -Doptimize=ReleaseSafe");
|
|
201
|
+
return false;
|
|
127
202
|
}
|
|
128
203
|
}
|
|
129
204
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
205
|
+
module.exports = { ensureInstalled, targetPath, platformDir, isSupported };
|
|
206
|
+
|
|
207
|
+
if (require.main === module) {
|
|
208
|
+
ensureInstalled().catch((err) => {
|
|
209
|
+
log(`unexpected error: ${err.stack || err.message}`);
|
|
210
|
+
// Don't break the npm install; the shim reports the missing binary.
|
|
211
|
+
});
|
|
212
|
+
}
|