humanish 0.111.4 → 0.113.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 +37 -0
- package/README.md +11 -0
- package/TELEMETRY.md +21 -0
- package/dist/actors/codex/restricted-participant-policy.d.ts +21 -1
- package/dist/actors/codex/restricted-participant-policy.js +20 -2
- package/dist/actors/codex/restricted-participant.d.ts +1 -1
- package/dist/actors/codex/restricted-participant.js +2 -1
- package/dist/actors/computer-use/loop/debrief.d.ts +12 -4
- package/dist/actors/computer-use/loop/debrief.js +57 -20
- package/dist/actors/computer-use/loop/ending.d.ts +5 -2
- package/dist/actors/computer-use/loop/impressions.d.ts +14 -0
- package/dist/actors/computer-use/loop/impressions.js +40 -0
- package/dist/actors/computer-use/loop/session.d.ts +3 -1
- package/dist/actors/computer-use/loop/session.js +2 -0
- package/dist/actors/computer-use/loop/trace.js +2 -0
- package/dist/actors/computer-use/loop/types.d.ts +8 -1
- package/dist/actors/computer-use/loop.d.ts +2 -1
- package/dist/actors/computer-use/loop.js +15 -3
- package/dist/actors/computer-use/openai-conversation.d.ts +96 -0
- package/dist/actors/computer-use/openai-conversation.js +238 -0
- package/dist/actors/computer-use/openai-provider.js +38 -48
- package/dist/actors/contract.d.ts +45 -3
- package/dist/actors/contract.js +13 -0
- package/dist/analysis/diagnostics.js +7 -12
- package/dist/analysis/evidence-sources.d.ts +3 -1
- package/dist/analysis/evidence-sources.js +10 -1
- package/dist/analysis/evidence.js +2 -2
- package/dist/analysis/execute.d.ts +1 -1
- package/dist/analysis/execute.js +37 -5
- package/dist/analysis/service.js +7 -1
- package/dist/analysis/types.d.ts +23 -0
- package/dist/analysis/validation.d.ts +80 -35
- package/dist/analysis/validation.js +58 -11
- package/dist/cli/commands/runs.js +37 -20
- package/dist/cli/commands/setup.js +95 -6
- package/dist/cli/commands/study-format.js +7 -4
- package/dist/cli/commands/study.js +1 -1
- package/dist/cli/commands/tui.js +1 -1
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.js +18 -0
- package/dist/cli/findings.d.ts +34 -3
- package/dist/cli/findings.js +136 -18
- package/dist/cli/io.d.ts +4 -1
- package/dist/cli/io.js +3 -2
- package/dist/cli/program.js +35 -1
- package/dist/cli/secret-prompt.d.ts +5 -1
- package/dist/cli/secret-prompt.js +6 -2
- package/dist/cli/update-check-worker.d.ts +1 -0
- package/dist/cli/update-check-worker.js +5 -0
- package/dist/cli/update-check.d.ts +46 -0
- package/dist/cli/update-check.js +203 -0
- package/dist/feedback/draft.js +32 -4
- package/dist/keys/key-resolution.js +2 -0
- package/dist/keys/key-status.d.ts +37 -0
- package/dist/keys/key-status.js +68 -0
- package/dist/observer/run-routes.js +8 -4
- package/dist/observer-app.html +4 -4
- package/dist/routes/computer-use/bundle.d.ts +1 -1
- package/dist/routes/computer-use/bundle.js +6 -16
- package/dist/routes/computer-use/fanout-bundle.js +40 -37
- package/dist/routes/computer-use/fanout-records.d.ts +2 -0
- package/dist/routes/computer-use/fanout-records.js +18 -26
- package/dist/routes/computer-use/local-vm.js +9 -2
- package/dist/routes/computer-use/participant-desktop.d.ts +6 -0
- package/dist/routes/computer-use/participant-execution.js +13 -12
- package/dist/routes/computer-use/participant-facts.d.ts +2 -2
- package/dist/routes/computer-use/participant-facts.js +10 -1
- package/dist/routes/computer-use/rerun-selection.js +23 -21
- package/dist/routes/computer-use/result.js +10 -17
- package/dist/routes/computer-use/self-report.js +7 -1
- package/dist/routes/computer-use/setup.js +14 -0
- package/dist/routes/computer-use/single-bundle.d.ts +2 -0
- package/dist/routes/computer-use/single-bundle.js +81 -37
- package/dist/routes/computer-use/types.d.ts +4 -1
- package/dist/routes/scripted/bundle.d.ts +1 -1
- package/dist/routes/scripted/bundle.js +9 -19
- package/dist/routes/scripted/surface-records.d.ts +0 -1
- package/dist/routes/scripted/surface-records.js +27 -13
- package/dist/routes/shared-world/bundle-records.d.ts +2 -2
- package/dist/routes/shared-world/bundle-records.js +24 -30
- package/dist/routes/shared-world/bundle.d.ts +1 -1
- package/dist/routes/shared-world/bundle.js +71 -79
- package/dist/routes/shared-world/participant-specs.d.ts +1 -2
- package/dist/routes/shared-world/participant-specs.js +0 -8
- package/dist/routes/shared-world/result.js +8 -12
- package/dist/routes/terminal/bundle.d.ts +1 -1
- package/dist/routes/terminal/bundle.js +10 -19
- package/dist/routes/terminal/dry-run.js +1 -0
- package/dist/routes/terminal/live-sandbox.js +9 -2
- package/dist/routes/terminal/plan.js +1 -0
- package/dist/routes/terminal/runtime.d.ts +1 -0
- package/dist/routes/terminal/runtime.js +1 -0
- package/dist/routes/terminal/session.js +1 -0
- package/dist/run/costs.js +9 -20
- package/dist/run/detail.d.ts +1 -1
- package/dist/run/detail.js +13 -4
- package/dist/run/display.d.ts +2 -2
- package/dist/run/display.js +15 -18
- package/dist/run/dry-run.js +10 -25
- package/dist/run/judge.d.ts +24 -0
- package/dist/run/judge.js +54 -0
- package/dist/run/liveness.d.ts +18 -0
- package/dist/run/liveness.js +24 -0
- package/dist/run/outcomes.d.ts +8 -1
- package/dist/run/outcomes.js +58 -12
- package/dist/run/participant-caption.d.ts +31 -0
- package/dist/run/participant-caption.js +60 -0
- package/dist/run/participant-records.d.ts +4 -2
- package/dist/run/participant-records.js +6 -0
- package/dist/run/review-markdown.d.ts +11 -0
- package/dist/run/review-markdown.js +22 -0
- package/dist/run/run-cost.d.ts +7 -0
- package/dist/run/run-cost.js +25 -5
- package/dist/run/run-index.d.ts +4 -50
- package/dist/run/run-index.js +57 -87
- package/dist/run/run.d.ts +2 -2
- package/dist/run/run.js +5 -3
- package/dist/run/stats.js +2 -12
- package/dist/run/transient-comms-secrets.d.ts +8 -3
- package/dist/run/transient-comms-secrets.js +21 -1
- package/dist/run-study.js +17 -1
- package/dist/study/discover.js +6 -1
- package/dist/study/doctor.d.ts +2 -0
- package/dist/study/doctor.js +45 -7
- package/dist/study/init-templates.js +10 -5
- package/dist/study/keys.d.ts +1 -0
- package/dist/study/keys.js +1 -1
- package/dist/study/parse/execution.js +7 -1
- package/dist/study/plan-types.d.ts +1 -0
- package/dist/study/plan.js +3 -2
- package/dist/study/preflight-result.d.ts +1 -0
- package/dist/study/preflight-result.js +1 -1
- package/dist/study/preflight.d.ts +2 -0
- package/dist/study/preflight.js +54 -14
- package/dist/study/types.d.ts +4 -1
- package/dist/study/warnings.d.ts +7 -1
- package/dist/study/warnings.js +82 -4
- package/dist/substrates/local/capacity.d.ts +75 -0
- package/dist/substrates/local/capacity.js +143 -0
- package/dist/substrates/local/firecracker-desktop.d.ts +8 -0
- package/dist/substrates/local/firecracker-desktop.js +34 -3
- package/dist/substrates/local/runtime-host.d.ts +16 -3
- package/dist/substrates/local/runtime-host.js +49 -4
- package/dist/substrates/local/runtime.d.ts +10 -0
- package/dist/substrates/local/runtime.js +44 -5
- package/dist/tui-app.js +75 -75
- package/dist/verify/liveness.d.ts +2 -2
- package/dist/verify/liveness.js +7 -4
- package/docs/architecture/actor-contract.md +20 -6
- package/docs/architecture/terminal-product-route.md +14 -0
- package/docs/contracts/feedback.md +8 -1
- package/docs/contracts/run-bundle.md +10 -0
- package/docs/contracts/schemas.md +65 -34
- package/docs/contracts/study-analysis.md +86 -22
- package/package.json +2 -1
- package/skills/humanish/SKILL.md +21 -10
- package/dist/actors/computer-use/openai-requests.d.ts +0 -62
- package/dist/actors/computer-use/openai-requests.js +0 -144
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { listUserKeys, resolveKeyName, setUserKey, unsetUserKey, userKeyStorePath, } from "../../keys/key-resolution.js";
|
|
3
|
+
import { askForMissingKeys, formatKeyStatus, keyStatus, otherStoredKeys, } from "../../keys/key-status.js";
|
|
3
4
|
import { promptSecret } from "../secret-prompt.js";
|
|
4
5
|
import { runInit } from "../../study/init.js";
|
|
5
6
|
import { buildPayload, disabledByEnvironment, readTelemetryState, telemetryStatePath, writeTelemetryState, } from "../telemetry.js";
|
|
@@ -177,20 +178,107 @@ async function readSecretValue(useStdin, promptLabel) {
|
|
|
177
178
|
const text = Buffer.concat(chunks).toString("utf8").trim();
|
|
178
179
|
return text.length > 0 ? text : null;
|
|
179
180
|
}
|
|
180
|
-
|
|
181
|
+
// An empty line is no value here: only the walk over missing keys reads it as "skip".
|
|
182
|
+
return (await promptSecret(promptLabel, process.stdin, process.stderr)) || null;
|
|
183
|
+
}
|
|
184
|
+
/** `humanish keys` and `humanish keys status`: each provider key's source, never a value. */
|
|
185
|
+
async function writeKeyStatus(command, io) {
|
|
186
|
+
const rows = await keyStatus({ cwd: process.cwd(), env: process.env });
|
|
187
|
+
const others = otherStoredKeys(process.env);
|
|
188
|
+
const missing = rows.filter((row) => row.source === null).length;
|
|
189
|
+
const result = {
|
|
190
|
+
schema: KEYS_RESULT_SCHEMA,
|
|
191
|
+
ok: true,
|
|
192
|
+
action: "status",
|
|
193
|
+
store: userKeyStorePath(process.env),
|
|
194
|
+
names: rows.filter((row) => row.source !== null).map((row) => row.name),
|
|
195
|
+
keys: rows.map(({ name, use, source, hint }) => ({
|
|
196
|
+
name,
|
|
197
|
+
use,
|
|
198
|
+
source,
|
|
199
|
+
...(source === null ? { hint } : {}),
|
|
200
|
+
})),
|
|
201
|
+
...(others.length > 0 ? { otherStored: others } : {}),
|
|
202
|
+
message: missing === 0
|
|
203
|
+
? "Every provider key is set."
|
|
204
|
+
: `${plural(missing, "provider key")} ${missing === 1 ? "is" : "are"} missing.`,
|
|
205
|
+
};
|
|
206
|
+
writeResult(command, io, result, () => formatKeyStatus(rows, others));
|
|
207
|
+
io.setExitCode(0);
|
|
208
|
+
}
|
|
209
|
+
/** "A", "A and B", "A, B and C". */
|
|
210
|
+
function listNames(names) {
|
|
211
|
+
return names.length < 2 ? names.join("") : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* `humanish keys set` with no key named. On a terminal it asks for each missing provider key in
|
|
215
|
+
* turn; without one there is no one to ask, so it refuses and names the --stdin form.
|
|
216
|
+
*/
|
|
217
|
+
async function setMissingKeys(command, io, useStdin) {
|
|
218
|
+
const store = userKeyStorePath(process.env);
|
|
219
|
+
const finish = (ok, names, message) => {
|
|
220
|
+
const result = {
|
|
221
|
+
schema: KEYS_RESULT_SCHEMA,
|
|
222
|
+
ok,
|
|
223
|
+
action: "set",
|
|
224
|
+
store,
|
|
225
|
+
names,
|
|
226
|
+
message,
|
|
227
|
+
};
|
|
228
|
+
writeResult(command, io, result, formatKeysHuman);
|
|
229
|
+
io.setExitCode(ok ? 0 : 2);
|
|
230
|
+
};
|
|
231
|
+
if (useStdin || process.stdin.isTTY !== true || process.stderr.isTTY !== true) {
|
|
232
|
+
finish(false, [], `missing required argument 'vendor-or-name'. Without a terminal humanish cannot ask for each key, so name the key and pipe its value, for example: printf '%s' "$E2B_API_KEY" | ${cli("keys set e2b --stdin")}`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const rows = await keyStatus({ cwd: process.cwd(), env: process.env });
|
|
236
|
+
const missing = rows.filter((row) => row.source === null).length;
|
|
237
|
+
if (missing === 0) {
|
|
238
|
+
finish(true, [], `Every provider key is already set, so there is nothing to ask for. Replace one with \`${cli("keys set <vendor>")}\`.`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
io.writeErr(`${plural(missing, "provider key")} ${missing === 1 ? "is" : "are"} missing. Paste each one when asked, or press Enter to skip it.\n`);
|
|
242
|
+
const outcome = await askForMissingKeys({
|
|
243
|
+
rows,
|
|
244
|
+
env: process.env,
|
|
245
|
+
prompt: (label) => promptSecret(label, process.stdin, process.stderr),
|
|
246
|
+
});
|
|
247
|
+
const sentences = [
|
|
248
|
+
outcome.stored.length > 0
|
|
249
|
+
? `Stored ${listNames(outcome.stored)} (0600).`
|
|
250
|
+
: "Nothing was stored.",
|
|
251
|
+
...(outcome.skipped.length > 0 ? [`Skipped ${listNames(outcome.skipped)}.`] : []),
|
|
252
|
+
...(outcome.rejected ?? []).map((entry) => `${entry.name} was not stored: ${entry.message}`),
|
|
253
|
+
...(outcome.stoppedAt === undefined
|
|
254
|
+
? []
|
|
255
|
+
: [`Stopped at ${outcome.stoppedAt}; the keys after it were not asked for.`]),
|
|
256
|
+
`\`${cli("keys")}\` shows where each key comes from.`,
|
|
257
|
+
];
|
|
258
|
+
// A cancel is the person's choice, so only a value the store refused fails the command.
|
|
259
|
+
finish(outcome.rejected === undefined, outcome.stored, sentences.join(" "));
|
|
181
260
|
}
|
|
182
261
|
export function registerKeysCommand(parent, io) {
|
|
183
262
|
const keys = parent
|
|
184
263
|
.command("keys")
|
|
185
|
-
.description("
|
|
186
|
-
.summary("
|
|
264
|
+
.description("Show which provider keys are set and where each comes from, and manage the humanish user key store that provider-key discovery reads.")
|
|
265
|
+
.summary("Check and store your provider keys.");
|
|
266
|
+
keys
|
|
267
|
+
.command("status", { isDefault: true })
|
|
268
|
+
.description("Show each provider key humanish uses: where it comes from, or the command that adds it. Values are never printed.")
|
|
269
|
+
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
270
|
+
.action(async (_options, command) => writeKeyStatus(command, io));
|
|
187
271
|
keys
|
|
188
272
|
.command("set")
|
|
189
|
-
.argument("
|
|
190
|
-
.description("Store one provider key in the user store (0600), prompted with hidden input.")
|
|
273
|
+
.argument("[vendor-or-name]", "A vendor alias (openai, e2b, anthropic, github, agentmail) or a raw ENV_NAME. Without one, a terminal asks for each missing key in turn.")
|
|
274
|
+
.description("Store one provider key in the user store (0600), prompted with hidden input. With no key named, ask for each missing key in turn.")
|
|
191
275
|
.option("--stdin", "Read the value from stdin instead of prompting (for agents/pipes).")
|
|
192
276
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
193
277
|
.action(async (vendorOrName, options, command) => {
|
|
278
|
+
if (vendorOrName === undefined) {
|
|
279
|
+
await setMissingKeys(command, io, options.stdin === true);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
194
282
|
const name = resolveKeyName(vendorOrName);
|
|
195
283
|
const storePath = userKeyStorePath(process.env);
|
|
196
284
|
if (name === null) {
|
|
@@ -294,11 +382,12 @@ export function registerKeysCommand(parent, io) {
|
|
|
294
382
|
function formatDoctorHuman(result) {
|
|
295
383
|
return ([
|
|
296
384
|
`humanish doctor ${result.ok ? "ok" : "needs setup"}`,
|
|
385
|
+
...(result.next === undefined ? [] : [`next: ${result.next}`]),
|
|
297
386
|
`cwd: ${result.cwd}`,
|
|
298
387
|
// "missing" is a verdict, and a row that never ran has none. A participant reading doctor on a
|
|
299
388
|
// fresh desktop got `- missing package.json: package.json is present and safe to read`, which
|
|
300
389
|
// contradicts itself in eleven words (labs/tui-self-study.yaml).
|
|
301
|
-
...result.checks.map((check) => `- ${check.status.replace("_", " ")} ${check.name}: ${check.message}`),
|
|
390
|
+
...result.checks.map((check) => `- ${check.status.replace("_", " ")} ${check.name}: ${check.message.replaceAll("\n", "\n ")}`),
|
|
302
391
|
].join("\n") + "\n");
|
|
303
392
|
}
|
|
304
393
|
function formatInitHuman(result) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { participantCaption } from "../../run/participant-caption.js";
|
|
1
2
|
import { formatCuaDiagnostics, formatCuaStopCause } from "../../routes/computer-use/diagnostics.js";
|
|
2
3
|
/** A run's first lines: the command that ran, whether it was a dry run, how it ended, and its route. */
|
|
3
4
|
function runHeader(result, route) {
|
|
@@ -39,17 +40,19 @@ export function formatConcurrentSharedWorldStudyHuman(result) {
|
|
|
39
40
|
...runHeader(result, "shared-world"),
|
|
40
41
|
`run: ${result.runId}`,
|
|
41
42
|
`actor: ${result.actor}`,
|
|
42
|
-
`
|
|
43
|
+
`participants: ${result.roleCount}, up to ${result.concurrency} at once`,
|
|
43
44
|
...(result.host ? [`host: ${result.host}`] : []),
|
|
44
45
|
...(result.subject?.commit
|
|
45
|
-
? [`
|
|
46
|
+
? [`app: ${result.subject.repo}@${result.subject.commit.slice(0, 12)}`]
|
|
46
47
|
: []),
|
|
47
48
|
...(result.overlapProven === undefined
|
|
48
49
|
? []
|
|
49
50
|
: [
|
|
50
|
-
|
|
51
|
+
result.overlapProven
|
|
52
|
+
? "Participants used the app at the same time (observed in this run only)."
|
|
53
|
+
: "Participants were not observed using the app at the same time.",
|
|
51
54
|
]),
|
|
52
|
-
...result.roles.map((participant) =>
|
|
55
|
+
...result.roles.map((participant) => `${participantCaption({ id: participant.id, personaId: participant.persona })}: ${participantStatus(participant.status)}${participant.ok ? "" : ", not ok"}`),
|
|
53
56
|
...(result.subjectSandbox
|
|
54
57
|
? [
|
|
55
58
|
`subject sandbox: ${sandboxName(result.subjectSandbox)} killed=${result.subjectSandbox.killed ? "yes" : "no"}`,
|
|
@@ -140,7 +140,7 @@ function formatStudyCheckHuman(result) {
|
|
|
140
140
|
...(result.sandbox.created
|
|
141
141
|
? [`sandbox: created=yes killed=${result.sandbox.killed === true ? "yes" : "no"}`]
|
|
142
142
|
: []),
|
|
143
|
-
...result.checks.map((check) => `- ${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}`),
|
|
143
|
+
...result.checks.map((check) => `- ${check.checked === false ? "not checked" : check.ok ? "ok" : "fail"} ${check.name}: ${check.message}`),
|
|
144
144
|
...result.warnings.map((warning) => `warning: ${warning}`),
|
|
145
145
|
].join("\n") + "\n";
|
|
146
146
|
return result.error === undefined ? stdout : { stdout, error: result.error };
|
package/dist/cli/commands/tui.js
CHANGED
|
@@ -278,7 +278,7 @@ async function storeAgentmailKey(session) {
|
|
|
278
278
|
const { cwd, runtime, sessionEnv } = session;
|
|
279
279
|
// startTui has unmounted: only the host reads the credential, then remounts the view.
|
|
280
280
|
const value = await runtime.promptSecret("AgentMail API key", runtime.stdin, runtime.stdout);
|
|
281
|
-
if (value === null)
|
|
281
|
+
if (value === null || value === "")
|
|
282
282
|
return "Key entry cancelled. Nothing was changed.";
|
|
283
283
|
try {
|
|
284
284
|
setUserKey("AGENTMAIL_API_KEY", value, sessionEnv);
|
package/dist/cli/doctor.d.ts
CHANGED
package/dist/cli/doctor.js
CHANGED
|
@@ -11,6 +11,8 @@ import { studySetupChecks, studiesByRequiredKey, } from "../study/doctor.js";
|
|
|
11
11
|
import { prepareSelectedOutputDirectory, } from "../run/contained-output.js";
|
|
12
12
|
import { implicitProjectDirectoryExists, readImplicitProjectFile, validateCwd, } from "../run/project.js";
|
|
13
13
|
import { cli } from "./invocation.js";
|
|
14
|
+
import { recordedVersion } from "./update-check.js";
|
|
15
|
+
import { CLI_VERSION } from "./version.js";
|
|
14
16
|
const DOCTOR_SCHEMA = "humanish.doctor-result.v1";
|
|
15
17
|
/**
|
|
16
18
|
* Version 2.3.1 stopped holding a launched background command's event stream open. Version 2.3.2
|
|
@@ -94,6 +96,7 @@ export async function doctor(cwdInput, options = {}) {
|
|
|
94
96
|
...(await projectChecks(projectRoot)),
|
|
95
97
|
await desktopSdkCheck(setup),
|
|
96
98
|
terminalSurfaceCheck(),
|
|
99
|
+
...versionCheck(env),
|
|
97
100
|
...localAgentChecks(agents),
|
|
98
101
|
await claudeTranscriptCheck(env),
|
|
99
102
|
...keyChecks(probes, receivingKey, setup, keyUsers),
|
|
@@ -109,6 +112,7 @@ export async function doctor(cwdInput, options = {}) {
|
|
|
109
112
|
schema: DOCTOR_SCHEMA,
|
|
110
113
|
ok: checks.every((check) => check.ok),
|
|
111
114
|
cwd,
|
|
115
|
+
...(setup?.next === undefined ? {} : { next: setup.next }),
|
|
112
116
|
checks: withStatus(checks),
|
|
113
117
|
};
|
|
114
118
|
}
|
|
@@ -246,6 +250,20 @@ function terminalSurfaceCheck() {
|
|
|
246
250
|
}),
|
|
247
251
|
};
|
|
248
252
|
}
|
|
253
|
+
/** The latest version the update check recorded on this machine; no row before a check. */
|
|
254
|
+
function versionCheck(env) {
|
|
255
|
+
const recorded = recordedVersion(CLI_VERSION, env, env.HOME ?? homedir());
|
|
256
|
+
if (recorded === undefined)
|
|
257
|
+
return [];
|
|
258
|
+
return [
|
|
259
|
+
{
|
|
260
|
+
name: "humanish version",
|
|
261
|
+
ok: true,
|
|
262
|
+
...(recorded.newer ? { status: "note" } : {}),
|
|
263
|
+
message: recorded.message,
|
|
264
|
+
},
|
|
265
|
+
];
|
|
266
|
+
}
|
|
249
267
|
/** Probes the live-run keys, plus the receiving-email key a real-comms study names. */
|
|
250
268
|
async function probeDoctorKeys(cwd, env, study, keyDeps, dotenv) {
|
|
251
269
|
const keyNames = new Set(["OPENAI_API_KEY", "E2B_API_KEY", "GH_TOKEN", "CODEX_API_KEY"]);
|
package/dist/cli/findings.d.ts
CHANGED
|
@@ -4,14 +4,13 @@ import type { AnalysisArtifact, LoadedAnalysis } from "../analysis/types.js";
|
|
|
4
4
|
import { type CliIo } from "./io.js";
|
|
5
5
|
export declare const ANALYSIS_FINDINGS_SCHEMA = "humanish.analysis-findings.v1";
|
|
6
6
|
type Finding = NonNullable<AnalysisArtifact["result"]>["findings"][number];
|
|
7
|
+
type DesignFinding = NonNullable<NonNullable<AnalysisArtifact["result"]>["designFindings"]>[number];
|
|
7
8
|
/** `ready` and `stale` carry findings; every other state carries none and says why in `message`. */
|
|
8
9
|
type FindingsState = "ready" | "stale" | "running" | "none" | "skipped" | "failed" | "dry_run" | "unavailable";
|
|
9
|
-
interface
|
|
10
|
+
interface CitedEvidence {
|
|
10
11
|
id: string;
|
|
11
12
|
streamId: string;
|
|
12
13
|
kind: string;
|
|
13
|
-
/** How the finding's observations used this item: visual, action, participant_statement, inference. */
|
|
14
|
-
bases: string[];
|
|
15
14
|
/** Index of the latest retained capture at this item, from 0; null before any capture. */
|
|
16
15
|
frame: number | null;
|
|
17
16
|
/** Time since the first retained capture, not a video offset. */
|
|
@@ -20,8 +19,19 @@ interface FindingEvidence {
|
|
|
20
19
|
/** The capture file relative to the project directory, or null for nonvisual evidence. */
|
|
21
20
|
capture: string | null;
|
|
22
21
|
}
|
|
22
|
+
interface FindingEvidence extends CitedEvidence {
|
|
23
|
+
/** How the finding's observations used this item: visual, action, participant_statement, inference. */
|
|
24
|
+
bases: string[];
|
|
25
|
+
}
|
|
23
26
|
interface FindingView {
|
|
24
27
|
id: string;
|
|
28
|
+
/**
|
|
29
|
+
* One plain sentence about what happened, and one to three about what the person tried and what
|
|
30
|
+
* got in the way. Null in analyses written before them. An amended finding keeps the original
|
|
31
|
+
* text here; `correction.replacementClaim` replaces both.
|
|
32
|
+
*/
|
|
33
|
+
headline: string | null;
|
|
34
|
+
experience: string | null;
|
|
25
35
|
title: string;
|
|
26
36
|
summary: string;
|
|
27
37
|
impact: Finding["impact"];
|
|
@@ -41,6 +51,22 @@ interface FindingView {
|
|
|
41
51
|
replacementClaim: string | null;
|
|
42
52
|
} | null;
|
|
43
53
|
}
|
|
54
|
+
interface DesignFindingView {
|
|
55
|
+
id: string;
|
|
56
|
+
headline: string;
|
|
57
|
+
screen: string;
|
|
58
|
+
notice: string;
|
|
59
|
+
whyItMatters: string;
|
|
60
|
+
suggestion: string;
|
|
61
|
+
severity: DesignFinding["severity"];
|
|
62
|
+
confidence: DesignFinding["confidence"];
|
|
63
|
+
seenBy: {
|
|
64
|
+
streamId: string;
|
|
65
|
+
label: string;
|
|
66
|
+
}[];
|
|
67
|
+
/** Each cited item once; at least one has a capture. */
|
|
68
|
+
evidence: CitedEvidence[];
|
|
69
|
+
}
|
|
44
70
|
export interface AnalysisFindings {
|
|
45
71
|
schema: typeof ANALYSIS_FINDINGS_SCHEMA;
|
|
46
72
|
runId: string;
|
|
@@ -66,6 +92,11 @@ export interface AnalysisFindings {
|
|
|
66
92
|
summary: string | null;
|
|
67
93
|
/** Highest priority first, as the analysis ranked them. */
|
|
68
94
|
findings: FindingView[];
|
|
95
|
+
/**
|
|
96
|
+
* Problems a designer would see in the captures, most severe first. Null when the view carries
|
|
97
|
+
* no analysis result, or the analysis was written before design findings.
|
|
98
|
+
*/
|
|
99
|
+
designFindings: DesignFindingView[] | null;
|
|
69
100
|
limitations: string[];
|
|
70
101
|
warnings: string[];
|
|
71
102
|
}
|
package/dist/cli/findings.js
CHANGED
|
@@ -27,16 +27,10 @@ function analyzeCommand(source, provider) {
|
|
|
27
27
|
? `${run} --provider codex`
|
|
28
28
|
: `${run} --max-cost ${DEFAULT_ANALYSIS_MAX_COST_USD}`;
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (const observation of finding.observations)
|
|
33
|
-
for (const id of observation.evidenceIds) {
|
|
34
|
-
const set = bases.get(id) ?? new Set();
|
|
35
|
-
set.add(observation.basis);
|
|
36
|
-
bases.set(id, set);
|
|
37
|
-
}
|
|
30
|
+
/** The cited items the analysis evidence resolves, each once, in citation order. */
|
|
31
|
+
function citedEvidence(ids, analysis, runRoot) {
|
|
38
32
|
const byId = new Map(analysis.evidence.map((entry) => [entry.id, entry]));
|
|
39
|
-
return [...
|
|
33
|
+
return [...new Set(ids)].flatMap((id) => {
|
|
40
34
|
const entry = byId.get(id);
|
|
41
35
|
if (!entry)
|
|
42
36
|
return [];
|
|
@@ -45,7 +39,6 @@ function evidenceOf(finding, analysis, runRoot) {
|
|
|
45
39
|
id,
|
|
46
40
|
streamId: entry.streamId,
|
|
47
41
|
kind: entry.kind,
|
|
48
|
-
bases: [...used],
|
|
49
42
|
frame: entry.frame,
|
|
50
43
|
elapsedMs: entry.elapsedMs,
|
|
51
44
|
at: entry.at,
|
|
@@ -54,11 +47,58 @@ function evidenceOf(finding, analysis, runRoot) {
|
|
|
54
47
|
];
|
|
55
48
|
});
|
|
56
49
|
}
|
|
50
|
+
function evidenceOf(finding, analysis, runRoot) {
|
|
51
|
+
const bases = new Map();
|
|
52
|
+
for (const observation of finding.observations)
|
|
53
|
+
for (const id of observation.evidenceIds) {
|
|
54
|
+
const set = bases.get(id) ?? new Set();
|
|
55
|
+
set.add(observation.basis);
|
|
56
|
+
bases.set(id, set);
|
|
57
|
+
}
|
|
58
|
+
return citedEvidence(bases.keys(), analysis, runRoot).map(({ id, streamId, kind, ...entry }) => ({
|
|
59
|
+
id,
|
|
60
|
+
streamId,
|
|
61
|
+
kind,
|
|
62
|
+
bases: [...bases.get(id)],
|
|
63
|
+
...entry,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
const SEVERITY_RANK = {
|
|
67
|
+
major: 0,
|
|
68
|
+
moderate: 1,
|
|
69
|
+
minor: 2,
|
|
70
|
+
};
|
|
71
|
+
/** Most severe first; equal severity keeps the analysis order. */
|
|
72
|
+
function designFindingViews(analysis, runRoot) {
|
|
73
|
+
const designFindings = analysis.result?.designFindings;
|
|
74
|
+
if (designFindings === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
const labels = new Map(analysis.participants.map((p) => [p.streamId, p.label]));
|
|
77
|
+
return [...designFindings]
|
|
78
|
+
.sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])
|
|
79
|
+
.map((finding) => ({
|
|
80
|
+
id: finding.id,
|
|
81
|
+
headline: finding.headline,
|
|
82
|
+
screen: finding.screen,
|
|
83
|
+
notice: finding.notice,
|
|
84
|
+
whyItMatters: finding.whyItMatters,
|
|
85
|
+
suggestion: finding.suggestion,
|
|
86
|
+
severity: finding.severity,
|
|
87
|
+
confidence: finding.confidence,
|
|
88
|
+
seenBy: finding.seenByStreamIds.map((streamId) => ({
|
|
89
|
+
streamId,
|
|
90
|
+
label: labels.get(streamId) ?? streamId,
|
|
91
|
+
})),
|
|
92
|
+
evidence: citedEvidence(finding.evidenceIds, analysis, runRoot),
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
57
95
|
function findingView(finding, loaded, runRoot) {
|
|
58
96
|
const labels = new Map(loaded.analysis.participants.map((p) => [p.streamId, p.label]));
|
|
59
97
|
const correction = loaded.corrections.filter((entry) => entry.findingId === finding.id).at(-1);
|
|
60
98
|
return {
|
|
61
99
|
id: finding.id,
|
|
100
|
+
headline: finding.headline ?? null,
|
|
101
|
+
experience: finding.experience ?? null,
|
|
62
102
|
title: finding.title,
|
|
63
103
|
summary: finding.summary,
|
|
64
104
|
impact: finding.impact,
|
|
@@ -99,6 +139,7 @@ function withoutFindings(source, state, reason, message, next) {
|
|
|
99
139
|
path: null,
|
|
100
140
|
summary: null,
|
|
101
141
|
findings: [],
|
|
142
|
+
designFindings: null,
|
|
102
143
|
limitations: [],
|
|
103
144
|
warnings: source.loaded.warnings,
|
|
104
145
|
};
|
|
@@ -142,6 +183,7 @@ export function analysisFindings(source) {
|
|
|
142
183
|
path: path.join(source.runRoot, "analysis", analysis.id, "analysis.json"),
|
|
143
184
|
summary: result.summary,
|
|
144
185
|
findings: result.findings.map((finding) => findingView(finding, { ...loaded, analysis }, source.runRoot)),
|
|
186
|
+
designFindings: designFindingViews(analysis, source.runRoot),
|
|
145
187
|
limitations: result.limitations,
|
|
146
188
|
warnings: loaded.warnings,
|
|
147
189
|
};
|
|
@@ -288,20 +330,49 @@ function momentsText(evidence) {
|
|
|
288
330
|
parts.push(`${plural(unframed, "item")} with no frame`);
|
|
289
331
|
return parts.join(", ");
|
|
290
332
|
}
|
|
333
|
+
/** The capture files, relative to the run directory, in frame order. */
|
|
334
|
+
function captureFiles(evidence, runRoot) {
|
|
335
|
+
return [
|
|
336
|
+
...new Set(evidence
|
|
337
|
+
.filter((entry) => entry.capture !== null)
|
|
338
|
+
.sort((a, b) => (a.frame ?? 0) - (b.frame ?? 0))
|
|
339
|
+
.map((entry) => (runRoot ? path.relative(runRoot, entry.capture) : entry.capture))),
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* What a reader sees first: the headline, or the reviewer's claim when a review amended the finding,
|
|
344
|
+
* since the headline and experience describe the claim it replaced. Null before headlines existed.
|
|
345
|
+
*/
|
|
346
|
+
function leadOf(finding) {
|
|
347
|
+
const amended = finding.correction?.status === "amended" ? finding.correction : null;
|
|
348
|
+
if (finding.headline === null)
|
|
349
|
+
return null;
|
|
350
|
+
return amended?.replacementClaim
|
|
351
|
+
? { headline: amended.replacementClaim, corrected: true }
|
|
352
|
+
: { headline: finding.headline, corrected: false };
|
|
353
|
+
}
|
|
291
354
|
function findingLines(finding, runRoot) {
|
|
292
355
|
const cited = [...new Set(finding.evidence.map((entry) => entry.streamId))];
|
|
293
356
|
const labels = new Map(finding.affected.map((p) => [p.streamId, p.label]));
|
|
294
357
|
const affectedIds = finding.affected.map((p) => p.streamId);
|
|
295
358
|
const streams = [...affectedIds, ...cited.filter((id) => !labels.has(id))];
|
|
296
|
-
const captures =
|
|
297
|
-
...new Set(finding.evidence
|
|
298
|
-
.filter((entry) => entry.capture !== null)
|
|
299
|
-
.sort((a, b) => (a.frame ?? 0) - (b.frame ?? 0))
|
|
300
|
-
.map((entry) => (runRoot ? path.relative(runRoot, entry.capture) : entry.capture))),
|
|
301
|
-
];
|
|
359
|
+
const captures = captureFiles(finding.evidence, runRoot);
|
|
302
360
|
const correction = finding.correction;
|
|
361
|
+
const lead = leadOf(finding);
|
|
303
362
|
return [
|
|
304
|
-
|
|
363
|
+
...(lead === null
|
|
364
|
+
? [`${finding.id} ${finding.title}`]
|
|
365
|
+
: [
|
|
366
|
+
`${finding.id} ${lead.headline}`,
|
|
367
|
+
...(lead.corrected
|
|
368
|
+
? [
|
|
369
|
+
" Corrected in human review. The reviewer's claim replaces the original headline and account.",
|
|
370
|
+
]
|
|
371
|
+
: finding.experience === null
|
|
372
|
+
? []
|
|
373
|
+
: [` ${finding.experience}`]),
|
|
374
|
+
` evidence: ${finding.title}`,
|
|
375
|
+
]),
|
|
305
376
|
` impact: ${IMPACT_TEXT[finding.impact]} · confidence: ${finding.confidence} · recovery: ${RECOVERY_TEXT[finding.recovery]}`,
|
|
306
377
|
` ${finding.summary}`,
|
|
307
378
|
` affected: ${finding.affected.length} of ${plural(finding.exposedCount, "exposed participant")}`,
|
|
@@ -319,6 +390,34 @@ function findingLines(finding, runRoot) {
|
|
|
319
390
|
]),
|
|
320
391
|
];
|
|
321
392
|
}
|
|
393
|
+
function designFindingLines(finding, runRoot) {
|
|
394
|
+
const captures = captureFiles(finding.evidence, runRoot);
|
|
395
|
+
return [
|
|
396
|
+
`${finding.id} ${finding.severity}: ${finding.headline}`,
|
|
397
|
+
` screen: ${finding.screen}`,
|
|
398
|
+
` notice: ${finding.notice}`,
|
|
399
|
+
` why it matters: ${finding.whyItMatters}`,
|
|
400
|
+
` suggestion: ${finding.suggestion}`,
|
|
401
|
+
` seen by: ${finding.seenBy.map((p) => p.label).join(", ")} · confidence: ${finding.confidence}`,
|
|
402
|
+
...(captures.length === 0 ? [] : [` captures: ${captures.join(", ")}`]),
|
|
403
|
+
];
|
|
404
|
+
}
|
|
405
|
+
/** Nothing for an analysis written before design findings. */
|
|
406
|
+
function designSection(view) {
|
|
407
|
+
const design = view.designFindings;
|
|
408
|
+
if (design === null)
|
|
409
|
+
return [];
|
|
410
|
+
if (design.length === 0)
|
|
411
|
+
return ["", "design findings: none in the reviewed captures"];
|
|
412
|
+
return [
|
|
413
|
+
"",
|
|
414
|
+
`design findings: ${design.length}, most severe first`,
|
|
415
|
+
...design.flatMap((finding, index) => [
|
|
416
|
+
...(index === 0 ? [] : [""]),
|
|
417
|
+
...designFindingLines(finding, view.runPath ?? ""),
|
|
418
|
+
]),
|
|
419
|
+
];
|
|
420
|
+
}
|
|
322
421
|
/** The full findings block: the analysis, every finding with its evidence, and its limitations. */
|
|
323
422
|
export function formatFindings(view) {
|
|
324
423
|
if (view.state !== "ready" && view.state !== "stale")
|
|
@@ -330,6 +429,7 @@ export function formatFindings(view) {
|
|
|
330
429
|
...(view.state === "stale" ? [`warning: ${view.message}`, `next: ${view.next}`] : []),
|
|
331
430
|
...(view.summary ? [view.summary] : []),
|
|
332
431
|
...view.findings.flatMap((finding) => ["", ...findingLines(finding, view.runPath ?? "")]),
|
|
432
|
+
...designSection(view),
|
|
333
433
|
...(view.limitations.length === 0
|
|
334
434
|
? []
|
|
335
435
|
: ["", "limitations:", ...view.limitations.map((limitation) => `- ${limitation}`)]),
|
|
@@ -343,12 +443,30 @@ export function formatFindingsSummary(view, fullCommand, limit = 3) {
|
|
|
343
443
|
if (view.state !== "ready" && view.state !== "stale")
|
|
344
444
|
return [`findings: ${view.message}`, ...(view.next ? [`next: ${view.next}`] : [])];
|
|
345
445
|
const shown = view.findings.slice(0, limit);
|
|
446
|
+
const design = view.designFindings;
|
|
346
447
|
return [
|
|
347
448
|
`findings: ${view.findings.length === 0 ? "none" : view.findings.length}${view.state === "stale" ? " (stale)" : ""}`,
|
|
348
|
-
...shown.map((finding) =>
|
|
449
|
+
...shown.map((finding) => {
|
|
450
|
+
const qualities = `${IMPACT_TEXT[finding.impact]}, ${finding.confidence} confidence, ${RECOVERY_TEXT[finding.recovery]}`;
|
|
451
|
+
const lead = leadOf(finding);
|
|
452
|
+
if (lead === null)
|
|
453
|
+
return `- ${finding.id} ${qualities}: ${finding.title}`;
|
|
454
|
+
return `- ${finding.id} ${lead.headline} (${lead.corrected ? "corrected in human review" : qualities})`;
|
|
455
|
+
}),
|
|
349
456
|
...(view.findings.length > limit
|
|
350
457
|
? [`- and ${plural(view.findings.length - limit, "more finding")}`]
|
|
351
458
|
: []),
|
|
459
|
+
...(design === null
|
|
460
|
+
? []
|
|
461
|
+
: [
|
|
462
|
+
`design findings: ${design.length === 0 ? "none" : design.length}`,
|
|
463
|
+
...design
|
|
464
|
+
.slice(0, limit)
|
|
465
|
+
.map((finding) => `- ${finding.id} ${finding.severity}: ${finding.headline}`),
|
|
466
|
+
...(design.length > limit
|
|
467
|
+
? [`- and ${plural(design.length - limit, "more design finding")}`]
|
|
468
|
+
: []),
|
|
469
|
+
]),
|
|
352
470
|
`all findings: ${fullCommand}`,
|
|
353
471
|
];
|
|
354
472
|
}
|
package/dist/cli/io.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command, Option } from "commander";
|
|
2
2
|
import { discoverProviderKeys, type DotenvLoad } from "../keys/key-resolution.js";
|
|
3
3
|
import { type TelemetryProperties } from "./telemetry.js";
|
|
4
|
+
import type { UpdateCheckContext } from "./update-check.js";
|
|
4
5
|
import type { RunResult } from "../run/results.js";
|
|
5
6
|
export declare const CLI_RESPONSE_SCHEMA = "humanish.cli-response.v1";
|
|
6
7
|
export { CLI_VERSION } from "./version.js";
|
|
@@ -9,6 +10,7 @@ export interface CliIo {
|
|
|
9
10
|
writeErr(text: string): void;
|
|
10
11
|
setExitCode(code: number): void;
|
|
11
12
|
keyDiscovery?: typeof discoverProviderKeys;
|
|
13
|
+
updateCheck?: Partial<UpdateCheckContext>;
|
|
12
14
|
}
|
|
13
15
|
export declare const JSON_OPTION_DESCRIPTION = "Print a machine-readable JSON response.";
|
|
14
16
|
export declare const CWD_OPTION_DESCRIPTION = "Project directory.";
|
|
@@ -18,7 +20,8 @@ export declare const DOTENV_OPTION_DESCRIPTION = "Load unset variables from this
|
|
|
18
20
|
* The hidden `--env-file <path>` that `--dotenv` replaced. Node scans the whole argv for
|
|
19
21
|
* `--env-file` before a script runs, and stops only at `--` (src/node_dotenv.cc), so a missing file
|
|
20
22
|
* exits 9 through Node and never reaches humanish. `--dotenv` does not collide.
|
|
21
|
-
* Removed in
|
|
23
|
+
* Removed in the first minor release on or after 2026-11-03: deprecated in 0.110.0 on 2026-10-03, it stays at least 30 days and one
|
|
24
|
+
* minor release.
|
|
22
25
|
*/
|
|
23
26
|
export declare function envFileAliasOption(): Option;
|
|
24
27
|
/** The dotenv path a command was given: `--dotenv`, or the `--env-file` alias with its warning. */
|
package/dist/cli/io.js
CHANGED
|
@@ -15,12 +15,13 @@ export const JSON_OPTION_DESCRIPTION = "Print a machine-readable JSON response."
|
|
|
15
15
|
export const CWD_OPTION_DESCRIPTION = "Project directory.";
|
|
16
16
|
export const RUN_OPTION_DESCRIPTION = "Run id, or latest.";
|
|
17
17
|
export const DOTENV_OPTION_DESCRIPTION = "Load unset variables from this dotenv file. Values are never printed or saved.";
|
|
18
|
-
const ENV_FILE_RENAMED = "`--env-file` is now `--dotenv`; `--env-file` is removed in
|
|
18
|
+
const ENV_FILE_RENAMED = "`--env-file` is now `--dotenv`; `--env-file` is removed in the first minor release on or after 2026-11-03.";
|
|
19
19
|
/**
|
|
20
20
|
* The hidden `--env-file <path>` that `--dotenv` replaced. Node scans the whole argv for
|
|
21
21
|
* `--env-file` before a script runs, and stops only at `--` (src/node_dotenv.cc), so a missing file
|
|
22
22
|
* exits 9 through Node and never reaches humanish. `--dotenv` does not collide.
|
|
23
|
-
* Removed in
|
|
23
|
+
* Removed in the first minor release on or after 2026-11-03: deprecated in 0.110.0 on 2026-10-03, it stays at least 30 days and one
|
|
24
|
+
* minor release.
|
|
24
25
|
*/
|
|
25
26
|
export function envFileAliasOption() {
|
|
26
27
|
return new Option("--env-file <path>").hideHelp().conflicts("dotenv");
|
package/dist/cli/program.js
CHANGED
|
@@ -2,10 +2,11 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { formatOrientationHuman, readOrientation } from "./orientation.js";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { Command } from "commander";
|
|
5
|
+
import { Command, Help } from "commander";
|
|
6
6
|
import { redactText } from "../evidence/redaction.js";
|
|
7
7
|
import { PortInUseError } from "../observer/listen.js";
|
|
8
8
|
import { buildPayload, disabledByEnvironment, durationBucket, readTelemetryState, sendTelemetry, TELEMETRY_NOTICE, writeTelemetryState, isOwnCheckoutRun, } from "./telemetry.js";
|
|
9
|
+
import { checkForUpdate, startRefreshWorker } from "./update-check.js";
|
|
9
10
|
import { registerAnalyzeCommand } from "./commands/analyze.js";
|
|
10
11
|
import { registerCodexCommands } from "./commands/codex.js";
|
|
11
12
|
import { registerCommsCommands } from "./commands/comms.js";
|
|
@@ -95,6 +96,29 @@ async function recordCommandTelemetry(command, ok, durationMs, exitCode) {
|
|
|
95
96
|
// Metrics are our problem, never the operator's.
|
|
96
97
|
}
|
|
97
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* After a command, one line on stderr when a newer humanish is published. It reads only the cache;
|
|
101
|
+
* the registry request runs detached (see update-check.ts), so nothing here waits on the network.
|
|
102
|
+
*/
|
|
103
|
+
function noticeNewerVersion(command, io) {
|
|
104
|
+
try {
|
|
105
|
+
const notice = checkForUpdate({
|
|
106
|
+
installed: CLI_VERSION,
|
|
107
|
+
env: process.env,
|
|
108
|
+
terminal: process.stderr.isTTY === true,
|
|
109
|
+
json: wantsJson(command),
|
|
110
|
+
ownCheckout: isOwnCheckoutRun(process.cwd(), dirname(fileURLToPath(import.meta.url)), (p) => readFileSync(p, "utf8")),
|
|
111
|
+
now: Date.now(),
|
|
112
|
+
startRefresh: startRefreshWorker,
|
|
113
|
+
...io.updateCheck,
|
|
114
|
+
});
|
|
115
|
+
if (notice !== undefined)
|
|
116
|
+
io.writeErr(`${notice}\n`);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// An update notice is never a reason for a command to fail.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
98
122
|
/**
|
|
99
123
|
* HUMANISH_DEBUG_HANDLES=1: after a command's handler settles, name what is still keeping the
|
|
100
124
|
* process alive. A live terminal run wrote its result 64 s in and the CLI stayed up for
|
|
@@ -165,6 +189,7 @@ class HumanishCommand extends Command {
|
|
|
165
189
|
const noticed = announceTelemetryOnce(this, cliIo);
|
|
166
190
|
const finish = (ok) => {
|
|
167
191
|
void recordCommandTelemetry(this, ok, Date.now() - startedAt, lastExitCode);
|
|
192
|
+
noticeNewerVersion(this, cliIo);
|
|
168
193
|
reportActiveHandles(this, cliIo);
|
|
169
194
|
};
|
|
170
195
|
try {
|
|
@@ -326,6 +351,15 @@ export function createProgram(io = {}) {
|
|
|
326
351
|
// each subcommand when it is created.
|
|
327
352
|
.helpOption("-h, --help", "Show help for this command.")
|
|
328
353
|
.helpCommand("help [command]", "Show help for a command.")
|
|
354
|
+
// `help <command>` and `<command> help` keep working; a command's listing names only the
|
|
355
|
+
// commands that do something, so `help` is left out of every Commands section.
|
|
356
|
+
.configureHelp({
|
|
357
|
+
visibleCommands(command) {
|
|
358
|
+
return Help.prototype.visibleCommands
|
|
359
|
+
.call(this, command)
|
|
360
|
+
.filter((child) => child.name() !== "help");
|
|
361
|
+
},
|
|
362
|
+
})
|
|
329
363
|
.showHelpAfterError()
|
|
330
364
|
.option("--json", "Print machine-readable JSON responses where supported.")
|
|
331
365
|
.configureOutput({
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Host-owned entry: no secret crosses the TUI view contract or reaches a writable terminal.
|
|
3
|
+
* Resolves to the trimmed line, an empty string when Enter was pressed alone, or null when the
|
|
4
|
+
* person cancelled or there is no terminal.
|
|
5
|
+
*/
|
|
2
6
|
export declare function promptSecret(label: string, stdin: NodeJS.ReadStream, stdout: NodeJS.WriteStream): Promise<string | null>;
|