@chikhamx/voidx 1.0.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/bin/voidx.js +224 -0
- package/package.json +23 -0
package/bin/voidx.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawn, spawnSync } = require("child_process");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
|
|
9
|
+
const pkg = require("../package.json");
|
|
10
|
+
|
|
11
|
+
function main(argv = process.argv.slice(2), env = process.env) {
|
|
12
|
+
try {
|
|
13
|
+
const python = selectPython(env);
|
|
14
|
+
const venvDir = resolveVenvDir(env);
|
|
15
|
+
ensureVenv(python, venvDir, env);
|
|
16
|
+
const executable = resolveVoidxExecutable(venvDir);
|
|
17
|
+
const child = spawn(executable, argv, { stdio: "inherit" });
|
|
18
|
+
child.on("exit", (code, signal) => {
|
|
19
|
+
if (signal) {
|
|
20
|
+
process.kill(process.pid, signal);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
process.exit(code === null ? 1 : code);
|
|
24
|
+
});
|
|
25
|
+
child.on("error", (error) => {
|
|
26
|
+
fail(`Failed to start voidx from npm-managed environment: ${error.message}`);
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
fail(error.message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function selectPython(env) {
|
|
34
|
+
const explicit = env.VOIDX_PYTHON;
|
|
35
|
+
if (explicit) {
|
|
36
|
+
const candidate = { command: explicit, args: [], label: explicit };
|
|
37
|
+
const probe = probePython(candidate);
|
|
38
|
+
if (!probe.ok) {
|
|
39
|
+
throw new Error(probe.reason || `Unable to run Python at ${explicit}.`);
|
|
40
|
+
}
|
|
41
|
+
if (!isCompatible(probe.version)) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`voidx requires Python 3.11+. Found Python ${probe.versionText} at ${explicit}.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return candidate;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const candidates = [
|
|
50
|
+
{ command: "python3", args: [], label: "python3" },
|
|
51
|
+
{ command: "python", args: [], label: "python" },
|
|
52
|
+
];
|
|
53
|
+
if (process.platform === "win32") {
|
|
54
|
+
candidates.push({ command: "py", args: ["-3.11"], label: "py -3.11" });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const oldVersions = [];
|
|
58
|
+
for (const candidate of candidates) {
|
|
59
|
+
const probe = probePython(candidate);
|
|
60
|
+
if (!probe.ok) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (isCompatible(probe.version)) {
|
|
64
|
+
return candidate;
|
|
65
|
+
}
|
|
66
|
+
oldVersions.push(`${probe.versionText} at ${candidate.label}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (oldVersions.length > 0) {
|
|
70
|
+
throw new Error(`voidx requires Python 3.11+. Found ${oldVersions.join(", ")}.`);
|
|
71
|
+
}
|
|
72
|
+
throw new Error(
|
|
73
|
+
"voidx npm launcher requires Python 3.11+. Install Python or set VOIDX_PYTHON."
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function probePython(candidate) {
|
|
78
|
+
const code = [
|
|
79
|
+
"import sys",
|
|
80
|
+
"print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')",
|
|
81
|
+
].join("; ");
|
|
82
|
+
const result = spawnSync(candidate.command, [...candidate.args, "-c", code], {
|
|
83
|
+
encoding: "utf8",
|
|
84
|
+
windowsHide: true,
|
|
85
|
+
});
|
|
86
|
+
if (result.error) {
|
|
87
|
+
return { ok: false, reason: result.error.message };
|
|
88
|
+
}
|
|
89
|
+
if (result.status !== 0) {
|
|
90
|
+
return { ok: false, reason: (result.stderr || "").trim() };
|
|
91
|
+
}
|
|
92
|
+
const versionText = (result.stdout || "").trim();
|
|
93
|
+
const version = parseVersion(versionText);
|
|
94
|
+
if (!version) {
|
|
95
|
+
return { ok: false, reason: `Unable to parse Python version: ${versionText}` };
|
|
96
|
+
}
|
|
97
|
+
return { ok: true, version, versionText };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseVersion(value) {
|
|
101
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(value);
|
|
102
|
+
if (!match) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return match.slice(1).map((part) => Number.parseInt(part, 10));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isCompatible(version) {
|
|
109
|
+
return version[0] > 3 || (version[0] === 3 && version[1] >= 11);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ensureVenv(python, venvDir, env) {
|
|
113
|
+
const executable = resolveVoidxExecutable(venvDir);
|
|
114
|
+
if (env.VOIDX_NPM_SKIP_BOOTSTRAP === "1") {
|
|
115
|
+
if (!fs.existsSync(executable)) {
|
|
116
|
+
throw new Error(`voidx executable not found in ${venvDir}.`);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const markerPath = path.join(venvDir, ".voidx-npm-version");
|
|
122
|
+
const packageSpec = env.VOIDX_NPM_PACKAGE_SPEC || `voidx==${pkg.version}`;
|
|
123
|
+
const marker = `${pkg.version}\n${packageSpec}\n`;
|
|
124
|
+
if (fs.existsSync(executable) && readFile(markerPath) === marker) {
|
|
125
|
+
debug(env, `Using cached npm-managed environment at ${venvDir}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fs.mkdirSync(path.dirname(venvDir), { recursive: true });
|
|
130
|
+
const venvPython = resolveVenvPython(venvDir);
|
|
131
|
+
if (!fs.existsSync(venvPython)) {
|
|
132
|
+
debug(env, `Creating npm-managed Python environment at ${venvDir}`);
|
|
133
|
+
runChecked(
|
|
134
|
+
python.command,
|
|
135
|
+
[...python.args, "-m", "venv", venvDir],
|
|
136
|
+
"Failed to create the npm-managed Python environment.",
|
|
137
|
+
env
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
debug(env, `Installing ${packageSpec} into ${venvDir}`);
|
|
142
|
+
runChecked(
|
|
143
|
+
venvPython,
|
|
144
|
+
["-m", "pip", "install", "--upgrade", packageSpec],
|
|
145
|
+
`Failed to install ${packageSpec} into the npm-managed Python environment.`,
|
|
146
|
+
env
|
|
147
|
+
);
|
|
148
|
+
fs.writeFileSync(markerPath, marker);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function runChecked(command, args, errorMessage, env) {
|
|
152
|
+
const result = spawnSync(command, args, {
|
|
153
|
+
encoding: "utf8",
|
|
154
|
+
stdio: env.VOIDX_NPM_DEBUG === "1" ? "inherit" : "pipe",
|
|
155
|
+
windowsHide: true,
|
|
156
|
+
});
|
|
157
|
+
if (result.error) {
|
|
158
|
+
throw new Error(`${errorMessage} ${result.error.message}`);
|
|
159
|
+
}
|
|
160
|
+
if (result.status !== 0) {
|
|
161
|
+
const stderr = result.stderr ? result.stderr.trim() : "";
|
|
162
|
+
throw new Error(stderr ? `${errorMessage} ${stderr}` : errorMessage);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function resolveVenvDir(env) {
|
|
167
|
+
if (env.VOIDX_NPM_VENV) {
|
|
168
|
+
return path.resolve(env.VOIDX_NPM_VENV);
|
|
169
|
+
}
|
|
170
|
+
return path.join(resolveDataHome(env), "voidx", "npm-venv");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function resolveDataHome(env) {
|
|
174
|
+
if (env.VOIDX_NPM_HOME) {
|
|
175
|
+
return path.resolve(env.VOIDX_NPM_HOME);
|
|
176
|
+
}
|
|
177
|
+
if (process.platform === "win32") {
|
|
178
|
+
return env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
179
|
+
}
|
|
180
|
+
return env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resolveVenvPython(venvDir) {
|
|
184
|
+
return process.platform === "win32"
|
|
185
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
186
|
+
: path.join(venvDir, "bin", "python");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function resolveVoidxExecutable(venvDir) {
|
|
190
|
+
return process.platform === "win32"
|
|
191
|
+
? path.join(venvDir, "Scripts", "voidx.exe")
|
|
192
|
+
: path.join(venvDir, "bin", "voidx");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function readFile(filePath) {
|
|
196
|
+
try {
|
|
197
|
+
return fs.readFileSync(filePath, "utf8");
|
|
198
|
+
} catch {
|
|
199
|
+
return "";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function debug(env, message) {
|
|
204
|
+
if (env.VOIDX_NPM_DEBUG === "1") {
|
|
205
|
+
console.error(`[voidx npm] ${message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function fail(message) {
|
|
210
|
+
console.error(message);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (require.main === module) {
|
|
215
|
+
main();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
isCompatible,
|
|
220
|
+
parseVersion,
|
|
221
|
+
resolveDataHome,
|
|
222
|
+
resolveVenvDir,
|
|
223
|
+
selectPython,
|
|
224
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chikhamx/voidx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "npm launcher for voidx, a terminal AI coding agent.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"voidx": "bin/voidx.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=16"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ai",
|
|
16
|
+
"agent",
|
|
17
|
+
"coding-agent",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"check": "node --check bin/voidx.js"
|
|
22
|
+
}
|
|
23
|
+
}
|