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 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 by scripts/install.js into ../prebuilt/.
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
- `Re-run \`npm install claude-p\` or build from source with \`zig build\`.`,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cld-pi",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "claude-p fork: `claude -p` drop-in plus an interactive TUI shell around Claude Code (fullscreen), with prefix completion, overlays and JS plugins.",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // Postinstall: download the prebuilt `claude-p` binary for the current
3
- // platform/arch from a GitHub release and drop it into prebuilt/<plat>-<arch>/.
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
- // If `CLAUDE_P_SKIP_DOWNLOAD=1` is set, exit 0 without doing anything (useful
15
- // for monorepo bootstraps where the binary is provided out-of-band).
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 { execFileSync } = require("node:child_process");
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 get(url) {
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
- .get(url, (res) => {
64
- if (
65
- res.statusCode &&
66
- res.statusCode >= 300 &&
67
- res.statusCode < 400 &&
68
- res.headers.location
69
- ) {
70
- // Follow redirect (GitHub release assets do this).
71
- resolve(get(res.headers.location));
72
- return;
73
- }
74
- if (res.statusCode !== 200) {
75
- reject(new Error(`HTTP ${res.statusCode} for ${url}`));
76
- return;
77
- }
78
- const chunks = [];
79
- res.on("data", (c) => chunks.push(c));
80
- res.on("end", () => resolve(Buffer.concat(chunks)));
81
- res.on("error", reject);
82
- })
83
- .on("error", reject);
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
- log(`downloading ${url}`);
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
- log(`installed ${dest} (${bin.length} bytes)`);
177
+ say(`installed ${dest} (${bin.length} bytes)`);
105
178
  }
106
179
 
107
- async function main() {
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
- log("CLAUDE_P_SKIP_DOWNLOAD set; skipping");
110
- return;
184
+ say("CLAUDE_P_SKIP_DOWNLOAD set; skipping");
185
+ return alreadyInstalled();
111
186
  }
112
187
  if (!isSupported()) {
113
- log(`platform ${platformDir()} not supported; skipping`);
114
- return;
188
+ say(`platform ${platformDir()} not supported; skipping`);
189
+ return false;
115
190
  }
116
191
  if (alreadyInstalled()) {
117
- log(`already installed at ${targetPath()}`);
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
- log(`download failed: ${err.message}`);
124
- log("you can build from source with: zig build -Doptimize=ReleaseSafe");
125
- // Don't fail the install — leave the binary missing; the shim will
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
- main().catch((err) => {
131
- log(`unexpected error: ${err.stack || err.message}`);
132
- // Same as above: don't break the npm install.
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
+ }