iterate-harness 1.6.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/LICENSE +21 -0
- package/README.md +75 -0
- package/bin/ih.js +14 -0
- package/bin/iterate-harness.js +14 -0
- package/lib/bootstrap.js +330 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 OpenHarness Contributors, iterate-harness Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# iterate-harness (npm distribution wrapper)
|
|
2
|
+
|
|
3
|
+
npm one-liner install for [iterate-harness](https://github.com/jingzhao-l/iterate-harness) —
|
|
4
|
+
the dedicated agent harness for the iterate review/fix loop (`ih` CLI, React TUI,
|
|
5
|
+
six `iterate_*` tools, engine-enforced convergence).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g iterate-harness
|
|
9
|
+
ih --version # first run bootstraps the Python side, then prints the version
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What this package is (and is not)
|
|
13
|
+
|
|
14
|
+
The harness itself is a **Python package**. This npm package is a thin
|
|
15
|
+
distribution wrapper: it does not re-implement anything, it just makes `ih`
|
|
16
|
+
installable with npm and keeps it up to date.
|
|
17
|
+
|
|
18
|
+
On first run the wrapper:
|
|
19
|
+
|
|
20
|
+
1. Finds a Python interpreter >= 3.10 (`py -3` / `python` / `python3`,
|
|
21
|
+
overridable via `ITERATE_HARNESS_PYTHON`).
|
|
22
|
+
2. Creates a managed virtualenv at `~/.iterate-harness-npm/venv`
|
|
23
|
+
(overridable via `ITERATE_HARNESS_NPM_HOME`).
|
|
24
|
+
3. `pip install`s the harness **release tarball pinned to this npm package's
|
|
25
|
+
version** — npm `1.6.0` always installs harness `v1.6.0`. Upgrading the npm
|
|
26
|
+
package automatically re-installs the matching harness version on the next
|
|
27
|
+
run.
|
|
28
|
+
4. Delegates to the venv's real `ih` executable with argv, stdio, signals and
|
|
29
|
+
exit codes forwarded.
|
|
30
|
+
|
|
31
|
+
The React TUI's frontend dependencies (`node_modules`) are installed
|
|
32
|
+
automatically by the harness itself on first TUI launch — npm users always
|
|
33
|
+
have Node, so the TUI always works.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Node.js >= 16 (you have it — you installed this via npm)
|
|
38
|
+
- Python >= 3.10 (`ITERATE_HARNESS_PYTHON=/path/to/python3.12` to override)
|
|
39
|
+
- Network access to registry.npmjs.org (done), pypi.org and github.com (for
|
|
40
|
+
the one-time pip install)
|
|
41
|
+
|
|
42
|
+
## Environment variables
|
|
43
|
+
|
|
44
|
+
| Variable | Effect |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `ITERATE_HARNESS_PYTHON` | Use this interpreter instead of auto-detection |
|
|
47
|
+
| `ITERATE_HARNESS_NPM_HOME` | Runtime dir (default `~/.iterate-harness-npm`) |
|
|
48
|
+
| `ITERATE_HARNESS_INSTALL_URL` | Install from this pip URL instead of the pinned release tarball (e.g. a git branch for testing) |
|
|
49
|
+
| `ITERATE_HARNESS_SKIP_INSTALL=1` | Skip bootstrap entirely; just run the already-installed `ih` |
|
|
50
|
+
|
|
51
|
+
## npx (no global install)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx -y iterate-harness iterate review --changed
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Uninstall
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm uninstall -g iterate-harness
|
|
61
|
+
rm -rf ~/.iterate-harness-npm # the managed venv
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Versioning
|
|
65
|
+
|
|
66
|
+
This package's version is kept in lockstep with the harness: wrapper `x.y.z`
|
|
67
|
+
installs harness tag `vx.y.z`. The wrapper is maintained in the
|
|
68
|
+
[iterate-skill monorepo](https://github.com/jingzhao-l/iterate-skill) at
|
|
69
|
+
`npm/iterate-harness/` and published to npm from there.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT — same as the harness. The agent kernel originates from
|
|
74
|
+
[OpenHarness](https://github.com/HKUDS/OpenHarness); the iterate semantic layer
|
|
75
|
+
originates from [iterate-skill](https://github.com/jingzhao-l/iterate-skill).
|
package/bin/ih.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* `ih` — global entry point of the iterate-harness npm wrapper. */
|
|
3
|
+
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const { BootstrapError, runHarness } = require("../lib/bootstrap");
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
runHarness(process.argv.slice(2));
|
|
10
|
+
} catch (error) {
|
|
11
|
+
const message = error instanceof BootstrapError ? error.message : (error && error.stack) || error;
|
|
12
|
+
process.stderr.write(`[iterate-harness] ${message}\n`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* `iterate-harness` — long-form entry point of the npm wrapper (same as `ih`). */
|
|
3
|
+
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const { BootstrapError, runHarness } = require("../lib/bootstrap");
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
runHarness(process.argv.slice(2));
|
|
10
|
+
} catch (error) {
|
|
11
|
+
const message = error instanceof BootstrapError ? error.message : (error && error.stack) || error;
|
|
12
|
+
process.stderr.write(`[iterate-harness] ${message}\n`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
package/lib/bootstrap.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Node-side bootstrap for the iterate-harness npm distribution wrapper.
|
|
3
|
+
*
|
|
4
|
+
* The harness itself is a Python package (the `ih` CLI). This module never
|
|
5
|
+
* re-implements harness logic — it only prepares a managed virtualenv and
|
|
6
|
+
* delegates every invocation to the real `ih` executable inside it:
|
|
7
|
+
*
|
|
8
|
+
* 1. Resolve a Python interpreter >= 3.10 (env override first).
|
|
9
|
+
* 2. Create/reuse the venv at ~/.iterate-harness-npm/venv.
|
|
10
|
+
* 3. pip-install the harness release tarball pinned to THIS npm package's
|
|
11
|
+
* version (lockstep: npm 1.6.0 installs harness v1.6.0).
|
|
12
|
+
* 4. Stamp the installed version; a mismatch (npm upgrade) triggers a
|
|
13
|
+
* re-install on the next run.
|
|
14
|
+
* 5. Spawn the venv's `ih` with argv/stdio/signals forwarded and the exit
|
|
15
|
+
* code passed through.
|
|
16
|
+
*
|
|
17
|
+
* Failures print an actionable message and exit 1 — never a raw stack trace.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
const { spawn, spawnSync } = require("child_process");
|
|
23
|
+
const fs = require("fs");
|
|
24
|
+
const os = require("os");
|
|
25
|
+
const path = require("path");
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Constants (no magic strings below — everything is named here)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
const RUNTIME_DIR_NAME = ".iterate-harness-npm";
|
|
32
|
+
const VENV_DIR_NAME = "venv";
|
|
33
|
+
const STAMP_FILE_NAME = "version.stamp";
|
|
34
|
+
|
|
35
|
+
const PYTHON_ENV_VAR = "ITERATE_HARNESS_PYTHON";
|
|
36
|
+
const HOME_ENV_VAR = "ITERATE_HARNESS_NPM_HOME";
|
|
37
|
+
const INSTALL_URL_ENV_VAR = "ITERATE_HARNESS_INSTALL_URL";
|
|
38
|
+
const SKIP_INSTALL_ENV_VAR = "ITERATE_HARNESS_SKIP_INSTALL";
|
|
39
|
+
|
|
40
|
+
const HARNESS_REPO_ARCHIVE_URL = "https://github.com/jingzhao-l/iterate-harness/archive/refs/tags";
|
|
41
|
+
|
|
42
|
+
const MIN_PYTHON_MAJOR = 3;
|
|
43
|
+
const MIN_PYTHON_MINOR = 10;
|
|
44
|
+
|
|
45
|
+
const PYTHON_VERSION_PATTERN = /Python\s+(\d+)\.(\d+)\.(\d+)/;
|
|
46
|
+
const IS_WINDOWS = process.platform === "win32";
|
|
47
|
+
|
|
48
|
+
class BootstrapError extends Error {}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Pure helpers (unit-tested in test/bootstrap.test.js)
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
function releaseTarballUrl(version) {
|
|
55
|
+
return `${HARNESS_REPO_ARCHIVE_URL}/v${version}.tar.gz`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parsePythonVersion(output) {
|
|
59
|
+
const match = PYTHON_VERSION_PATTERN.exec(String(output));
|
|
60
|
+
if (!match) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
major: Number.parseInt(match[1], 10),
|
|
65
|
+
minor: Number.parseInt(match[2], 10),
|
|
66
|
+
patch: Number.parseInt(match[3], 10),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isSupportedPython(version) {
|
|
71
|
+
if (!version) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return (
|
|
75
|
+
version.major > MIN_PYTHON_MAJOR ||
|
|
76
|
+
(version.major === MIN_PYTHON_MAJOR && version.minor >= MIN_PYTHON_MINOR)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function pythonCandidates(platform, env) {
|
|
81
|
+
const override = env ? env[PYTHON_ENV_VAR] : undefined;
|
|
82
|
+
const candidates = [];
|
|
83
|
+
if (override) {
|
|
84
|
+
candidates.push({ command: override, preArgs: [] });
|
|
85
|
+
}
|
|
86
|
+
if (platform === "win32") {
|
|
87
|
+
candidates.push({ command: "py", preArgs: ["-3"] });
|
|
88
|
+
candidates.push({ command: "python", preArgs: [] });
|
|
89
|
+
} else {
|
|
90
|
+
candidates.push({ command: "python3", preArgs: [] });
|
|
91
|
+
candidates.push({ command: "python", preArgs: [] });
|
|
92
|
+
}
|
|
93
|
+
return candidates;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function venvExecutablePaths(venvDir, platform) {
|
|
97
|
+
if (platform === "win32") {
|
|
98
|
+
return {
|
|
99
|
+
python: path.join(venvDir, "Scripts", "python.exe"),
|
|
100
|
+
ih: path.join(venvDir, "Scripts", "ih.exe"),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
python: path.join(venvDir, "bin", "python"),
|
|
105
|
+
ih: path.join(venvDir, "bin", "ih"),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function needsBootstrap(stampContent, expectedVersion) {
|
|
110
|
+
return String(stampContent || "").trim() !== String(expectedVersion || "").trim();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Runtime resolution
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
function packageVersion() {
|
|
118
|
+
const manifestPath = path.resolve(__dirname, "..", "package.json");
|
|
119
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
120
|
+
return manifest.version;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function runtimeHomeDir(env) {
|
|
124
|
+
const override = env ? env[HOME_ENV_VAR] : undefined;
|
|
125
|
+
if (override) {
|
|
126
|
+
return override;
|
|
127
|
+
}
|
|
128
|
+
return path.join(os.homedir(), RUNTIME_DIR_NAME);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function detectPython(env) {
|
|
132
|
+
const errors = [];
|
|
133
|
+
for (const candidate of pythonCandidates(process.platform, env || process.env)) {
|
|
134
|
+
const probe = spawnSync(candidate.command, [...candidate.preArgs, "--version"], {
|
|
135
|
+
encoding: "utf8",
|
|
136
|
+
windowsHide: true,
|
|
137
|
+
});
|
|
138
|
+
if (probe.error) {
|
|
139
|
+
errors.push(`${candidate.command}: not found`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const stdout = `${probe.stdout || ""}${probe.stderr || ""}`;
|
|
143
|
+
const version = parsePythonVersion(stdout);
|
|
144
|
+
if (!version) {
|
|
145
|
+
errors.push(`${candidate.command}: unreadable version output`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!isSupportedPython(version)) {
|
|
149
|
+
errors.push(
|
|
150
|
+
`${candidate.command}: ${version.major}.${version.minor}.${version.patch} is older than 3.10`
|
|
151
|
+
);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
return { ...candidate, version };
|
|
155
|
+
}
|
|
156
|
+
throw new BootstrapError(
|
|
157
|
+
[
|
|
158
|
+
"Python >= 3.10 not found (tried: " + errors.join("; ") + ").",
|
|
159
|
+
"",
|
|
160
|
+
"Install Python 3.10+ or point the wrapper at an existing interpreter:",
|
|
161
|
+
` ${PYTHON_ENV_VAR}=/path/to/python3.12 npx iterate-harness --version`,
|
|
162
|
+
].join("\n")
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Bootstrap (venv + pip install of the pinned release tarball)
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
const CERT_VERIFY_FAILURE_MARKER = "CERTIFICATE_VERIFY_FAILED";
|
|
171
|
+
|
|
172
|
+
function runStep(command, args, options) {
|
|
173
|
+
const result = spawnSync(command, args, {
|
|
174
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
175
|
+
windowsHide: true,
|
|
176
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
177
|
+
...options,
|
|
178
|
+
});
|
|
179
|
+
const capturedOutput = `${result.stdout || ""}${result.stderr || ""}`;
|
|
180
|
+
if (capturedOutput) {
|
|
181
|
+
process.stderr.write(capturedOutput);
|
|
182
|
+
}
|
|
183
|
+
if (result.error) {
|
|
184
|
+
throw new BootstrapError(`failed to run ${command}: ${result.error.message}`);
|
|
185
|
+
}
|
|
186
|
+
if (result.status !== 0) {
|
|
187
|
+
if (capturedOutput.includes(CERT_VERIFY_FAILURE_MARKER)) {
|
|
188
|
+
throw new BootstrapError(
|
|
189
|
+
[
|
|
190
|
+
`${command} ${args.join(" ")} exited with code ${result.status}`,
|
|
191
|
+
"",
|
|
192
|
+
"The download failed TLS certificate verification. This is a local",
|
|
193
|
+
"Python/pip trust-store issue, not a harness problem. Common fixes:",
|
|
194
|
+
" - macOS python.org installs: run",
|
|
195
|
+
" /Applications/Python 3.1x/Install Certificates.command",
|
|
196
|
+
" - Point pip at a CA bundle: export PIP_CERT=$(python3 -m certifi)",
|
|
197
|
+
` - Or use another interpreter: ${PYTHON_ENV_VAR}=/opt/homebrew/bin/python3.12 npm i -g iterate-harness`,
|
|
198
|
+
].join("\n")
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
throw new BootstrapError(`${command} ${args.join(" ")} exited with code ${result.status}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function installUrl(version, env) {
|
|
206
|
+
const override = env ? env[INSTALL_URL_ENV_VAR] : undefined;
|
|
207
|
+
if (override) {
|
|
208
|
+
return override;
|
|
209
|
+
}
|
|
210
|
+
return releaseTarballUrl(version);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function bootstrap(homeDir, version, env) {
|
|
214
|
+
fs.mkdirSync(homeDir, { recursive: true });
|
|
215
|
+
|
|
216
|
+
const venvDir = path.join(homeDir, VENV_DIR_NAME);
|
|
217
|
+
const executable = venvExecutablePaths(venvDir, process.platform);
|
|
218
|
+
const activateMarker = IS_WINDOWS
|
|
219
|
+
? path.join(venvDir, "Scripts", "activate")
|
|
220
|
+
: path.join(venvDir, "bin", "activate");
|
|
221
|
+
|
|
222
|
+
const interpreter = detectPython(env);
|
|
223
|
+
const recreateVenv = fs.existsSync(venvDir) && !fs.existsSync(activateMarker);
|
|
224
|
+
if (recreateVenv) {
|
|
225
|
+
fs.rmSync(venvDir, { recursive: true, force: true });
|
|
226
|
+
}
|
|
227
|
+
if (!fs.existsSync(activateMarker)) {
|
|
228
|
+
process.stderr.write(`[iterate-harness] creating virtualenv at ${venvDir} ...\n`);
|
|
229
|
+
runStep(interpreter.command, [...interpreter.preArgs, "-m", "venv", venvDir]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const url = installUrl(version, env);
|
|
233
|
+
process.stderr.write(`[iterate-harness] pip installing iterate-harness ${version} ...\n`);
|
|
234
|
+
runStep(executable.python, [
|
|
235
|
+
"-m",
|
|
236
|
+
"pip",
|
|
237
|
+
"install",
|
|
238
|
+
"--upgrade",
|
|
239
|
+
"--force-reinstall",
|
|
240
|
+
url,
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
const stampPath = path.join(homeDir, STAMP_FILE_NAME);
|
|
244
|
+
fs.writeFileSync(stampPath, `${version}\n`, "utf8");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function ensureRuntime(env) {
|
|
248
|
+
const environment = env || process.env;
|
|
249
|
+
const version = packageVersion();
|
|
250
|
+
const homeDir = runtimeHomeDir(environment);
|
|
251
|
+
const venvDir = path.join(homeDir, VENV_DIR_NAME);
|
|
252
|
+
const executable = venvExecutablePaths(venvDir, process.platform);
|
|
253
|
+
const stampPath = path.join(homeDir, STAMP_FILE_NAME);
|
|
254
|
+
|
|
255
|
+
const skipInstall = environment[SKIP_INSTALL_ENV_VAR] === "1";
|
|
256
|
+
const stampContent = fs.existsSync(stampPath)
|
|
257
|
+
? fs.readFileSync(stampPath, "utf8")
|
|
258
|
+
: "";
|
|
259
|
+
|
|
260
|
+
if (!skipInstall && needsBootstrap(stampContent, version)) {
|
|
261
|
+
bootstrap(homeDir, version, environment);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!fs.existsSync(executable.ih)) {
|
|
265
|
+
throw new BootstrapError(
|
|
266
|
+
[
|
|
267
|
+
`ih is not installed yet at ${executable.ih}.`,
|
|
268
|
+
skipInstall
|
|
269
|
+
? `Bootstrap was skipped (${SKIP_INSTALL_ENV_VAR}=1) — unset it and retry.`
|
|
270
|
+
: "The installation may have failed; remove the runtime dir and retry:",
|
|
271
|
+
` rm -rf ${homeDir}`,
|
|
272
|
+
].join("\n")
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return executable.ih;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Delegation to the real ih executable
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
function runHarness(args, env) {
|
|
283
|
+
const target = ensureRuntime(env);
|
|
284
|
+
const child = spawn(target, args, {
|
|
285
|
+
stdio: "inherit",
|
|
286
|
+
windowsHide: false,
|
|
287
|
+
env: env || process.env,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const forwardSignal = (signal) => {
|
|
291
|
+
if (!child.killed) {
|
|
292
|
+
child.kill(signal);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
296
|
+
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
297
|
+
|
|
298
|
+
child.on("error", (error) => {
|
|
299
|
+
process.stderr.write(`[iterate-harness] failed to launch ih: ${error.message}\n`);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
});
|
|
302
|
+
child.on("close", (code, signal) => {
|
|
303
|
+
if (signal) {
|
|
304
|
+
process.kill(process.pid, signal);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
process.exit(code === null ? 1 : code);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
BootstrapError,
|
|
313
|
+
MIN_PYTHON_MAJOR,
|
|
314
|
+
MIN_PYTHON_MINOR,
|
|
315
|
+
PYTHON_ENV_VAR,
|
|
316
|
+
HOME_ENV_VAR,
|
|
317
|
+
INSTALL_URL_ENV_VAR,
|
|
318
|
+
SKIP_INSTALL_ENV_VAR,
|
|
319
|
+
detectPython,
|
|
320
|
+
ensureRuntime,
|
|
321
|
+
installUrl,
|
|
322
|
+
isSupportedPython,
|
|
323
|
+
needsBootstrap,
|
|
324
|
+
packageVersion,
|
|
325
|
+
parsePythonVersion,
|
|
326
|
+
pythonCandidates,
|
|
327
|
+
releaseTarballUrl,
|
|
328
|
+
runHarness,
|
|
329
|
+
venvExecutablePaths,
|
|
330
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "iterate-harness",
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "npm distribution wrapper for iterate-harness — installs the Python harness (ih CLI) into a managed venv on first run and exposes it as a global command",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/jingzhao-l/iterate-harness.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/jingzhao-l/iterate-harness#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jingzhao-l/iterate-harness/issues"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"ih": "bin/ih.js",
|
|
17
|
+
"iterate-harness": "bin/iterate-harness.js"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=16"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin",
|
|
24
|
+
"lib",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"iterate",
|
|
30
|
+
"code-review",
|
|
31
|
+
"agent-harness",
|
|
32
|
+
"cli",
|
|
33
|
+
"installer"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "node --test test/bootstrap.test.js"
|
|
37
|
+
}
|
|
38
|
+
}
|