humanish 0.109.1 → 0.110.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/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/cli/commands/comms.js +25 -7
- package/dist/cli/commands/observe.js +4 -2
- package/dist/cli/commands/run-command.js +4 -3
- package/dist/cli/commands/runs.js +11 -5
- package/dist/cli/commands/setup.js +19 -5
- package/dist/cli/commands/study-format.js +29 -2
- package/dist/cli/commands/study.js +4 -3
- package/dist/cli/commands/tui.js +4 -3
- package/dist/cli/commands/watch.js +2 -2
- package/dist/cli/doctor.d.ts +3 -1
- package/dist/cli/doctor.js +5 -4
- package/dist/cli/io.d.ts +17 -3
- package/dist/cli/io.js +20 -3
- package/dist/comms/receiving-runtime.js +1 -1
- package/dist/comms/setup.js +1 -1
- package/dist/evidence/redaction.d.ts +7 -3
- package/dist/evidence/redaction.js +68 -24
- package/dist/feedback/export-bundle.js +6 -1
- package/dist/feedback/export.js +3 -0
- package/dist/keys/key-resolution.d.ts +7 -0
- package/dist/keys/key-resolution.js +10 -8
- package/dist/routes/computer-use/live-phase.d.ts +1 -0
- package/dist/routes/computer-use/preflight.js +1 -1
- package/dist/routes/computer-use/types.d.ts +6 -0
- package/dist/routes/scripted/types.d.ts +1 -0
- package/dist/routes/shared-world/route.js +1 -1
- package/dist/routes/shared-world/types.d.ts +2 -0
- package/dist/routes/terminal/credentials.js +1 -1
- package/dist/routes/terminal/live-finish.js +8 -4
- package/dist/routes/terminal/types.d.ts +3 -0
- package/dist/run/bundle-shape.js +1 -0
- package/dist/run/bundle.d.ts +6 -0
- package/dist/run/reclaim.d.ts +3 -0
- package/dist/run/reclaim.js +10 -3
- package/dist/run/results.d.ts +3 -0
- package/dist/run/run.js +14 -8
- package/dist/run/sandbox-ids.d.ts +53 -0
- package/dist/run/sandbox-ids.js +198 -0
- package/dist/run/sandbox-receipts.d.ts +2 -0
- package/dist/run/sandbox-receipts.js +26 -0
- package/dist/run/status.js +5 -2
- package/dist/run/stored-runs.js +10 -1
- package/dist/run-study.js +6 -1
- package/dist/study/doctor.js +2 -2
- package/dist/study/init-templates.js +2 -2
- package/dist/study/types.d.ts +1 -1
- package/dist/verify/artifacts.d.ts +4 -0
- package/dist/verify/artifacts.js +8 -0
- package/dist/verify/liveness.js +8 -4
- package/dist/verify/verify.d.ts +1 -1
- package/dist/verify/verify.js +21 -1
- package/docs/architecture/restricted-codex-analysis.md +1 -1
- package/docs/contracts/run-bundle.md +18 -4
- package/package.json +3 -2
- package/skills/humanish/SKILL.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,17 @@ The Unreleased section holds the full notes for the next version until it is tag
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.110.0: --dotenv, and sandbox ids only in receipts (2026-10-03)
|
|
12
|
+
|
|
13
|
+
humanish 0.110.0 renames `--env-file` to `--dotenv`, so a missing file reaches humanish's exit 2
|
|
14
|
+
result where Node exited 9 first. `--env-file` stays as a hidden alias until 0.111.0. Raw sandbox
|
|
15
|
+
ids live only in a run's `sandbox-receipts.ndjson`: every other record, output and export copy names
|
|
16
|
+
a sandbox as `[redacted-sandbox-id]` with a digest, and `humanish verify` grades a run that still
|
|
17
|
+
holds a raw id `local_only` with `RAW_SANDBOX_ID`. The release notes list each changed field and
|
|
18
|
+
what to read instead.
|
|
19
|
+
|
|
20
|
+
[Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.110.0)
|
|
21
|
+
|
|
11
22
|
## 0.109.1: Current option names in messages, reclaim --env-file (2026-10-03)
|
|
12
23
|
|
|
13
24
|
humanish 0.109.1 is a patch. Library refusals and warnings name `RunStudyOptions`, which 0.109.0
|
package/README.md
CHANGED
|
@@ -285,7 +285,7 @@ why. A hosted participant on a release humanish has not tested records a warning
|
|
|
285
285
|
the first of these sources that has it. It prints the name and source of a key it
|
|
286
286
|
fills from sources 2 to 4, never the value:
|
|
287
287
|
|
|
288
|
-
1. the process environment, including a file passed with `--
|
|
288
|
+
1. the process environment, including a file passed with `--dotenv`;
|
|
289
289
|
2. `.humanish/local/provider.env`;
|
|
290
290
|
3. the vendor's own store: `~/.e2b/config.json` for `E2B_API_KEY`, and
|
|
291
291
|
`gh auth token` for `GH_TOKEN`;
|
|
@@ -9,7 +9,7 @@ import { resolveReceivingConnection } from "../../comms/receiving-runtime.js";
|
|
|
9
9
|
import { resolveStudyManifest } from "../../study/discover.js";
|
|
10
10
|
import { runCommsCatchHost } from "../../comms/catch-host.js";
|
|
11
11
|
import { DEFAULT_SANDBOX_CATCH_PORT } from "../../comms/sandbox-catch.js";
|
|
12
|
-
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION,
|
|
12
|
+
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, JSON_OPTION_DESCRIPTION, PORT_OPTION_DESCRIPTION, writeResult, messageOutput, } from "../io.js";
|
|
13
13
|
export function registerCommsCommands(parent, io) {
|
|
14
14
|
const comms = parent
|
|
15
15
|
.command("comms")
|
|
@@ -27,7 +27,8 @@ export function registerCommsCommands(parent, io) {
|
|
|
27
27
|
.command("list")
|
|
28
28
|
.description("Show saved connections and local credential status; does not authenticate with a provider.")
|
|
29
29
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
30
|
-
.option("--
|
|
30
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
31
|
+
.addOption(envFileAliasOption())
|
|
31
32
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
32
33
|
.action((options, command) => handleCommsList(io, options, command));
|
|
33
34
|
connections
|
|
@@ -46,7 +47,8 @@ export function registerCommsCommands(parent, io) {
|
|
|
46
47
|
.option("--connection <name>", "Saved connection name.", "agentmail")
|
|
47
48
|
.option("--study <path>", "Check the connection this study selects.")
|
|
48
49
|
.option("--online", "Make a read-only provider authentication request.")
|
|
49
|
-
.option("--
|
|
50
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
51
|
+
.addOption(envFileAliasOption())
|
|
50
52
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
51
53
|
.action((options, command) => handleCommsCheck(io, options, command));
|
|
52
54
|
comms
|
|
@@ -65,7 +67,8 @@ export function registerCommsCommands(parent, io) {
|
|
|
65
67
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
66
68
|
.option("--run <id>", "One run to inspect or recover.")
|
|
67
69
|
.option("--apply", "Recover the selected inactive run and verify mailbox deletion.")
|
|
68
|
-
.option("--
|
|
70
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
71
|
+
.addOption(envFileAliasOption())
|
|
69
72
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
70
73
|
.action((options, command) => handleCommsRecover(io, options, command));
|
|
71
74
|
comms
|
|
@@ -89,7 +92,12 @@ function handleCommsProviders(io, _options, command) {
|
|
|
89
92
|
writeResult(command, io, result, () => COMMS_PROVIDERS.map((provider) => `${provider.label}: ${provider.limitation}\nKey: ${provider.keyEnv}\nSetup: ${provider.setupUrl}\n`).join("\n"));
|
|
90
93
|
}
|
|
91
94
|
async function handleCommsList(io, options, command) {
|
|
92
|
-
if (!(await applyEnvFileOption({
|
|
95
|
+
if (!(await applyEnvFileOption({
|
|
96
|
+
command,
|
|
97
|
+
cwd: options.cwd,
|
|
98
|
+
envFile: dotenvPathOf(options, command, io),
|
|
99
|
+
io,
|
|
100
|
+
})))
|
|
93
101
|
return;
|
|
94
102
|
const result = await readCommsSetup(resolve(options.cwd), process.env);
|
|
95
103
|
writeResult(command, io, result, (value) => `${value.message}\nAgentMail key: ${value.credential.present ? "present" : "missing"}\n${value.connections.map((connection) => `${connection.name}: ${connection.provider} (${connection.apiKeyEnv})\n`).join("")}`);
|
|
@@ -106,7 +114,12 @@ async function handleCommsAdd(io, name, options, command) {
|
|
|
106
114
|
io.setExitCode(result.ok ? 0 : 2);
|
|
107
115
|
}
|
|
108
116
|
async function handleCommsCheck(io, options, command) {
|
|
109
|
-
if (!(await applyEnvFileOption({
|
|
117
|
+
if (!(await applyEnvFileOption({
|
|
118
|
+
command,
|
|
119
|
+
cwd: options.cwd,
|
|
120
|
+
envFile: dotenvPathOf(options, command, io),
|
|
121
|
+
io,
|
|
122
|
+
})))
|
|
110
123
|
return;
|
|
111
124
|
let connection = options.connection;
|
|
112
125
|
const study = options.study;
|
|
@@ -150,7 +163,12 @@ async function handleCommsConfigure(io, options, command) {
|
|
|
150
163
|
io.setExitCode(result.ok ? 0 : 2);
|
|
151
164
|
}
|
|
152
165
|
async function handleCommsRecover(io, options, command) {
|
|
153
|
-
if (!(await applyEnvFileOption({
|
|
166
|
+
if (!(await applyEnvFileOption({
|
|
167
|
+
command,
|
|
168
|
+
cwd: options.cwd,
|
|
169
|
+
envFile: dotenvPathOf(options, command, io),
|
|
170
|
+
io,
|
|
171
|
+
})))
|
|
154
172
|
return;
|
|
155
173
|
const cwd = resolve(options.cwd);
|
|
156
174
|
try {
|
|
@@ -352,8 +352,10 @@ function hiddenRunLines(groups) {
|
|
|
352
352
|
const reasons = group.reasons.length > 0 ? ` (${group.reasons.join(", ")})` : "";
|
|
353
353
|
lines.push(` ${runs(group.runs)} ${group.status}${reasons}`);
|
|
354
354
|
}
|
|
355
|
-
|
|
356
|
-
|
|
355
|
+
// Export clears both: it blurs screenshots and writes no raw sandbox id.
|
|
356
|
+
const exportable = new Set(["RAW_SCREENSHOTS", "RAW_SANDBOX_ID"]);
|
|
357
|
+
if (groups.some((group) => group.reasons.length > 0 && group.reasons.every((reason) => exportable.has(reason))))
|
|
358
|
+
lines.push("share: a run held back only for RAW_SCREENSHOTS or RAW_SANDBOX_ID can be copied with blurred screenshots and no sandbox ids: `humanish export --run <id> --format bundle --redact-screenshots --out <dir>`, then `humanish observe --all --safe --cwd <dir>`");
|
|
357
359
|
lines.push("why: `humanish verify --run <id>` explains each reason");
|
|
358
360
|
return lines;
|
|
359
361
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runDryRun } from "../../run/dry-run.js";
|
|
2
2
|
import { runStudyCommand } from "./study-run.js";
|
|
3
|
-
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION,
|
|
3
|
+
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, formatRunHuman, freePortOption, JSON_OPTION_DESCRIPTION, parsePositiveInteger, writeResult, } from "../io.js";
|
|
4
4
|
/** The flags a run takes, whichever command starts it. */
|
|
5
5
|
export function addRunOptions(command) {
|
|
6
6
|
return command
|
|
@@ -14,7 +14,8 @@ export function addRunOptions(command) {
|
|
|
14
14
|
.option("--participants <ids>", "With --rerun-failed-from: comma-separated ids of the participants to rerun. The study's participants decide who takes part; ids are their id, or lane-01, lane-02, … by position.")
|
|
15
15
|
.option("--scorer <path>", "Score with this .mjs module, a path inside the repo, in place of review.scorer.ref. It runs as code, so review it first. Terminal, computer-use and shared-world studies only.")
|
|
16
16
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
17
|
-
.option("--
|
|
17
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
18
|
+
.addOption(envFileAliasOption())
|
|
18
19
|
.option("--run-id <id>", "Explicit run id for deterministic fixture tests; refused when that run already exists.")
|
|
19
20
|
.option("--json", JSON_OPTION_DESCRIPTION);
|
|
20
21
|
}
|
|
@@ -52,7 +53,7 @@ export async function handleRun(io, named, options, command) {
|
|
|
52
53
|
const loaded = await applyEnvFileOption({
|
|
53
54
|
command,
|
|
54
55
|
cwd: options.cwd,
|
|
55
|
-
envFile: options
|
|
56
|
+
envFile: dotenvPathOf(options, command, io),
|
|
56
57
|
io,
|
|
57
58
|
// runStudyCommand discovers keys for a live study; a preview without a study needs none.
|
|
58
59
|
discoverKeys: false,
|
|
@@ -7,7 +7,7 @@ import { cleanupRun, listRuns, readReview } from "../../run/stored-runs.js";
|
|
|
7
7
|
import { verifyRun } from "../../verify/verify.js";
|
|
8
8
|
import { reclaimPreflightSandboxes, reclaimRunSandboxes, } from "../../run/reclaim.js";
|
|
9
9
|
import { addRunOptions, handleRun } from "./run-command.js";
|
|
10
|
-
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION,
|
|
10
|
+
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, JSON_OPTION_DESCRIPTION, RUN_OPTION_DESCRIPTION, writeResult, humanError, } from "../io.js";
|
|
11
11
|
import { plural } from "../../run/text.js";
|
|
12
12
|
export function registerRunCommand(parent, io) {
|
|
13
13
|
addRunOptions(parent
|
|
@@ -165,16 +165,22 @@ export function registerRuntimeCommands(parent, io) {
|
|
|
165
165
|
export function registerReclaimCommand(parent, io) {
|
|
166
166
|
parent
|
|
167
167
|
.command("reclaim")
|
|
168
|
-
.description("Kill an interrupted run's sandboxes by the exact ids journaled in its sandbox-receipts.ndjson; never enumerates the E2B account. Needs E2B_API_KEY in the environment or in --
|
|
168
|
+
.description("Kill an interrupted run's sandboxes by the exact ids journaled in its sandbox-receipts.ndjson; never enumerates the E2B account. Needs E2B_API_KEY in the environment or in --dotenv.")
|
|
169
169
|
.summary("Stop an interrupted run's leftover sandboxes.")
|
|
170
170
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
171
171
|
.option("--run <id>", RUN_OPTION_DESCRIPTION, "latest")
|
|
172
172
|
.addOption(new Option("--preflight", "Reclaim sandboxes left by interrupted `humanish study check` probes (journaled in .humanish/preflight) instead of a run's.").conflicts("run"))
|
|
173
|
-
.option("--
|
|
173
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
174
|
+
.addOption(envFileAliasOption())
|
|
174
175
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
175
176
|
.action(async (options, command) => {
|
|
176
177
|
// The kill calls read E2B_API_KEY from the environment: the env file, then discovered keys.
|
|
177
|
-
if (!(await applyEnvFileOption({
|
|
178
|
+
if (!(await applyEnvFileOption({
|
|
179
|
+
command,
|
|
180
|
+
cwd: options.cwd,
|
|
181
|
+
envFile: dotenvPathOf(options, command, io),
|
|
182
|
+
io,
|
|
183
|
+
})))
|
|
178
184
|
return;
|
|
179
185
|
const result = options.preflight
|
|
180
186
|
? await reclaimPreflightSandboxes(options.cwd)
|
|
@@ -187,7 +193,7 @@ function formatReclaimHuman(result) {
|
|
|
187
193
|
const lines = [];
|
|
188
194
|
lines.push(`Reclaim ${result.runId}: ${result.ok ? "ok" : "failed"}, ${plural(result.receiptCount, "sandbox receipt")}.`);
|
|
189
195
|
for (const outcome of result.outcomes) {
|
|
190
|
-
lines.push(` ${outcome.
|
|
196
|
+
lines.push(` sandbox ${outcome.sandboxIdDigest} (${outcome.laneId}): ${outcome.state}${outcome.detail ? ` — ${outcome.detail}` : ""}`);
|
|
191
197
|
}
|
|
192
198
|
for (const warning of result.warnings)
|
|
193
199
|
lines.push(` warning: ${warning}`);
|
|
@@ -4,7 +4,7 @@ import { promptSecret } from "../secret-prompt.js";
|
|
|
4
4
|
import { runInit } from "../../study/init.js";
|
|
5
5
|
import { buildPayload, disabledByEnvironment, readTelemetryState, telemetryStatePath, writeTelemetryState, } from "../telemetry.js";
|
|
6
6
|
import { doctor } from "../doctor.js";
|
|
7
|
-
import { applyEnvFileOption, CLI_VERSION, CWD_OPTION_DESCRIPTION,
|
|
7
|
+
import { applyEnvFileOption, CLI_VERSION, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, JSON_OPTION_DESCRIPTION, markInvocationEnvelopeWritten, wantsJson, writeResult, formatCliError, } from "../io.js";
|
|
8
8
|
import { plural } from "../../run/text.js";
|
|
9
9
|
export function registerInitCommand(parent, io) {
|
|
10
10
|
parent
|
|
@@ -51,13 +51,27 @@ export function registerDoctorCommand(parent, io) {
|
|
|
51
51
|
.summary("Check what this project and machine need for a run.")
|
|
52
52
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
53
53
|
.option("--study <study>", "Check the study's desktop, participant authentication and separate analysis requirements; no provider calls.")
|
|
54
|
-
.option("--
|
|
54
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
55
|
+
.addOption(envFileAliasOption())
|
|
55
56
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
56
57
|
.action(async (options, command) => {
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const dotenv = dotenvPathOf(options, command, io);
|
|
59
|
+
let loaded;
|
|
60
|
+
if (dotenv &&
|
|
61
|
+
!(await applyEnvFileOption({
|
|
62
|
+
command,
|
|
63
|
+
cwd: options.cwd,
|
|
64
|
+
envFile: dotenv,
|
|
65
|
+
io,
|
|
66
|
+
onLoaded: (load) => {
|
|
67
|
+
loaded = load;
|
|
68
|
+
},
|
|
69
|
+
})))
|
|
59
70
|
return;
|
|
60
|
-
const result = await doctor(options.cwd,
|
|
71
|
+
const result = await doctor(options.cwd, {
|
|
72
|
+
...(options.study ? { study: options.study } : {}),
|
|
73
|
+
...(loaded === undefined ? {} : { dotenv: loaded }),
|
|
74
|
+
});
|
|
61
75
|
writeResult(command, io, result, formatDoctorHuman);
|
|
62
76
|
// Behavioral change: was exit 1, every other structured command uses 2.
|
|
63
77
|
io.setExitCode(result.ok ? 0 : 2);
|
|
@@ -15,6 +15,21 @@ function withError(error, lines) {
|
|
|
15
15
|
const stdout = `${lines.join("\n")}\n`;
|
|
16
16
|
return error === undefined ? stdout : { stdout, error };
|
|
17
17
|
}
|
|
18
|
+
/** How a summary names a sandbox: by digest. The raw id is only in the run's receipts. */
|
|
19
|
+
function sandboxName(sandbox) {
|
|
20
|
+
return sandbox.sandboxIdDigest !== undefined
|
|
21
|
+
? `[redacted-sandbox-id ${sandbox.sandboxIdDigest}]`
|
|
22
|
+
: sandbox.sandboxId;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One line per run naming the file that holds its raw sandbox ids, for the operator, when the
|
|
26
|
+
* result names a sandbox at all (each one carries a digest).
|
|
27
|
+
*/
|
|
28
|
+
function sandboxIdsLine(result) {
|
|
29
|
+
return result.runId && JSON.stringify(result).includes('"sandboxIdDigest"')
|
|
30
|
+
? [`sandbox ids: .humanish/runs/${result.runId}/sandbox-receipts.ndjson`]
|
|
31
|
+
: [];
|
|
32
|
+
}
|
|
18
33
|
/** A participant's status in words: a dry run's placeholder status says that nothing ran live. */
|
|
19
34
|
function participantStatus(status) {
|
|
20
35
|
return status === "contract_proof_only" ? "dry run, nothing ran live" : status;
|
|
@@ -37,9 +52,10 @@ export function formatConcurrentSharedWorldStudyHuman(result) {
|
|
|
37
52
|
...result.roles.map((participant) => `persona ${participant.id} (${participant.persona}): ${participantStatus(participant.status)}${participant.session ? ` (${participant.session.completionReason})` : ""}${participant.ok ? "" : ", not ok"}`),
|
|
38
53
|
...(result.subjectSandbox
|
|
39
54
|
? [
|
|
40
|
-
`subject sandbox: ${result.subjectSandbox
|
|
55
|
+
`subject sandbox: ${sandboxName(result.subjectSandbox)} killed=${result.subjectSandbox.killed ? "yes" : "no"}`,
|
|
41
56
|
]
|
|
42
57
|
: []),
|
|
58
|
+
...sandboxIdsLine(result),
|
|
43
59
|
...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
|
|
44
60
|
...(result.observer?.opened === undefined
|
|
45
61
|
? []
|
|
@@ -53,6 +69,10 @@ export function formatTerminalStudyHuman(result) {
|
|
|
53
69
|
`run: ${result.runId}`,
|
|
54
70
|
`actor: ${result.actor}`,
|
|
55
71
|
`product: ${result.product}`,
|
|
72
|
+
...(result.sandbox
|
|
73
|
+
? [`sandbox: ${sandboxName(result.sandbox)} killed=${result.sandbox.killed ? "yes" : "no"}`]
|
|
74
|
+
: []),
|
|
75
|
+
...sandboxIdsLine(result),
|
|
56
76
|
...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
|
|
57
77
|
...(result.observer?.opened === undefined
|
|
58
78
|
? []
|
|
@@ -72,6 +92,12 @@ export function formatScriptedStudyHuman(result) {
|
|
|
72
92
|
]
|
|
73
93
|
: []),
|
|
74
94
|
...result.sessions.map((session) => `session ${session.surface}: ${session.status} (${session.completionReason}) · ${session.reason} [${session.screenshots} screenshot${session.screenshots === 1 ? "" : "s"}]`),
|
|
95
|
+
...(result.subjectSandbox
|
|
96
|
+
? [
|
|
97
|
+
`subject sandbox: ${sandboxName(result.subjectSandbox)} killed=${result.subjectSandbox.killed ? "yes" : "no"}`,
|
|
98
|
+
]
|
|
99
|
+
: []),
|
|
100
|
+
...sandboxIdsLine(result),
|
|
75
101
|
...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
|
|
76
102
|
...(result.observer?.opened === undefined
|
|
77
103
|
? []
|
|
@@ -105,9 +131,10 @@ export function formatCuaStudyHuman(result) {
|
|
|
105
131
|
: []),
|
|
106
132
|
...(result.sandbox
|
|
107
133
|
? [
|
|
108
|
-
`sandbox: ${result.sandbox
|
|
134
|
+
`sandbox: ${sandboxName(result.sandbox)} stream=${result.sandbox.streamUrlPresent ? "connected" : "missing"} killed=${result.sandbox.killed ? "yes" : "no"}`,
|
|
109
135
|
]
|
|
110
136
|
: []),
|
|
137
|
+
...sandboxIdsLine(result),
|
|
111
138
|
...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
|
|
112
139
|
...(result.observer?.opened === undefined
|
|
113
140
|
? []
|
|
@@ -3,7 +3,7 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import { inspectStudyManifest, listStudyManifests } from "../../study/discover.js";
|
|
5
5
|
import { runStudyPreflight, } from "../../study/preflight.js";
|
|
6
|
-
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION,
|
|
6
|
+
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, JSON_OPTION_DESCRIPTION, parsePositiveInteger, writeResult, humanError, } from "../io.js";
|
|
7
7
|
export function registerStudyCommands(parent, io) {
|
|
8
8
|
const study = parent
|
|
9
9
|
.command("study")
|
|
@@ -36,7 +36,8 @@ function checkCommand(command, io) {
|
|
|
36
36
|
.choices(["metadata", "public-preview", "sandbox-loopback", "prepared-host"])
|
|
37
37
|
.default("metadata"))
|
|
38
38
|
.option("--timeout-ms <ms>", "Target reachability timeout.", String(30_000))
|
|
39
|
-
.option("--
|
|
39
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
40
|
+
.addOption(envFileAliasOption())
|
|
40
41
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
41
42
|
.action((name, options, command) => handleStudyCheck(io, name, options, command));
|
|
42
43
|
}
|
|
@@ -54,7 +55,7 @@ async function handleStudyCheck(io, name, options, command) {
|
|
|
54
55
|
if (!(await applyEnvFileOption({
|
|
55
56
|
command,
|
|
56
57
|
cwd: options.cwd,
|
|
57
|
-
envFile: options
|
|
58
|
+
envFile: dotenvPathOf(options, command, io),
|
|
58
59
|
io,
|
|
59
60
|
}))) {
|
|
60
61
|
return;
|
package/dist/cli/commands/tui.js
CHANGED
|
@@ -19,7 +19,7 @@ import { readRunDetail } from "../../run/detail.js";
|
|
|
19
19
|
import { launchRun, readLaunchLogTail } from "../../tui/launch.js";
|
|
20
20
|
import { TUI_MIN_NODE_MAJOR, nodeSupportsTui, TUI_BUNDLE_URL, } from "../../tui/contract.js";
|
|
21
21
|
import { detectAgentSession } from "../../actors/agent-session.js";
|
|
22
|
-
import { applyEnvFileOption, CLI_VERSION, CWD_OPTION_DESCRIPTION,
|
|
22
|
+
import { applyEnvFileOption, CLI_VERSION, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, JSON_OPTION_DESCRIPTION, markInvocationEnvelopeWritten, wantsJson, } from "../io.js";
|
|
23
23
|
export const defaultTuiRuntime = {
|
|
24
24
|
checkComms: checkCommsConnection,
|
|
25
25
|
promptSecret,
|
|
@@ -59,7 +59,8 @@ export function registerTuiCommand(parent, io, runtime = defaultTuiRuntime) {
|
|
|
59
59
|
.description("Browse studies and runs in an interactive terminal UI. It refuses detected agent sessions and non-TTY input or output; agents use humanish study list --json, humanish study show <study> --json and humanish runs --json.")
|
|
60
60
|
.summary("Browse studies and runs in a terminal UI for people.")
|
|
61
61
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
62
|
-
.option("--
|
|
62
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
63
|
+
.addOption(envFileAliasOption())
|
|
63
64
|
.option("--force", "Open it anyway in a session that looks like an agent's.")
|
|
64
65
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
65
66
|
.action((options, command) => handleTui(io, runtime, options, command));
|
|
@@ -89,7 +90,7 @@ async function handleTui(io, runtime, options, command) {
|
|
|
89
90
|
if (!(await applyEnvFileOption({
|
|
90
91
|
command,
|
|
91
92
|
cwd: options.cwd,
|
|
92
|
-
envFile: options
|
|
93
|
+
envFile: dotenvPathOf(options, command, io),
|
|
93
94
|
io,
|
|
94
95
|
env: runtime.env,
|
|
95
96
|
onDiscovered: (names) => names.forEach((name) => discoveredKeys.add(name)),
|
|
@@ -4,7 +4,7 @@ import { WATCH_SAFE_NOT_APPLICABLE_MESSAGE } from "../../observer/exposure.js";
|
|
|
4
4
|
import { runDryRun } from "../../run/dry-run.js";
|
|
5
5
|
import { runStudyCommand } from "./study-run.js";
|
|
6
6
|
import { addRunOptions, studyOnlyFlags } from "./run-command.js";
|
|
7
|
-
import { applyEnvFileOption, collectRepeated, formatRunHuman, parseObserverPort, parsePositiveInteger, wantsJson, writeResult, } from "../io.js";
|
|
7
|
+
import { applyEnvFileOption, collectRepeated, dotenvPathOf, formatRunHuman, parseObserverPort, parsePositiveInteger, wantsJson, writeResult, } from "../io.js";
|
|
8
8
|
import { followObserver, formatObserverHuman, watchExposeRequested } from "../observer-follow.js";
|
|
9
9
|
export function registerWatchCommand(parent, io) {
|
|
10
10
|
// A run's flags come from addRunOptions, the helper `run` uses, so the two cannot drift.
|
|
@@ -59,7 +59,7 @@ async function handleWatch(io, named, parsed, command) {
|
|
|
59
59
|
if (!(await applyEnvFileOption({
|
|
60
60
|
command,
|
|
61
61
|
cwd: options.cwd,
|
|
62
|
-
envFile: options
|
|
62
|
+
envFile: dotenvPathOf(options, command, io),
|
|
63
63
|
io,
|
|
64
64
|
// runStudyCommand discovers keys for a live study; a preview run needs none.
|
|
65
65
|
discoverKeys: false,
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type KeyResolutionDeps } from "../keys/key-resolution.js";
|
|
1
|
+
import { type KeyResolutionDeps, type DotenvLoad } from "../keys/key-resolution.js";
|
|
2
2
|
import { type DetectLocalAgentsOptions } from "../actors/local-agent/cli.js";
|
|
3
3
|
import { type StudySetupCheckArgs } from "../study/doctor.js";
|
|
4
4
|
declare const DOCTOR_SCHEMA = "humanish.doctor-result.v1";
|
|
@@ -48,5 +48,7 @@ export declare function doctor(cwdInput: string, options?: {
|
|
|
48
48
|
keyDeps?: KeyResolutionDeps;
|
|
49
49
|
/** The hosted Codex participant's operator handshake; tests replace it. */
|
|
50
50
|
codexParticipantReadiness?: StudySetupCheckArgs["codexParticipantReadiness"];
|
|
51
|
+
/** What `--dotenv` loaded, so a key row names the file that supplied it. */
|
|
52
|
+
dotenv?: DotenvLoad;
|
|
51
53
|
}): Promise<DoctorResult>;
|
|
52
54
|
export {};
|
package/dist/cli/doctor.js
CHANGED
|
@@ -65,7 +65,7 @@ export async function doctor(cwdInput, options = {}) {
|
|
|
65
65
|
}
|
|
66
66
|
const env = options.env ?? process.env;
|
|
67
67
|
const agents = await detectLocalAgents({ ...options.localAgents, env });
|
|
68
|
-
const { probes, receivingKey } = await probeDoctorKeys(cwd, env, options.study, options.keyDeps);
|
|
68
|
+
const { probes, receivingKey } = await probeDoctorKeys(cwd, env, options.study, options.keyDeps, options.dotenv);
|
|
69
69
|
const keyPresent = (name) => probes.some((probe) => probe.name === name && probe.source !== null);
|
|
70
70
|
const setup = options.study
|
|
71
71
|
? await studySetupChecks({
|
|
@@ -237,7 +237,7 @@ function terminalSurfaceCheck() {
|
|
|
237
237
|
};
|
|
238
238
|
}
|
|
239
239
|
/** Probes the live-run keys, plus the receiving-email key a real-comms study names. */
|
|
240
|
-
async function probeDoctorKeys(cwd, env, study, keyDeps) {
|
|
240
|
+
async function probeDoctorKeys(cwd, env, study, keyDeps, dotenv) {
|
|
241
241
|
const keyNames = new Set(["OPENAI_API_KEY", "E2B_API_KEY", "GH_TOKEN", "CODEX_API_KEY"]);
|
|
242
242
|
let receivingKey = null;
|
|
243
243
|
if (study) {
|
|
@@ -254,6 +254,7 @@ async function probeDoctorKeys(cwd, env, study, keyDeps) {
|
|
|
254
254
|
cwd,
|
|
255
255
|
env,
|
|
256
256
|
...(keyDeps === undefined ? {} : { deps: keyDeps }),
|
|
257
|
+
...(dotenv === undefined ? {} : { dotenv }),
|
|
257
258
|
});
|
|
258
259
|
return { probes, receivingKey };
|
|
259
260
|
}
|
|
@@ -281,7 +282,7 @@ function studyList(studies) {
|
|
|
281
282
|
}
|
|
282
283
|
/**
|
|
283
284
|
* Provider-key discovery: which source supplies each live-run key, through the same
|
|
284
|
-
* chain a live command resolves (env/--
|
|
285
|
+
* chain a live command resolves (env/--dotenv, project overlay, vendor stores, the
|
|
285
286
|
* humanish user store). Values never appear; sources and fill commands do.
|
|
286
287
|
*
|
|
287
288
|
* With --study, a key the selected route requires fails when missing. Without it, `keyUsers` maps
|
|
@@ -292,7 +293,7 @@ function keyChecks(probes, receivingKey, setup, keyUsers) {
|
|
|
292
293
|
return probes.map((probe) => {
|
|
293
294
|
const present = probe.source !== null;
|
|
294
295
|
const hint = probe.name === receivingKey
|
|
295
|
-
? `provide ${probe.name} through process env or --
|
|
296
|
+
? `provide ${probe.name} through process env or --dotenv`
|
|
296
297
|
: probe.hint;
|
|
297
298
|
if (!setup) {
|
|
298
299
|
const users = keyUsers?.get(probe.name) ?? [];
|
package/dist/cli/io.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Option } from "commander";
|
|
2
|
-
import { discoverProviderKeys } from "../keys/key-resolution.js";
|
|
2
|
+
import { discoverProviderKeys, type DotenvLoad } from "../keys/key-resolution.js";
|
|
3
3
|
import { type TelemetryProperties } from "./telemetry.js";
|
|
4
4
|
import type { RunResult } from "../run/results.js";
|
|
5
5
|
export declare const CLI_RESPONSE_SCHEMA = "humanish.cli-response.v1";
|
|
@@ -13,7 +13,18 @@ export interface CliIo {
|
|
|
13
13
|
export declare const JSON_OPTION_DESCRIPTION = "Print a machine-readable JSON response.";
|
|
14
14
|
export declare const CWD_OPTION_DESCRIPTION = "Project directory.";
|
|
15
15
|
export declare const RUN_OPTION_DESCRIPTION = "Run id, or latest.";
|
|
16
|
-
export declare const
|
|
16
|
+
export declare const DOTENV_OPTION_DESCRIPTION = "Load unset variables from this dotenv file. Values are never printed or saved.";
|
|
17
|
+
/**
|
|
18
|
+
* The hidden `--env-file <path>` that `--dotenv` replaced. Node scans the whole argv for
|
|
19
|
+
* `--env-file` before a script runs, and stops only at `--` (src/node_dotenv.cc), so a missing file
|
|
20
|
+
* exits 9 through Node and never reaches humanish. `--dotenv` does not collide. Removed in 0.111.0.
|
|
21
|
+
*/
|
|
22
|
+
export declare function envFileAliasOption(): Option;
|
|
23
|
+
/** The dotenv path a command was given: `--dotenv`, or the `--env-file` alias with its warning. */
|
|
24
|
+
export declare function dotenvPathOf(options: {
|
|
25
|
+
dotenv?: string | undefined;
|
|
26
|
+
envFile?: string | undefined;
|
|
27
|
+
}, command: Command, io: CliIo): string | undefined;
|
|
17
28
|
export declare const PORT_OPTION_DESCRIPTION = "Port to listen on at 127.0.0.1.";
|
|
18
29
|
/** `--port` for a loopback server whose default, 0, lets the OS pick a free port. */
|
|
19
30
|
export declare function freePortOption(): Option;
|
|
@@ -22,6 +33,7 @@ export interface StudyCommandOptions {
|
|
|
22
33
|
cwd: string;
|
|
23
34
|
detach?: boolean | undefined;
|
|
24
35
|
dryRun?: boolean | undefined;
|
|
36
|
+
dotenv?: string | undefined;
|
|
25
37
|
envFile?: string | undefined;
|
|
26
38
|
json?: boolean | undefined;
|
|
27
39
|
open?: boolean | undefined;
|
|
@@ -50,13 +62,15 @@ export declare function applyEnvFileOption(args: {
|
|
|
50
62
|
io: CliIo;
|
|
51
63
|
env?: NodeJS.ProcessEnv;
|
|
52
64
|
onDiscovered?: (names: string[]) => void;
|
|
65
|
+
/** Called with the dotenv path and the names it set, which were unset before. */
|
|
66
|
+
onLoaded?: (load: DotenvLoad) => void;
|
|
53
67
|
/** False for the study-running commands, which discover only once the study resolves to live. */
|
|
54
68
|
discoverKeys?: boolean;
|
|
55
69
|
}): Promise<boolean>;
|
|
56
70
|
/**
|
|
57
71
|
* Provider-key discovery: fill still-missing keys from the documented project overlay, the
|
|
58
72
|
* owning vendors' native stores, and the humanish user store. It is fill-only (an explicit
|
|
59
|
-
* --
|
|
73
|
+
* --dotenv or process env always wins), and each fill is announced by name and source on
|
|
60
74
|
* stderr, never by value. HUMANISH_STRICT_KEYS=1 restores env-only behavior.
|
|
61
75
|
*/
|
|
62
76
|
export declare function discoverCliKeys(args: {
|
package/dist/cli/io.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Command, Option } from "commander";
|
|
|
7
7
|
import { loadEnvFile } from "../keys/env-file.js";
|
|
8
8
|
import { discoverProviderKeys } from "../keys/key-resolution.js";
|
|
9
9
|
import { deriveRunFacts } from "./telemetry.js";
|
|
10
|
-
import { withQueuedWarnings } from "./deprecations.js";
|
|
10
|
+
import { warnAndQueue, withQueuedWarnings } from "./deprecations.js";
|
|
11
11
|
import { forTerminal } from "../routes/terminal/encoding.js";
|
|
12
12
|
export const CLI_RESPONSE_SCHEMA = "humanish.cli-response.v1";
|
|
13
13
|
function readCliVersion() {
|
|
@@ -23,7 +23,23 @@ export const CLI_VERSION = readCliVersion();
|
|
|
23
23
|
export const JSON_OPTION_DESCRIPTION = "Print a machine-readable JSON response.";
|
|
24
24
|
export const CWD_OPTION_DESCRIPTION = "Project directory.";
|
|
25
25
|
export const RUN_OPTION_DESCRIPTION = "Run id, or latest.";
|
|
26
|
-
export const
|
|
26
|
+
export const DOTENV_OPTION_DESCRIPTION = "Load unset variables from this dotenv file. Values are never printed or saved.";
|
|
27
|
+
const ENV_FILE_RENAMED = "`--env-file` is now `--dotenv`; `--env-file` is removed in 0.111.0.";
|
|
28
|
+
/**
|
|
29
|
+
* The hidden `--env-file <path>` that `--dotenv` replaced. Node scans the whole argv for
|
|
30
|
+
* `--env-file` before a script runs, and stops only at `--` (src/node_dotenv.cc), so a missing file
|
|
31
|
+
* exits 9 through Node and never reaches humanish. `--dotenv` does not collide. Removed in 0.111.0.
|
|
32
|
+
*/
|
|
33
|
+
export function envFileAliasOption() {
|
|
34
|
+
return new Option("--env-file <path>").hideHelp().conflicts("dotenv");
|
|
35
|
+
}
|
|
36
|
+
/** The dotenv path a command was given: `--dotenv`, or the `--env-file` alias with its warning. */
|
|
37
|
+
export function dotenvPathOf(options, command, io) {
|
|
38
|
+
if (options.envFile === undefined)
|
|
39
|
+
return options.dotenv;
|
|
40
|
+
warnAndQueue(command, io, ENV_FILE_RENAMED);
|
|
41
|
+
return options.envFile;
|
|
42
|
+
}
|
|
27
43
|
export const PORT_OPTION_DESCRIPTION = "Port to listen on at 127.0.0.1.";
|
|
28
44
|
/** `--port` for a loopback server whose default, 0, lets the OS pick a free port. */
|
|
29
45
|
export function freePortOption() {
|
|
@@ -74,6 +90,7 @@ export async function applyEnvFileOption(args) {
|
|
|
74
90
|
}
|
|
75
91
|
for (const name of result.loaded)
|
|
76
92
|
env[name] = stagedEnv[name];
|
|
93
|
+
args.onLoaded?.({ path: args.envFile, names: result.loaded });
|
|
77
94
|
}
|
|
78
95
|
if (args.discoverKeys !== false) {
|
|
79
96
|
await discoverCliKeys({
|
|
@@ -88,7 +105,7 @@ export async function applyEnvFileOption(args) {
|
|
|
88
105
|
/**
|
|
89
106
|
* Provider-key discovery: fill still-missing keys from the documented project overlay, the
|
|
90
107
|
* owning vendors' native stores, and the humanish user store. It is fill-only (an explicit
|
|
91
|
-
* --
|
|
108
|
+
* --dotenv or process env always wins), and each fill is announced by name and source on
|
|
92
109
|
* stderr, never by value. HUMANISH_STRICT_KEYS=1 restores env-only behavior.
|
|
93
110
|
*/
|
|
94
111
|
export async function discoverCliKeys(args) {
|
|
@@ -29,7 +29,7 @@ export async function resolveReceivingConnection(cwd, name, env, keyDeps) {
|
|
|
29
29
|
});
|
|
30
30
|
const apiKey = resolved[connection.apiKeyEnv]?.trim();
|
|
31
31
|
if (!apiKey)
|
|
32
|
-
throw new Error("Email connection credential is missing. Add it in Connections, or provide the configured key with --
|
|
32
|
+
throw new Error("Email connection credential is missing. Add it in Connections, or provide the configured key with --dotenv.");
|
|
33
33
|
return { connection, apiKey, adapter: createReceivingAdapter(connection, apiKey) };
|
|
34
34
|
}
|
|
35
35
|
export async function prepareReceivingRun(args) {
|
package/dist/comms/setup.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function checkCommsConnection(args) {
|
|
|
53
53
|
...base,
|
|
54
54
|
ready: false,
|
|
55
55
|
code: "credential_missing",
|
|
56
|
-
message: "The configured credential is missing. Add it in Connections or provide it with --
|
|
56
|
+
message: "The configured credential is missing. Add it in Connections or provide it with --dotenv.",
|
|
57
57
|
};
|
|
58
58
|
base.credentialPresent = true;
|
|
59
59
|
if (!args.online)
|
|
@@ -7,10 +7,14 @@ export declare function containsSensitive(text: string): boolean;
|
|
|
7
7
|
* passes only this value at a sandbox-id key.
|
|
8
8
|
*/
|
|
9
9
|
export declare const REDACTED_SANDBOX_ID = "[redacted-sandbox-id]";
|
|
10
|
+
/** A sandbox id's public stand-in: its digest matches a receipt without naming the sandbox. */
|
|
11
|
+
export declare function sandboxIdDigest(id: string): string;
|
|
12
|
+
/** The raw ids at the keys redactSandboxIds replaces, anywhere in `value`. */
|
|
13
|
+
export declare function collectSandboxIds(value: unknown, into?: Set<string>): Set<string>;
|
|
10
14
|
/**
|
|
11
|
-
* `value` with every sandbox id replaced by REDACTED_SANDBOX_ID: strings at
|
|
12
|
-
* `subjectSandboxId`, and the `id` of each `providerResources` entry.
|
|
13
|
-
*
|
|
15
|
+
* `value` with every sandbox id replaced by REDACTED_SANDBOX_ID plus its digest: strings at
|
|
16
|
+
* `sandboxId` and `subjectSandboxId`, and the `id` of each `providerResources` or `resources` entry. Raw ids live
|
|
17
|
+
* only in a run's sandbox-receipts.ndjson; every record and output names a sandbox by digest.
|
|
14
18
|
*/
|
|
15
19
|
export declare function redactSandboxIds(value: unknown): unknown;
|
|
16
20
|
/** Redact secrets to [REDACTED_SECRET] and local paths to their path labels. */
|