@chikhamx/voidx 1.0.1 → 1.1.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.
@@ -1,64 +1,319 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- // Lightweight environment check that runs after npm install.
5
- // Detects Python 3.11+ and prints a hint if missing. Never blocks installation.
4
+ // Post-install: download a standalone Python, create venv, pip install voidx.
5
+ // Falls back to system Python if download fails.
6
6
 
7
+ const { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs");
7
8
  const { spawnSync } = require("child_process");
9
+ const { get, request } = require("https");
10
+ const { createGunzip } = require("zlib");
11
+ const { pipeline } = require("stream/promises");
12
+ const { createUnzip } = require("zlib");
13
+ const { join, dirname } = require("path");
14
+ const os = require("os");
15
+ const fs = require("fs");
16
+ const path = require("path");
17
+ const { execSync } = require("child_process");
8
18
 
9
- function main() {
10
- const probe = detectPython();
11
- if (probe.ok) {
12
- console.error(`\n✅ voidx: Python ${probe.versionText} found at "${probe.path}"\n`);
13
- return;
19
+ const pkg = require("../package.json");
20
+
21
+ // ── Python build metadata ──────────────────────────────────────────────────
22
+ // Pin to a specific python-build-standalone release tag and CPython version.
23
+ // Update these when upgrading the bundled Python.
24
+ const PBS_TAG = "20260602";
25
+ const PBS_CPYTHON = "3.12.13";
26
+ const PBS_RELEASE_BASE = `https://github.com/astral-sh/python-build-standalone/releases/download/${PBS_TAG}`;
27
+
28
+ // ── Platform mapping ───────────────────────────────────────────────────────
29
+ function getPlatformInfo() {
30
+ const platform = os.platform();
31
+ const arch = os.arch();
32
+
33
+ const map = {
34
+ "darwin-arm64": { target: "aarch64-apple-darwin" },
35
+ "darwin-x64": { target: "x86_64-apple-darwin" },
36
+ "linux-arm64": { target: "aarch64-unknown-linux-gnu" },
37
+ "linux-x64": { target: "x86_64-unknown-linux-gnu" },
38
+ "win32-arm64": { target: "aarch64-pc-windows-msvc" },
39
+ "win32-x64": { target: "x86_64-pc-windows-msvc" },
40
+ };
41
+
42
+ const key = `${platform}-${arch}`;
43
+ const entry = map[key];
44
+ if (!entry) {
45
+ return null;
46
+ }
47
+ return { platform, arch, target: entry.target };
48
+ }
49
+
50
+ function getPbsFilename(target) {
51
+ return `cpython-${PBS_CPYTHON}+${PBS_TAG}-${target}-install_only_stripped.tar.gz`;
52
+ }
53
+
54
+ // ── Paths ──────────────────────────────────────────────────────────────────
55
+ function resolveDataHome(env) {
56
+ if (env.VOIDX_NPM_HOME) return path.resolve(env.VOIDX_NPM_HOME);
57
+ if (process.platform === "win32") {
58
+ return env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
59
+ }
60
+ return env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
61
+ }
62
+
63
+ function resolvePythonDir(env) {
64
+ return path.join(resolveDataHome(env), "voidx", "python");
65
+ }
66
+
67
+ function resolveVenvDir(env) {
68
+ if (env.VOIDX_NPM_VENV) return path.resolve(env.VOIDX_NPM_VENV);
69
+ return path.join(resolveDataHome(env), "voidx", "npm-venv");
70
+ }
71
+
72
+ function resolveVenvPython(venvDir) {
73
+ return process.platform === "win32"
74
+ ? path.join(venvDir, "Scripts", "python.exe")
75
+ : path.join(venvDir, "bin", "python");
76
+ }
77
+
78
+ function resolveVoidxExecutable(venvDir) {
79
+ return process.platform === "win32"
80
+ ? path.join(venvDir, "Scripts", "voidx.exe")
81
+ : path.join(venvDir, "bin", "voidx");
82
+ }
83
+
84
+ function resolveBundledPython(pythonDir, platform) {
85
+ // install_only_stripped layout:
86
+ // unix: python/python/bin/python3
87
+ // win32: python/python/python.exe
88
+ const installDir = path.join(pythonDir, "python");
89
+ if (platform === "win32") {
90
+ return path.join(installDir, "python.exe");
91
+ }
92
+ return path.join(installDir, "bin", "python3");
93
+ }
94
+
95
+ // ── Download ───────────────────────────────────────────────────────────────
96
+ function downloadFile(url, dest) {
97
+ return new Promise((resolve, reject) => {
98
+ const doRequest = (currentUrl, redirects = 0) => {
99
+ if (redirects > 5) {
100
+ return reject(new Error(`Too many redirects downloading ${url}`));
101
+ }
102
+ const mod = currentUrl.startsWith("https") ? require("https") : require("http");
103
+ mod.get(currentUrl, { timeout: 60000 }, (res) => {
104
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
105
+ return doRequest(res.headers.location, redirects + 1);
106
+ }
107
+ if (res.statusCode !== 200) {
108
+ return reject(new Error(`HTTP ${res.statusCode} downloading ${currentUrl}`));
109
+ }
110
+ const total = parseInt(res.headers["content-length"] || "0", 10);
111
+ let downloaded = 0;
112
+ let lastPercent = -1;
113
+
114
+ res.on("data", (chunk) => {
115
+ downloaded += chunk.length;
116
+ if (total > 0) {
117
+ const pct = Math.floor((downloaded / total) * 100);
118
+ if (pct !== lastPercent && pct % 10 === 0) {
119
+ lastPercent = pct;
120
+ process.stderr.write(` ${pct}% (${Math.round(downloaded / 1024 / 1024)}/${Math.round(total / 1024 / 1024)} MB)\n`);
121
+ }
122
+ }
123
+ });
124
+
125
+ const stream = createWriteStream(dest);
126
+ res.pipe(stream);
127
+ stream.on("finish", () => {
128
+ stream.close();
129
+ resolve(dest);
130
+ });
131
+ stream.on("error", reject);
132
+ res.on("error", reject);
133
+ }).on("error", reject);
134
+ };
135
+ doRequest(url);
136
+ });
137
+ }
138
+
139
+ // ── Extract ────────────────────────────────────────────────────────────────
140
+ function extractTarGz(archive, destDir) {
141
+ // Use the system tar command — available on macOS, Linux, and modern Windows (10+)
142
+ const result = spawnSync("tar", ["-xzf", archive, "-C", destDir], {
143
+ encoding: "utf8",
144
+ windowsHide: true,
145
+ });
146
+ if (result.error) {
147
+ throw new Error(`Failed to extract Python: ${result.error.message}`);
14
148
  }
149
+ if (result.status !== 0) {
150
+ throw new Error(`Failed to extract Python (tar exited ${result.status}).`);
151
+ }
152
+ }
153
+
154
+ // ── Version marker ─────────────────────────────────────────────────────────
155
+ function readMarker(markerPath) {
156
+ try { return readFileSync(markerPath, "utf8"); } catch { return ""; }
157
+ }
15
158
 
16
- console.error("\n⚠️ voidx requires Python 3.11+, but no compatible Python was found.\n");
17
- console.error(hint());
18
- console.error(`\nOnce Python 3.11+ is installed, voidx will bootstrap the rest on first run.\n`);
159
+ function writeMarker(markerPath, content) {
160
+ mkdirSync(dirname(markerPath), { recursive: true });
161
+ writeFileSync(markerPath, content);
162
+ }
163
+
164
+ // ── pip install ────────────────────────────────────────────────────────────
165
+ function pipInstall(venvPython, packageSpec, env) {
166
+ const pipEnv = Object.assign({}, env, {
167
+ PIP_NO_INPUT: "1",
168
+ PIP_DISABLE_PIP_VERSION_CHECK: "1",
169
+ PYTHON_KEYRING_BACKEND: "keyring.backends.null.Keyring",
170
+ });
171
+ const result = spawnSync(
172
+ venvPython,
173
+ ["-m", "pip", "install", "--upgrade", "--progress-bar", "on", packageSpec],
174
+ { encoding: "utf8", stdio: "inherit", windowsHide: true, env: pipEnv }
175
+ );
176
+ if (result.error) {
177
+ throw new Error(`pip install failed: ${result.error.message}`);
178
+ }
179
+ if (result.status !== 0) {
180
+ throw new Error("pip install failed. See errors above.");
181
+ }
19
182
  }
20
183
 
21
- function detectPython() {
184
+ // ── System Python fallback ─────────────────────────────────────────────────
185
+ function probeSystemPython() {
22
186
  const candidates = [
23
- "python3", "python",
24
- "python3.13", "python3.12", "python3.11",
187
+ { command: "python3", args: [] },
188
+ { command: "python", args: [] },
189
+ { command: "python3.12", args: [] },
190
+ { command: "python3.11", args: [] },
25
191
  ];
26
192
  if (process.platform === "win32") {
27
- candidates.push("py");
193
+ candidates.push({ command: "py", args: ["-3"] });
28
194
  }
29
195
 
30
- for (const cmd of candidates) {
31
- const args = process.platform === "win32" && cmd === "py" ? ["-3.11"] : [];
32
- const result = spawnSync(cmd, [...args, "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')"], {
33
- encoding: "utf8",
34
- windowsHide: true,
35
- });
196
+ for (const candidate of candidates) {
197
+ const result = spawnSync(
198
+ candidate.command,
199
+ [...candidate.args, "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"],
200
+ { encoding: "utf8", windowsHide: true }
201
+ );
36
202
  if (result.error || result.status !== 0) continue;
37
- const versionText = (result.stdout || "").trim();
38
- const match = /^(\d+)\.(\d+)\.\d+/.exec(versionText);
203
+ const ver = (result.stdout || "").trim();
204
+ const match = /^(\d+)\.(\d+)/.exec(ver);
39
205
  if (!match) continue;
40
- const major = Number.parseInt(match[1], 10);
41
- const minor = Number.parseInt(match[2], 10);
206
+ const major = parseInt(match[1], 10);
207
+ const minor = parseInt(match[2], 10);
42
208
  if (major > 3 || (major === 3 && minor >= 11)) {
43
- return { ok: true, versionText, path: cmd };
209
+ return { command: candidate.command, args: candidate.args };
44
210
  }
45
211
  }
46
- return { ok: false };
212
+ return null;
47
213
  }
48
214
 
49
- function hint() {
50
- if (process.platform === "darwin") {
51
- return " Install: brew install python@3.12";
215
+ // ── Main ───────────────────────────────────────────────────────────────────
216
+ async function main() {
217
+ const env = process.env;
218
+ const venvDir = resolveVenvDir(env);
219
+ const venvPython = resolveVenvPython(venvDir);
220
+ const executable = resolveVoidxExecutable(venvDir);
221
+ const packageSpec = env.VOIDX_NPM_PACKAGE_SPEC || `voidx==${pkg.version}`;
222
+ const markerPath = path.join(venvDir, ".voidx-npm-version");
223
+ const marker = `${pkg.version}\n${packageSpec}\n${PBS_TAG}\n${PBS_CPYTHON}\n`;
224
+
225
+ // Already set up and up-to-date?
226
+ if (existsSync(executable) && readMarker(markerPath) === marker) {
227
+ console.error(`\n✅ voidx ${pkg.version} ready (cached)\n`);
228
+ return;
52
229
  }
53
- if (process.platform === "linux") {
54
- return " Install via your package manager, e.g.:\n" +
55
- " sudo apt install python3.12 (Debian/Ubuntu)\n" +
56
- " sudo dnf install python3.12 (Fedora)";
230
+
231
+ // Step 1: Get a Python interpreter (bundled or system)
232
+ let pythonForVenv;
233
+ const platformInfo = getPlatformInfo();
234
+
235
+ if (platformInfo && env.VOIDX_NPM_SKIP_BUNDLED_PYTHON !== "1") {
236
+ console.error(`\n🐍 Setting up voidx ${pkg.version}…\n`);
237
+ console.error(" [1/3] Downloading Python runtime…");
238
+
239
+ const pythonDir = resolvePythonDir(env);
240
+ const pbsFilename = getPbsFilename(platformInfo.target);
241
+ const pbsUrl = `${PBS_RELEASE_BASE}/${pbsFilename}`;
242
+ const archivePath = path.join(pythonDir, pbsFilename);
243
+ const bundledPython = resolveBundledPython(pythonDir, platformInfo.platform);
244
+
245
+ if (!existsSync(bundledPython)) {
246
+ try {
247
+ mkdirSync(pythonDir, { recursive: true });
248
+
249
+ if (!existsSync(archivePath)) {
250
+ console.error(` Downloading ${pbsFilename}…`);
251
+ await downloadFile(pbsUrl, archivePath);
252
+ console.error(" Download complete.");
253
+ }
254
+
255
+ console.error(" Extracting Python runtime…");
256
+ extractTarGz(archivePath, pythonDir);
257
+ console.error(" Extraction complete.");
258
+
259
+ // Clean up archive to save disk
260
+ try { fs.unlinkSync(archivePath); } catch {}
261
+ } catch (err) {
262
+ console.error(`\n ⚠️ Bundled Python download failed: ${err.message}`);
263
+ console.error(" Falling back to system Python…\n");
264
+ }
265
+ } else {
266
+ console.error(" Using cached Python runtime.");
267
+ }
268
+
269
+ if (existsSync(bundledPython)) {
270
+ pythonForVenv = { command: bundledPython, args: [], label: "bundled" };
271
+ }
57
272
  }
58
- if (process.platform === "win32") {
59
- return " Install: https://python.org/downloads";
273
+
274
+ // Fallback to system Python
275
+ if (!pythonForVenv) {
276
+ const sysPython = probeSystemPython();
277
+ if (sysPython) {
278
+ pythonForVenv = sysPython;
279
+ console.error("\n Using system Python.\n");
280
+ } else {
281
+ console.error("\n ❌ No Python 3.11+ found. Please install Python 3.11+ and run voidx again.\n");
282
+ console.error(" macOS: brew install python@3.12");
283
+ console.error(" Linux: sudo apt install python3.12");
284
+ console.error(" Windows: https://python.org/downloads\n");
285
+ process.exit(1);
286
+ }
60
287
  }
61
- return " Install Python 3.11+ from https://python.org/downloads";
288
+
289
+ // Step 2: Create venv
290
+ console.error(" [2/3] Creating virtual environment…");
291
+ if (!existsSync(venvPython)) {
292
+ const venvResult = spawnSync(
293
+ pythonForVenv.command,
294
+ [...pythonForVenv.args, "-m", "venv", venvDir],
295
+ { encoding: "utf8", stdio: "inherit", windowsHide: true }
296
+ );
297
+ if (venvResult.error) {
298
+ console.error(`\n ❌ Failed to create venv: ${venvResult.error.message}\n`);
299
+ process.exit(1);
300
+ }
301
+ if (venvResult.status !== 0) {
302
+ console.error("\n ❌ Failed to create venv. See errors above.\n");
303
+ process.exit(1);
304
+ }
305
+ }
306
+
307
+ // Step 3: pip install
308
+ console.error(" [3/3] Installing voidx and dependencies…");
309
+ pipInstall(venvPython, packageSpec, env);
310
+
311
+ // Done
312
+ writeMarker(markerPath, marker);
313
+ console.error(`\n✅ voidx ${pkg.version} installed! Run: voidx\n`);
62
314
  }
63
315
 
64
- main();
316
+ main().catch((err) => {
317
+ console.error(`\n❌ Setup failed: ${err.message}\n`);
318
+ process.exit(1);
319
+ });
package/bin/voidx.js CHANGED
@@ -8,6 +8,13 @@ const path = require("path");
8
8
 
9
9
  const pkg = require("../package.json");
10
10
 
11
+ // ── Configuration ──────────────────────────────────────────────────────────
12
+
13
+ const PBS_TAG = "20260602";
14
+ const PBS_PYTHON_MAJOR = "3.12";
15
+
16
+ // ── Main ───────────────────────────────────────────────────────────────────
17
+
11
18
  function main(argv = process.argv.slice(2), env = process.env) {
12
19
  try {
13
20
  const python = selectPython(env);
@@ -23,14 +30,17 @@ function main(argv = process.argv.slice(2), env = process.env) {
23
30
  process.exit(code === null ? 1 : code);
24
31
  });
25
32
  child.on("error", (error) => {
26
- fail(`Failed to start voidx from npm-managed environment: ${error.message}`);
33
+ fail(`Failed to start voidx: ${error.message}`);
27
34
  });
28
35
  } catch (error) {
29
36
  fail(error.message);
30
37
  }
31
38
  }
32
39
 
40
+ // ── Python selection ───────────────────────────────────────────────────────
41
+
33
42
  function selectPython(env) {
43
+ // 1. Explicit override
34
44
  const explicit = env.VOIDX_PYTHON;
35
45
  if (explicit) {
36
46
  const candidate = { command: explicit, args: [], label: explicit };
@@ -46,6 +56,17 @@ function selectPython(env) {
46
56
  return candidate;
47
57
  }
48
58
 
59
+ // 2. Bundled Python (downloaded by postinstall)
60
+ const bundledBin = resolveBundledPythonBin(env);
61
+ if (bundledBin && fs.existsSync(bundledBin)) {
62
+ const candidate = { command: bundledBin, args: [], label: "bundled" };
63
+ const probe = probePython(candidate);
64
+ if (probe.ok && isCompatible(probe.version)) {
65
+ return candidate;
66
+ }
67
+ }
68
+
69
+ // 3. System Python
49
70
  const candidates = [
50
71
  { command: "python3", args: [], label: "python3" },
51
72
  { command: "python", args: [], label: "python" },
@@ -54,7 +75,7 @@ function selectPython(env) {
54
75
  { command: "python3.11", args: [], label: "python3.11" },
55
76
  ];
56
77
  if (process.platform === "win32") {
57
- candidates.push({ command: "py", args: ["-3.11"], label: "py -3.11" });
78
+ candidates.push({ command: "py", args: ["-3"], label: "py -3" });
58
79
  }
59
80
 
60
81
  const oldVersions = [];
@@ -76,26 +97,10 @@ function selectPython(env) {
76
97
  );
77
98
  }
78
99
  throw new Error(
79
- `voidx npm launcher requires Python 3.11+. ${hint}`
100
+ `voidx requires Python 3.11+. No Python found.\n${hint}`
80
101
  );
81
102
  }
82
103
 
83
- function pythonHint() {
84
- if (process.platform === "darwin") {
85
- return "Install Python 3.11+ via: brew install python@3.12\n" +
86
- "Or point to an existing install: VOIDX_PYTHON=/path/to/python3 voidx";
87
- }
88
- if (process.platform === "linux") {
89
- return "Install Python 3.11+ via your package manager (apt/dnf).\n" +
90
- "Or point to an existing install: VOIDX_PYTHON=/path/to/python3 voidx";
91
- }
92
- if (process.platform === "win32") {
93
- return "Install Python 3.11+ from https://python.org/downloads\n" +
94
- "Or: VOIDX_PYTHON=C:\\Python312\\python.exe voidx";
95
- }
96
- return "Install Python 3.11+ or set VOIDX_PYTHON.";
97
- }
98
-
99
104
  function probePython(candidate) {
100
105
  const code = [
101
106
  "import sys",
@@ -131,6 +136,69 @@ function isCompatible(version) {
131
136
  return version[0] > 3 || (version[0] === 3 && version[1] >= 11);
132
137
  }
133
138
 
139
+ function pythonHint() {
140
+ if (process.platform === "darwin") {
141
+ return "Install Python 3.11+ via: brew install python@3.12\n" +
142
+ "Or reinstall voidx to get the bundled Python.";
143
+ }
144
+ if (process.platform === "linux") {
145
+ return "Install Python 3.11+ via your package manager (apt/dnf).\n" +
146
+ "Or reinstall voidx to get the bundled Python.";
147
+ }
148
+ if (process.platform === "win32") {
149
+ return "Install Python 3.11+ from https://python.org/downloads\n" +
150
+ "Or reinstall voidx to get the bundled Python.";
151
+ }
152
+ return "Install Python 3.11+ or reinstall voidx.";
153
+ }
154
+
155
+ // ── Paths ──────────────────────────────────────────────────────────────────
156
+
157
+ function resolveDataHome(env) {
158
+ if (env.VOIDX_NPM_HOME) {
159
+ return path.resolve(env.VOIDX_NPM_HOME);
160
+ }
161
+ if (process.platform === "win32") {
162
+ return env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
163
+ }
164
+ return env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
165
+ }
166
+
167
+ function resolvePythonDir(env) {
168
+ if (env.VOIDX_NPM_PYTHON_DIR) {
169
+ return path.resolve(env.VOIDX_NPM_PYTHON_DIR);
170
+ }
171
+ return path.join(resolveDataHome(env), "voidx", "python");
172
+ }
173
+
174
+ function resolveBundledPythonBin(env) {
175
+ const pythonDir = resolvePythonDir(env);
176
+ return process.platform === "win32"
177
+ ? path.join(pythonDir, "python", "python.exe")
178
+ : path.join(pythonDir, "python", "bin", `python${PBS_PYTHON_MAJOR}`);
179
+ }
180
+
181
+ function resolveVenvDir(env) {
182
+ if (env.VOIDX_NPM_VENV) {
183
+ return path.resolve(env.VOIDX_NPM_VENV);
184
+ }
185
+ return path.join(resolveDataHome(env), "voidx", "npm-venv");
186
+ }
187
+
188
+ function resolveVenvPython(venvDir) {
189
+ return process.platform === "win32"
190
+ ? path.join(venvDir, "Scripts", "python.exe")
191
+ : path.join(venvDir, "bin", "python");
192
+ }
193
+
194
+ function resolveVoidxExecutable(venvDir) {
195
+ return process.platform === "win32"
196
+ ? path.join(venvDir, "Scripts", "voidx.exe")
197
+ : path.join(venvDir, "bin", "voidx");
198
+ }
199
+
200
+ // ── Venv setup ─────────────────────────────────────────────────────────────
201
+
134
202
  function ensureVenv(python, venvDir, env) {
135
203
  const executable = resolveVoidxExecutable(venvDir);
136
204
  if (env.VOIDX_NPM_SKIP_BOOTSTRAP === "1") {
@@ -142,7 +210,8 @@ function ensureVenv(python, venvDir, env) {
142
210
 
143
211
  const markerPath = path.join(venvDir, ".voidx-npm-version");
144
212
  const packageSpec = env.VOIDX_NPM_PACKAGE_SPEC || `voidx==${pkg.version}`;
145
- const marker = `${pkg.version}\n${packageSpec}\n`;
213
+ // Marker must match postinstall.js format (includes PBS_TAG + PBS_CPYTHON)
214
+ const marker = `${pkg.version}\n${packageSpec}\n${PBS_TAG}\n3.12.13\n`;
146
215
  if (fs.existsSync(executable) && readFile(markerPath) === marker) {
147
216
  debug(env, `Using cached npm-managed environment at ${venvDir}`);
148
217
  return;
@@ -209,34 +278,7 @@ function ensureVenv(python, venvDir, env) {
209
278
  fs.writeFileSync(markerPath, marker);
210
279
  }
211
280
 
212
- function resolveVenvDir(env) {
213
- if (env.VOIDX_NPM_VENV) {
214
- return path.resolve(env.VOIDX_NPM_VENV);
215
- }
216
- return path.join(resolveDataHome(env), "voidx", "npm-venv");
217
- }
218
-
219
- function resolveDataHome(env) {
220
- if (env.VOIDX_NPM_HOME) {
221
- return path.resolve(env.VOIDX_NPM_HOME);
222
- }
223
- if (process.platform === "win32") {
224
- return env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
225
- }
226
- return env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
227
- }
228
-
229
- function resolveVenvPython(venvDir) {
230
- return process.platform === "win32"
231
- ? path.join(venvDir, "Scripts", "python.exe")
232
- : path.join(venvDir, "bin", "python");
233
- }
234
-
235
- function resolveVoidxExecutable(venvDir) {
236
- return process.platform === "win32"
237
- ? path.join(venvDir, "Scripts", "voidx.exe")
238
- : path.join(venvDir, "bin", "voidx");
239
- }
281
+ // ── Helpers ────────────────────────────────────────────────────────────────
240
282
 
241
283
  function readFile(filePath) {
242
284
  try {
@@ -266,5 +308,6 @@ module.exports = {
266
308
  parseVersion,
267
309
  resolveDataHome,
268
310
  resolveVenvDir,
311
+ resolveBundledPythonBin,
269
312
  selectPython,
270
313
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chikhamx/voidx",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "npm launcher for voidx, a terminal AI coding agent.",
5
5
  "bin": {
6
6
  "voidx": "bin/voidx.js"
@@ -11,6 +11,15 @@
11
11
  "engines": {
12
12
  "node": ">=16"
13
13
  },
14
+ "os": [
15
+ "darwin",
16
+ "linux",
17
+ "win32"
18
+ ],
19
+ "cpu": [
20
+ "x64",
21
+ "arm64"
22
+ ],
14
23
  "keywords": [
15
24
  "ai",
16
25
  "agent",