@osfactory/har 0.61.2 → 0.62.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/dist/index.js +102 -40
- package/dist/templates/har-boilerplate/agent-slot.sh +23 -0
- package/dist/templates/har-boilerplate/provision-toolchain.sh +258 -19
- package/dist/templates/har-boilerplate/verify.sh +6 -19
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +23 -0
- package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +258 -19
- package/dist/templates/har-boilerplate-cli/verify.sh +4 -10
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +23 -0
- package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +258 -19
- package/dist/templates/har-boilerplate-ios/verify.sh +4 -10
- package/dist/templates/runtime-bundles/shared-kernel/provision-toolchain.sh +258 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,31 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
mod
|
|
34
34
|
));
|
|
35
35
|
|
|
36
|
+
// src/core/package-version.ts
|
|
37
|
+
function getHarPackageVersion() {
|
|
38
|
+
if (process.env.HAR_PACKAGE_VERSION) {
|
|
39
|
+
return process.env.HAR_PACKAGE_VERSION;
|
|
40
|
+
}
|
|
41
|
+
const candidates = [
|
|
42
|
+
path.resolve(__dirname, "..", "package.json"),
|
|
43
|
+
path.resolve(__dirname, "..", "..", "package.json")
|
|
44
|
+
];
|
|
45
|
+
for (const packageJsonPath of candidates) {
|
|
46
|
+
if (!fs.existsSync(packageJsonPath)) continue;
|
|
47
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
48
|
+
return pkg.version;
|
|
49
|
+
}
|
|
50
|
+
throw new Error("Could not resolve @osfactory/har package.json for version lookup");
|
|
51
|
+
}
|
|
52
|
+
var fs, path;
|
|
53
|
+
var init_package_version = __esm({
|
|
54
|
+
"src/core/package-version.ts"() {
|
|
55
|
+
"use strict";
|
|
56
|
+
fs = __toESM(require("fs"));
|
|
57
|
+
path = __toESM(require("path"));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
36
61
|
// src/core/telemetry-config.ts
|
|
37
62
|
function getPreferencePath() {
|
|
38
63
|
if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
|
|
@@ -28779,12 +28804,10 @@ function run(command2, options = {}) {
|
|
|
28779
28804
|
};
|
|
28780
28805
|
}
|
|
28781
28806
|
}
|
|
28782
|
-
function runScript(scriptPath, args = [], options = {}) {
|
|
28783
|
-
return runScriptCapture(scriptPath, args, { ...options, stream: true });
|
|
28784
|
-
}
|
|
28785
28807
|
function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
28786
28808
|
const stream = options.stream ?? false;
|
|
28787
|
-
const
|
|
28809
|
+
const echoStdout = stream && (options.streamStdout ?? true);
|
|
28810
|
+
const { stream: _stream, streamStdout: _streamStdout, ...spawnOptions } = options;
|
|
28788
28811
|
return new Promise((resolve51) => {
|
|
28789
28812
|
const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
|
|
28790
28813
|
...spawnOptions,
|
|
@@ -28794,7 +28817,7 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
|
28794
28817
|
let stderr = "";
|
|
28795
28818
|
proc.stdout?.on("data", (d) => {
|
|
28796
28819
|
stdout += d;
|
|
28797
|
-
if (
|
|
28820
|
+
if (echoStdout) process.stdout.write(d);
|
|
28798
28821
|
});
|
|
28799
28822
|
proc.stderr?.on("data", (d) => {
|
|
28800
28823
|
stderr += d;
|
|
@@ -28805,7 +28828,8 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
|
28805
28828
|
}
|
|
28806
28829
|
function runShellCommand(command2, options = {}) {
|
|
28807
28830
|
const stream = options.stream ?? false;
|
|
28808
|
-
const
|
|
28831
|
+
const echoStdout = stream && (options.streamStdout ?? true);
|
|
28832
|
+
const { stream: _stream, streamStdout: _streamStdout, ...spawnOptions } = options;
|
|
28809
28833
|
return new Promise((resolve51) => {
|
|
28810
28834
|
const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
|
|
28811
28835
|
...spawnOptions,
|
|
@@ -28815,7 +28839,7 @@ function runShellCommand(command2, options = {}) {
|
|
|
28815
28839
|
let stderr = "";
|
|
28816
28840
|
proc.stdout?.on("data", (d) => {
|
|
28817
28841
|
stdout += d;
|
|
28818
|
-
if (
|
|
28842
|
+
if (echoStdout) process.stdout.write(d);
|
|
28819
28843
|
});
|
|
28820
28844
|
proc.stderr?.on("data", (d) => {
|
|
28821
28845
|
stderr += d;
|
|
@@ -29090,6 +29114,18 @@ function parseVerificationResult(stdout) {
|
|
|
29090
29114
|
const parsed = VerificationResultSchema.safeParse(json);
|
|
29091
29115
|
return parsed.success ? parsed.data : null;
|
|
29092
29116
|
}
|
|
29117
|
+
function slimVerificationResult(verification) {
|
|
29118
|
+
if (!verification) return null;
|
|
29119
|
+
return {
|
|
29120
|
+
...verification,
|
|
29121
|
+
stages: verification.stages.map((stage) => {
|
|
29122
|
+
if (!stage.pass || stage.output === void 0) return stage;
|
|
29123
|
+
const rest = { ...stage };
|
|
29124
|
+
delete rest.output;
|
|
29125
|
+
return rest;
|
|
29126
|
+
})
|
|
29127
|
+
};
|
|
29128
|
+
}
|
|
29093
29129
|
function buildStageResult(input) {
|
|
29094
29130
|
const verification = input.verification ?? parseVerificationResult(input.stdout) ?? void 0;
|
|
29095
29131
|
const parsedStageResult = StageResultSchema.safeParse(extractJsonFromOutput(input.stdout));
|
|
@@ -29287,16 +29323,18 @@ function buildExecutionPlan(repoPath, stage, options) {
|
|
|
29287
29323
|
env: mergedEnv
|
|
29288
29324
|
};
|
|
29289
29325
|
}
|
|
29290
|
-
async function executePlan(plan,
|
|
29291
|
-
const spawnOptions = {
|
|
29326
|
+
async function executePlan(plan, options) {
|
|
29327
|
+
const spawnOptions = {
|
|
29328
|
+
cwd: plan.cwd,
|
|
29329
|
+
env: plan.env,
|
|
29330
|
+
stream: !options.capture,
|
|
29331
|
+
streamStdout: options.streamStdout
|
|
29332
|
+
};
|
|
29292
29333
|
if (plan.mode === "shell" && plan.shellCommand) {
|
|
29293
|
-
return runShellCommand(plan.shellCommand,
|
|
29334
|
+
return runShellCommand(plan.shellCommand, spawnOptions);
|
|
29294
29335
|
}
|
|
29295
29336
|
if (plan.scriptPath) {
|
|
29296
|
-
|
|
29297
|
-
return runScriptCapture(plan.scriptPath, plan.args, spawnOptions);
|
|
29298
|
-
}
|
|
29299
|
-
return runScript(plan.scriptPath, plan.args, spawnOptions);
|
|
29337
|
+
return runScriptCapture(plan.scriptPath, plan.args, spawnOptions);
|
|
29300
29338
|
}
|
|
29301
29339
|
throw new Error("Invalid stage execution plan");
|
|
29302
29340
|
}
|
|
@@ -29328,7 +29366,10 @@ var init_local_executor = __esm({
|
|
|
29328
29366
|
const plan = buildExecutionPlan(repoPath, stage, options);
|
|
29329
29367
|
const capture = options.capture ?? ctx.capture ?? true;
|
|
29330
29368
|
const started = Date.now();
|
|
29331
|
-
const result = await executePlan(plan,
|
|
29369
|
+
const result = await executePlan(plan, {
|
|
29370
|
+
capture,
|
|
29371
|
+
streamStdout: stage.kind !== "verify"
|
|
29372
|
+
});
|
|
29332
29373
|
const durationMs = Date.now() - started;
|
|
29333
29374
|
const verification = stage.kind === "verify" ? parseVerificationResult(result.stdout) : void 0;
|
|
29334
29375
|
const previewUrls = stage.kind === "launch" && result.code === 0 && options.agentId !== void 0 ? computePreviewUrls(repoPath, options.agentId) : void 0;
|
|
@@ -32865,6 +32906,7 @@ async function buildPortalPayload(repoPath, controlApiUrl, since, full = false)
|
|
|
32865
32906
|
}
|
|
32866
32907
|
const syncBody = {
|
|
32867
32908
|
path: repoPath,
|
|
32909
|
+
cliVersion: getHarPackageVersion(),
|
|
32868
32910
|
...status.gitRemote ? { gitRemote: status.gitRemote } : {},
|
|
32869
32911
|
...manifest ? { manifest } : {},
|
|
32870
32912
|
...stagesRegistry ? { stagesRegistry } : {},
|
|
@@ -33287,6 +33329,7 @@ var init_control_sync = __esm({
|
|
|
33287
33329
|
init_work_units();
|
|
33288
33330
|
init_cloud_executor();
|
|
33289
33331
|
init_telemetry_config();
|
|
33332
|
+
init_package_version();
|
|
33290
33333
|
init_logging();
|
|
33291
33334
|
init_usage_harvest();
|
|
33292
33335
|
init_telemetry_env();
|
|
@@ -66476,26 +66519,8 @@ function isYargsInstance(y) {
|
|
|
66476
66519
|
var Yargs = YargsFactory(esm_default);
|
|
66477
66520
|
var yargs_default = Yargs;
|
|
66478
66521
|
|
|
66479
|
-
// src/core/package-version.ts
|
|
66480
|
-
var fs = __toESM(require("fs"));
|
|
66481
|
-
var path = __toESM(require("path"));
|
|
66482
|
-
function getHarPackageVersion() {
|
|
66483
|
-
if (process.env.HAR_PACKAGE_VERSION) {
|
|
66484
|
-
return process.env.HAR_PACKAGE_VERSION;
|
|
66485
|
-
}
|
|
66486
|
-
const candidates = [
|
|
66487
|
-
path.resolve(__dirname, "..", "package.json"),
|
|
66488
|
-
path.resolve(__dirname, "..", "..", "package.json")
|
|
66489
|
-
];
|
|
66490
|
-
for (const packageJsonPath of candidates) {
|
|
66491
|
-
if (!fs.existsSync(packageJsonPath)) continue;
|
|
66492
|
-
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
66493
|
-
return pkg.version;
|
|
66494
|
-
}
|
|
66495
|
-
throw new Error("Could not resolve @osfactory/har package.json for version lookup");
|
|
66496
|
-
}
|
|
66497
|
-
|
|
66498
66522
|
// src/cli/index.ts
|
|
66523
|
+
init_package_version();
|
|
66499
66524
|
init_telemetry_config();
|
|
66500
66525
|
init_sync_context();
|
|
66501
66526
|
|
|
@@ -72190,6 +72215,7 @@ var import_child_process13 = require("child_process");
|
|
|
72190
72215
|
var path52 = __toESM(require("path"));
|
|
72191
72216
|
|
|
72192
72217
|
// src/core/control-image.ts
|
|
72218
|
+
init_package_version();
|
|
72193
72219
|
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
72194
72220
|
function getControlImageName() {
|
|
72195
72221
|
return process.env.HAR_CONTROL_IMAGE ?? DEFAULT_CONTROL_IMAGE;
|
|
@@ -73264,6 +73290,7 @@ function writeOnboardingPreferences(overrides) {
|
|
|
73264
73290
|
// src/cli/commands/env.ts
|
|
73265
73291
|
init_schema2();
|
|
73266
73292
|
init_validation();
|
|
73293
|
+
init_results();
|
|
73267
73294
|
init_logging();
|
|
73268
73295
|
|
|
73269
73296
|
// src/cli/help-text.ts
|
|
@@ -73511,7 +73538,11 @@ var envCommand = {
|
|
|
73511
73538
|
).command(
|
|
73512
73539
|
"verify <id>",
|
|
73513
73540
|
"Run verification suite for an agent",
|
|
73514
|
-
(y) => y.positional("id", { type: "number", describe: "Agent slot id (see .har/stages.json agentSlots)" }).option("repo", { type: "string", default: "." }).option("full", { type: "boolean", default: false }),
|
|
73541
|
+
(y) => y.positional("id", { type: "number", describe: "Agent slot id (see .har/stages.json agentSlots)" }).option("repo", { type: "string", default: "." }).option("full", { type: "boolean", default: false }).option("json", {
|
|
73542
|
+
type: "boolean",
|
|
73543
|
+
default: false,
|
|
73544
|
+
describe: "Structured JSON (passing steps omit output; default is progress only)"
|
|
73545
|
+
}),
|
|
73515
73546
|
handleVerify
|
|
73516
73547
|
).command(
|
|
73517
73548
|
"teardown <id>",
|
|
@@ -73976,9 +74007,26 @@ async function handleVerify(argv) {
|
|
|
73976
74007
|
repoPath: repo,
|
|
73977
74008
|
agentId,
|
|
73978
74009
|
full: argv.full,
|
|
73979
|
-
capture:
|
|
74010
|
+
capture: Boolean(argv.json)
|
|
73980
74011
|
});
|
|
73981
|
-
if (
|
|
74012
|
+
if (argv.json) {
|
|
74013
|
+
const payload = slimVerificationResult(result.verification) ?? {
|
|
74014
|
+
status: result.code === 0 ? "pass" : "fail",
|
|
74015
|
+
agent_id: agentId,
|
|
74016
|
+
stages: []
|
|
74017
|
+
};
|
|
74018
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
74019
|
+
return finishCommand(result.code);
|
|
74020
|
+
}
|
|
74021
|
+
if (result.verification) {
|
|
74022
|
+
const failed = result.verification.stages.filter((stage) => !stage.pass).map((stage) => stage.name);
|
|
74023
|
+
const elapsed = result.verification.total_ms != null ? ` (${result.verification.total_ms}ms)` : "";
|
|
74024
|
+
if (failed.length > 0) {
|
|
74025
|
+
error(`Verification failed: ${failed.join(", ")}${elapsed}`);
|
|
74026
|
+
} else {
|
|
74027
|
+
success(`Verification ${result.verification.status}${elapsed}`);
|
|
74028
|
+
}
|
|
74029
|
+
}
|
|
73982
74030
|
return finishCommand(result.code);
|
|
73983
74031
|
}
|
|
73984
74032
|
async function handleTeardown(argv) {
|
|
@@ -82466,6 +82514,7 @@ var StdioServerTransport = class {
|
|
|
82466
82514
|
};
|
|
82467
82515
|
|
|
82468
82516
|
// src/mcp/server.ts
|
|
82517
|
+
init_package_version();
|
|
82469
82518
|
init_telemetry_config();
|
|
82470
82519
|
init_runs();
|
|
82471
82520
|
init_work_units();
|
|
@@ -82495,6 +82544,7 @@ function objectJsonSchema(properties, required2) {
|
|
|
82495
82544
|
}
|
|
82496
82545
|
|
|
82497
82546
|
// src/mcp/server.ts
|
|
82547
|
+
init_results();
|
|
82498
82548
|
init_validation();
|
|
82499
82549
|
|
|
82500
82550
|
// src/mcp/schemas.ts
|
|
@@ -82780,7 +82830,7 @@ var HAR_MCP_TOOLS = [
|
|
|
82780
82830
|
},
|
|
82781
82831
|
{
|
|
82782
82832
|
name: "har_run_verification",
|
|
82783
|
-
description: "Run the project verification pipeline for an agent slot.",
|
|
82833
|
+
description: "Run the project verification pipeline for an agent slot. Returns status, timing, and failed-step output (passing steps omit logs; stdout is not the raw JSON dump).",
|
|
82784
82834
|
inputSchema: objectJsonSchema(
|
|
82785
82835
|
{
|
|
82786
82836
|
repo: repoJsonProperty,
|
|
@@ -83006,7 +83056,14 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
83006
83056
|
capture: true,
|
|
83007
83057
|
trigger: "mcp"
|
|
83008
83058
|
});
|
|
83009
|
-
return jsonContent(
|
|
83059
|
+
return jsonContent(
|
|
83060
|
+
RunVerificationOutputSchema.parse({
|
|
83061
|
+
code: result.code,
|
|
83062
|
+
stdout: "",
|
|
83063
|
+
stderr: result.stderr,
|
|
83064
|
+
verification: slimVerificationResult(result.verification)
|
|
83065
|
+
})
|
|
83066
|
+
);
|
|
83010
83067
|
}
|
|
83011
83068
|
case "har_get_status": {
|
|
83012
83069
|
const agentIdRaw = args.agentId;
|
|
@@ -83052,7 +83109,12 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
83052
83109
|
capture: true,
|
|
83053
83110
|
trigger: "mcp"
|
|
83054
83111
|
});
|
|
83055
|
-
return jsonContent(
|
|
83112
|
+
return jsonContent(
|
|
83113
|
+
CompleteEnvironmentOutputSchema.parse({
|
|
83114
|
+
...result,
|
|
83115
|
+
verification: slimVerificationResult(result.verification)
|
|
83116
|
+
})
|
|
83117
|
+
);
|
|
83056
83118
|
}
|
|
83057
83119
|
case "har_list_artifacts": {
|
|
83058
83120
|
const input = ListArtifactsInputSchema.parse({ ...args, repo });
|
|
@@ -776,6 +776,29 @@ escape_step_output() {
|
|
|
776
776
|
printf '%s' "$1" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const s=d.trim().split('\n').slice(0,50).join('\n');process.stdout.write(JSON.stringify(s))})" 2>/dev/null || echo '""'
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
+
# Append one verify.sh step to RESULTS_JSON (caller global).
|
|
780
|
+
# Passing steps omit output — it already appeared as ✓ on stderr.
|
|
781
|
+
record_step_result() {
|
|
782
|
+
local name="$1"
|
|
783
|
+
local pass_bool="$2"
|
|
784
|
+
local elapsed="$3"
|
|
785
|
+
local output="${4:-}"
|
|
786
|
+
local step_json
|
|
787
|
+
if [ "$pass_bool" = "true" ]; then
|
|
788
|
+
step_json=$(NAME="$name" MS="$elapsed" node -e 'process.stdout.write(JSON.stringify({name:process.env.NAME,pass:true,ms:Number(process.env.MS)}))' 2>/dev/null) || return 0
|
|
789
|
+
else
|
|
790
|
+
local escaped
|
|
791
|
+
escaped=$(escape_step_output "$output")
|
|
792
|
+
step_json=$(NAME="$name" MS="$elapsed" node -e "process.stdout.write(JSON.stringify({name:process.env.NAME,pass:false,ms:Number(process.env.MS),output:$escaped}))" 2>/dev/null) || return 0
|
|
793
|
+
fi
|
|
794
|
+
RESULTS_JSON=$(echo "$RESULTS_JSON" | node -e "
|
|
795
|
+
const fs = require('fs');
|
|
796
|
+
const arr = JSON.parse(fs.readFileSync('/dev/stdin','utf8'));
|
|
797
|
+
arr.push($step_json);
|
|
798
|
+
process.stdout.write(JSON.stringify(arr));
|
|
799
|
+
" 2>/dev/null || echo "$RESULTS_JSON")
|
|
800
|
+
}
|
|
801
|
+
|
|
779
802
|
# Emit "<id>\t<command>" for each registered stage listed in stages.json
|
|
780
803
|
# verificationStages (used by verify --full). Ids without a matching registered
|
|
781
804
|
# stage are inline verify.sh steps and skipped here; lifecycle stages
|
|
@@ -228,52 +228,291 @@ provision_node() {
|
|
|
228
228
|
append_path_prefix "$dir/node_modules/.bin"
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
# ── Python interpreter helpers ────────────────────────────────────────────────
|
|
232
|
+
# Resolve the project Python (requires-python, .python-version, uv) instead of
|
|
233
|
+
# blindly using PATH python3.
|
|
234
|
+
|
|
235
|
+
har_python_version_from_bin() {
|
|
236
|
+
local bin="$1"
|
|
237
|
+
"$bin" -c 'import sys; print(".".join(map(str, sys.version_info[:3])))' 2>/dev/null \
|
|
238
|
+
| tr -d '[:space:]'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
har_python_version_to_sortable() {
|
|
242
|
+
local v="${1#python}"
|
|
243
|
+
v="${v#v}"
|
|
244
|
+
local major=0 minor=0 patch=0
|
|
245
|
+
IFS='.' read -r major minor patch _ <<< "$v"
|
|
246
|
+
major=${major:-0}
|
|
247
|
+
minor=${minor:-0}
|
|
248
|
+
patch=${patch:-0}
|
|
249
|
+
printf '%03d%03d%03d' "$major" "$minor" "$patch"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
har_python_version_ge() {
|
|
253
|
+
local a b
|
|
254
|
+
a="$(har_python_version_to_sortable "$1")"
|
|
255
|
+
b="$(har_python_version_to_sortable "$2")"
|
|
256
|
+
[ "$a" -ge "$b" ]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
har_python_requires_minimum() {
|
|
260
|
+
local spec="$1"
|
|
261
|
+
local ver=""
|
|
262
|
+
|
|
263
|
+
[ -n "$spec" ] || return 1
|
|
264
|
+
|
|
265
|
+
ver="$(printf '%s' "$spec" | sed -n \
|
|
266
|
+
-e 's/.*>=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
267
|
+
-e 's/.*~=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
268
|
+
-e 's/.*==\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
269
|
+
| head -1)"
|
|
270
|
+
|
|
271
|
+
if [ -z "$ver" ]; then
|
|
272
|
+
ver="$(printf '%s' "$spec" | sed -n 's/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p')"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
[ -n "$ver" ] && echo "$ver"
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
har_python_read_dot_version() {
|
|
279
|
+
local dir="$1"
|
|
280
|
+
[ -f "$dir/.python-version" ] || return 1
|
|
281
|
+
head -1 "$dir/.python-version" | tr -d '[:space:]'
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
har_python_read_requires_spec() {
|
|
285
|
+
local dir="$1"
|
|
286
|
+
local spec=""
|
|
287
|
+
|
|
288
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
289
|
+
spec="$(sed -n 's/^[[:space:]]*requires-python[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
290
|
+
"$dir/pyproject.toml" | head -1)"
|
|
291
|
+
fi
|
|
292
|
+
if [ -z "$spec" ] && [ -f "$dir/setup.cfg" ]; then
|
|
293
|
+
spec="$(sed -n '/^\[options\]/,/^\[/ s/^python_requires[[:space:]]*=[[:space:]]*\(.*\)/\1/p' \
|
|
294
|
+
"$dir/setup.cfg" | head -1 | tr -d ' "')"
|
|
295
|
+
fi
|
|
296
|
+
[ -n "$spec" ] && echo "$spec"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
har_python_project_minimum() {
|
|
300
|
+
local dir="$1"
|
|
301
|
+
local req_min="" dot_ver="" higher=""
|
|
302
|
+
|
|
303
|
+
req_min="$(har_python_requires_minimum "$(har_python_read_requires_spec "$dir" || true)" || true)"
|
|
304
|
+
dot_ver="$(har_python_read_dot_version "$dir" || true)"
|
|
305
|
+
if [ -n "$dot_ver" ]; then
|
|
306
|
+
dot_ver="$(printf '%s' "$dot_ver" | sed 's/^v//; s/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/')"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
if [ -n "$req_min" ] && [ -n "$dot_ver" ]; then
|
|
310
|
+
if har_python_version_ge "$dot_ver" "$req_min"; then
|
|
311
|
+
higher="$dot_ver"
|
|
312
|
+
else
|
|
313
|
+
higher="$req_min"
|
|
314
|
+
fi
|
|
315
|
+
elif [ -n "$req_min" ]; then
|
|
316
|
+
higher="$req_min"
|
|
317
|
+
elif [ -n "$dot_ver" ]; then
|
|
318
|
+
higher="$dot_ver"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
[ -n "$higher" ] && echo "$higher"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
har_python_is_uv_project() {
|
|
325
|
+
local dir="$1"
|
|
326
|
+
[ -f "$dir/uv.lock" ] && return 0
|
|
327
|
+
[ -f "$dir/pyproject.toml" ] && grep -q '^\[tool\.uv\]' "$dir/pyproject.toml" 2>/dev/null
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
har_python_uv_find() {
|
|
331
|
+
local dir="$1"
|
|
332
|
+
local request="${2:-}"
|
|
333
|
+
|
|
334
|
+
command -v uv >/dev/null 2>&1 || return 1
|
|
335
|
+
if [ -n "$request" ]; then
|
|
336
|
+
(cd "$dir" && uv python find "$request" 2>/dev/null)
|
|
337
|
+
else
|
|
338
|
+
(cd "$dir" && uv python find 2>/dev/null)
|
|
339
|
+
fi
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
har_python_resolve_interpreter() {
|
|
343
|
+
local dir="$1"
|
|
344
|
+
local minimum="${2:-}"
|
|
345
|
+
local found=""
|
|
346
|
+
|
|
347
|
+
if har_python_is_uv_project "$dir"; then
|
|
348
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
349
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
350
|
+
echo "$found"
|
|
351
|
+
return 0
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
if [ -f "$dir/.python-version" ]; then
|
|
356
|
+
found="$(har_python_uv_find "$dir" "$(har_python_read_dot_version "$dir")" || true)"
|
|
357
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
358
|
+
echo "$found"
|
|
359
|
+
return 0
|
|
360
|
+
fi
|
|
361
|
+
if command -v pyenv >/dev/null 2>&1; then
|
|
362
|
+
found="$(cd "$dir" && pyenv which python 2>/dev/null || true)"
|
|
363
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
364
|
+
echo "$found"
|
|
365
|
+
return 0
|
|
366
|
+
fi
|
|
367
|
+
fi
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if [ -n "$minimum" ]; then
|
|
371
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
372
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
373
|
+
echo "$found"
|
|
374
|
+
return 0
|
|
375
|
+
fi
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
379
|
+
local sys_py sys_ver=""
|
|
380
|
+
sys_py="$(command -v python3)"
|
|
381
|
+
if [ -z "$minimum" ]; then
|
|
382
|
+
echo "$sys_py"
|
|
383
|
+
return 0
|
|
384
|
+
fi
|
|
385
|
+
sys_ver="$(har_python_version_from_bin "$sys_py")"
|
|
386
|
+
if [ -n "$sys_ver" ] && har_python_version_ge "$sys_ver" "$minimum"; then
|
|
387
|
+
echo "$sys_py"
|
|
388
|
+
return 0
|
|
389
|
+
fi
|
|
390
|
+
echo "$sys_py"
|
|
391
|
+
return 0
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
return 1
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
har_python_warn_if_below_minimum() {
|
|
398
|
+
local bin="$1"
|
|
399
|
+
local minimum="$2"
|
|
400
|
+
local actual=""
|
|
401
|
+
|
|
402
|
+
[ -n "$minimum" ] || return 0
|
|
403
|
+
actual="$(har_python_version_from_bin "$bin")"
|
|
404
|
+
[ -n "$actual" ] || return 0
|
|
405
|
+
if ! har_python_version_ge "$actual" "$minimum"; then
|
|
406
|
+
pt_log "WARNING: resolved Python ${actual} is older than required ${minimum} (requires-python / .python-version)"
|
|
407
|
+
fi
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
har_python_create_venv() {
|
|
411
|
+
local dir="$1"
|
|
412
|
+
local venv_dir="$2"
|
|
413
|
+
local interpreter="$3"
|
|
414
|
+
local venv_rel="${venv_dir#"$dir"/}"
|
|
415
|
+
|
|
416
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
417
|
+
pt_log "Creating Python venv with uv at ${venv_rel}..."
|
|
418
|
+
if (cd "$dir" && uv venv --python "$interpreter" "$venv_dir" 2>/dev/null) \
|
|
419
|
+
|| (cd "$dir" && uv venv "$venv_dir" 2>/dev/null); then
|
|
420
|
+
return 0
|
|
421
|
+
fi
|
|
422
|
+
pt_log "uv venv failed — falling back to ${interpreter} -m venv"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
pt_log "Creating Python venv at ${venv_rel}..."
|
|
426
|
+
"$interpreter" -m venv "$venv_dir"
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
har_python_install_deps() {
|
|
430
|
+
local dir="$1"
|
|
431
|
+
local venv_dir="$2"
|
|
432
|
+
|
|
433
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
434
|
+
pt_log "Syncing Python dependencies with uv..."
|
|
435
|
+
if (cd "$dir" && UV_PROJECT_ENVIRONMENT="$venv_dir" uv sync --quiet 2>/dev/null); then
|
|
436
|
+
return 0
|
|
437
|
+
fi
|
|
438
|
+
if (cd "$dir" && uv sync --quiet 2>/dev/null); then
|
|
439
|
+
return 0
|
|
440
|
+
fi
|
|
441
|
+
pt_log "uv sync failed — falling back to pip"
|
|
442
|
+
return 1
|
|
443
|
+
fi
|
|
444
|
+
return 1
|
|
445
|
+
}
|
|
446
|
+
|
|
231
447
|
provision_python() {
|
|
232
448
|
local dir="$1"
|
|
233
449
|
local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
|
|
234
450
|
local venv_dir="$dir/$venv_rel"
|
|
235
|
-
local python_bin="
|
|
451
|
+
local python_bin=""
|
|
452
|
+
local interpreter=""
|
|
453
|
+
local project_min=""
|
|
454
|
+
|
|
455
|
+
project_min="$(har_python_project_minimum "$dir" || true)"
|
|
456
|
+
interpreter="$(har_python_resolve_interpreter "$dir" "$project_min" || true)"
|
|
236
457
|
|
|
237
|
-
if ! command -v
|
|
238
|
-
pt_log "
|
|
458
|
+
if [ -z "$interpreter" ] || ! command -v "$interpreter" >/dev/null 2>&1; then
|
|
459
|
+
pt_log "No suitable Python interpreter found — skipping venv provisioning"
|
|
239
460
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
240
461
|
append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
|
|
241
462
|
return
|
|
242
463
|
fi
|
|
243
464
|
|
|
465
|
+
if [ -n "$project_min" ]; then
|
|
466
|
+
har_python_warn_if_below_minimum "$interpreter" "$project_min"
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
if [ -d "$venv_dir" ] && [ -x "$venv_dir/bin/python" ] && [ -n "$project_min" ]; then
|
|
470
|
+
local venv_ver=""
|
|
471
|
+
venv_ver="$(har_python_version_from_bin "$venv_dir/bin/python")"
|
|
472
|
+
if [ -n "$venv_ver" ] && ! har_python_version_ge "$venv_ver" "$project_min"; then
|
|
473
|
+
pt_log "Existing venv Python ${venv_ver} is older than required ${project_min} — recreating..."
|
|
474
|
+
rm -rf "$venv_dir"
|
|
475
|
+
fi
|
|
476
|
+
fi
|
|
477
|
+
|
|
244
478
|
if [ ! -d "$venv_dir" ]; then
|
|
245
|
-
|
|
246
|
-
if ! python3 -m venv "$venv_dir"; then
|
|
479
|
+
if ! har_python_create_venv "$dir" "$venv_dir" "$interpreter"; then
|
|
247
480
|
rm -rf "$venv_dir"
|
|
248
|
-
pt_log "
|
|
481
|
+
pt_log "venv creation failed (on Debian/Ubuntu install python3-venv) — using resolved interpreter"
|
|
249
482
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
250
|
-
append_env "PYTHON_BIN" "$
|
|
483
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
251
484
|
return
|
|
252
485
|
fi
|
|
253
486
|
fi
|
|
254
487
|
|
|
255
488
|
if [ ! -x "$venv_dir/bin/python" ]; then
|
|
256
|
-
pt_log "Python venv at $venv_rel is missing or broken — using
|
|
489
|
+
pt_log "Python venv at $venv_rel is missing or broken — using resolved interpreter"
|
|
257
490
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
258
|
-
append_env "PYTHON_BIN" "$
|
|
491
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
259
492
|
return
|
|
260
493
|
fi
|
|
261
494
|
|
|
262
495
|
python_bin="$venv_dir/bin/python"
|
|
496
|
+
if [ -n "$project_min" ]; then
|
|
497
|
+
har_python_warn_if_below_minimum "$python_bin" "$project_min"
|
|
498
|
+
fi
|
|
499
|
+
|
|
263
500
|
# shellcheck disable=SC1091
|
|
264
501
|
source "$venv_dir/bin/activate"
|
|
265
502
|
|
|
266
503
|
if ! run_install_cmd "$dir"; then
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
504
|
+
if ! har_python_install_deps "$dir" "$venv_dir"; then
|
|
505
|
+
pt_log "Installing Python dependencies..."
|
|
506
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
507
|
+
(cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
|
|
508
|
+
elif [ -f "$dir/requirements.txt" ]; then
|
|
509
|
+
(cd "$dir" && pip install -q -r requirements.txt)
|
|
510
|
+
elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
|
|
511
|
+
(cd "$dir" && pip install -q -e .)
|
|
512
|
+
elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
|
|
513
|
+
(cd "$dir" && pipenv install --dev)
|
|
514
|
+
python_bin="$(cd "$dir" && pipenv --py)"
|
|
515
|
+
fi
|
|
277
516
|
fi
|
|
278
517
|
fi
|
|
279
518
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Progressive verification pipeline for an agent environment.
|
|
3
|
-
# Outputs JSON to stdout, human-readable progress to stderr.
|
|
3
|
+
# Outputs JSON to stdout (machine contract), human-readable progress to stderr.
|
|
4
|
+
# Passing steps omit `output`. `har env verify` streams progress only.
|
|
4
5
|
#
|
|
5
6
|
# Usage: ./.har/verify.sh <agent-id> [--full]
|
|
6
7
|
#
|
|
@@ -73,7 +74,7 @@ run_step() {
|
|
|
73
74
|
end=$(now_ms)
|
|
74
75
|
elapsed=$(( end - start ))
|
|
75
76
|
|
|
76
|
-
local pass_bool
|
|
77
|
+
local pass_bool
|
|
77
78
|
if [ "$exit_code" = "0" ]; then
|
|
78
79
|
echo "✓ (${elapsed}ms)" >&2
|
|
79
80
|
pass_bool="true"
|
|
@@ -84,14 +85,7 @@ run_step() {
|
|
|
84
85
|
OVERALL_PASS=false
|
|
85
86
|
fi
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
RESULTS_JSON=$(echo "$RESULTS_JSON" | node -e "
|
|
90
|
-
const fs = require('fs');
|
|
91
|
-
let arr = JSON.parse(fs.readFileSync('/dev/stdin','utf8'));
|
|
92
|
-
arr.push({name:'$name',pass:$pass_bool,ms:$elapsed,output:$step_output_escaped});
|
|
93
|
-
process.stdout.write(JSON.stringify(arr));
|
|
94
|
-
" 2>/dev/null || echo "$RESULTS_JSON")
|
|
88
|
+
record_step_result "$name" "$pass_bool" "$elapsed" "$output"
|
|
95
89
|
|
|
96
90
|
if [ "$pass_bool" = "false" ] && [ -z "$FULL" ]; then
|
|
97
91
|
return 1
|
|
@@ -114,7 +108,7 @@ run_http_step() {
|
|
|
114
108
|
end=$(now_ms)
|
|
115
109
|
elapsed=$(( end - start ))
|
|
116
110
|
|
|
117
|
-
local pass_bool
|
|
111
|
+
local pass_bool
|
|
118
112
|
if [ "$exit_code" = "0" ]; then
|
|
119
113
|
echo "✓ (${elapsed}ms)" >&2
|
|
120
114
|
pass_bool="true"
|
|
@@ -124,14 +118,7 @@ run_http_step() {
|
|
|
124
118
|
OVERALL_PASS=false
|
|
125
119
|
fi
|
|
126
120
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
RESULTS_JSON=$(echo "$RESULTS_JSON" | node -e "
|
|
130
|
-
const fs = require('fs');
|
|
131
|
-
let arr = JSON.parse(fs.readFileSync('/dev/stdin','utf8'));
|
|
132
|
-
arr.push({name:'$name',pass:$pass_bool,ms:$elapsed,output:$step_output_escaped});
|
|
133
|
-
process.stdout.write(JSON.stringify(arr));
|
|
134
|
-
" 2>/dev/null || echo "$RESULTS_JSON")
|
|
121
|
+
record_step_result "$name" "$pass_bool" "$elapsed" "$output"
|
|
135
122
|
|
|
136
123
|
if [ "$pass_bool" = "false" ] && [ -z "$FULL" ]; then
|
|
137
124
|
return 1
|