humanish 0.115.0 → 0.117.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.
Files changed (195) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +7 -6
  3. package/TELEMETRY.md +6 -0
  4. package/dist/actors/codex/app-server-ui.js +11 -4
  5. package/dist/actors/codex/restricted-participant-policy.d.ts +0 -2
  6. package/dist/actors/codex/restricted-participant-policy.js +4 -32
  7. package/dist/actors/codex/restricted-participant.d.ts +5 -0
  8. package/dist/actors/codex/restricted-participant.js +3 -2
  9. package/dist/actors/computer-use/actor.d.ts +4 -0
  10. package/dist/actors/computer-use/actor.js +2 -0
  11. package/dist/actors/computer-use/loop/actions.d.ts +4 -4
  12. package/dist/actors/computer-use/loop/actions.js +25 -18
  13. package/dist/actors/computer-use/loop/debrief.js +61 -27
  14. package/dist/actors/computer-use/loop/session.d.ts +3 -0
  15. package/dist/actors/computer-use/loop/session.js +9 -1
  16. package/dist/actors/computer-use/loop/trace.js +1 -0
  17. package/dist/actors/computer-use/loop/types.d.ts +16 -14
  18. package/dist/actors/computer-use/loop.d.ts +1 -1
  19. package/dist/actors/computer-use/loop.js +1 -1
  20. package/dist/actors/computer-use/wait.d.ts +74 -0
  21. package/dist/actors/computer-use/wait.js +100 -0
  22. package/dist/actors/contract.d.ts +14 -0
  23. package/dist/actors/scripted-browser/steps.js +6 -5
  24. package/dist/analysis/analysis-limits.d.ts +35 -0
  25. package/dist/analysis/analysis-limits.js +41 -0
  26. package/dist/analysis/validation.js +14 -21
  27. package/dist/browser-control/protocol.d.ts +86 -6
  28. package/dist/browser-control/protocol.js +13 -6
  29. package/dist/cli/commands/analyze.js +2 -1
  30. package/dist/cli/commands/notes.js +2 -1
  31. package/dist/cli/commands/run-signals.js +5 -1
  32. package/dist/cli/commands/runs.js +2 -1
  33. package/dist/cli/commands/setup.js +3 -3
  34. package/dist/cli/commands/study-format.d.ts +5 -2
  35. package/dist/cli/commands/study-format.js +29 -9
  36. package/dist/cli/commands/study-route-computer-use.js +2 -2
  37. package/dist/cli/commands/study-route-scripted.js +1 -1
  38. package/dist/cli/commands/tui.js +59 -17
  39. package/dist/cli/doctor.js +9 -2
  40. package/dist/cli/findings-text.d.ts +5 -0
  41. package/dist/cli/findings-text.js +169 -0
  42. package/dist/cli/findings.d.ts +0 -4
  43. package/dist/cli/findings.js +5 -167
  44. package/dist/cli/io.js +6 -1
  45. package/dist/cli/update-check.js +4 -1
  46. package/dist/comms/connections.js +4 -3
  47. package/dist/comms/setup.js +18 -12
  48. package/dist/evidence/redaction.js +9 -1
  49. package/dist/evidence/secret-scrub.d.ts +15 -5
  50. package/dist/evidence/secret-scrub.js +67 -9
  51. package/dist/feedback/draft.js +1 -1
  52. package/dist/feedback/export-bundle.js +18 -14
  53. package/dist/feedback/feedback.js +25 -7
  54. package/dist/guest/desktop-executor.js +3 -1
  55. package/dist/guest/desktop-keys.js +59 -5
  56. package/dist/keys/key-resolution.d.ts +9 -0
  57. package/dist/keys/key-resolution.js +60 -48
  58. package/dist/keys/key-status.d.ts +2 -0
  59. package/dist/keys/key-status.js +9 -16
  60. package/dist/observer/pinned-files.js +1 -1
  61. package/dist/observer/render.js +3 -2
  62. package/dist/observer-app.html +5 -5
  63. package/dist/routes/computer-use/bundle-parts.d.ts +4 -4
  64. package/dist/routes/computer-use/bundle-parts.js +2 -2
  65. package/dist/routes/computer-use/bundle.d.ts +4 -5
  66. package/dist/routes/computer-use/bundle.js +1 -3
  67. package/dist/routes/computer-use/e2b-desktop/prepare.js +40 -31
  68. package/dist/routes/computer-use/fanout-records.js +10 -11
  69. package/dist/routes/computer-use/live-phase.d.ts +1 -1
  70. package/dist/routes/computer-use/live-phase.js +0 -1
  71. package/dist/routes/computer-use/local-vm.js +5 -2
  72. package/dist/routes/computer-use/participant-execution.d.ts +3 -2
  73. package/dist/routes/computer-use/participant-model.js +9 -0
  74. package/dist/routes/computer-use/participant-runs.d.ts +6 -2
  75. package/dist/routes/computer-use/participant-runs.js +9 -5
  76. package/dist/routes/computer-use/plan.d.ts +3 -0
  77. package/dist/routes/computer-use/plan.js +18 -13
  78. package/dist/routes/computer-use/rerun-selection.js +2 -2
  79. package/dist/routes/computer-use/result.js +3 -6
  80. package/dist/routes/computer-use/setup.d.ts +4 -6
  81. package/dist/routes/computer-use/setup.js +2 -6
  82. package/dist/routes/computer-use/single-bundle.d.ts +4 -5
  83. package/dist/routes/computer-use/single-bundle.js +22 -25
  84. package/dist/routes/computer-use/subject-projection.d.ts +4 -8
  85. package/dist/routes/computer-use/subject-projection.js +9 -31
  86. package/dist/routes/computer-use/types.d.ts +5 -42
  87. package/dist/routes/scripted/scenario.js +28 -12
  88. package/dist/routes/scripted/session-result.js +6 -3
  89. package/dist/routes/shared-world/bundle-records.js +2 -8
  90. package/dist/routes/shared-world/participant-specs.d.ts +0 -2
  91. package/dist/routes/shared-world/participant-specs.js +1 -20
  92. package/dist/routes/shared-world/plan.d.ts +3 -0
  93. package/dist/routes/shared-world/plan.js +25 -4
  94. package/dist/routes/shared-world/setup.js +2 -2
  95. package/dist/routes/terminal/ledger.d.ts +9 -9
  96. package/dist/routes/terminal/ledger.js +63 -40
  97. package/dist/routes/terminal/live-finish.js +7 -10
  98. package/dist/routes/terminal/live-sandbox.d.ts +1 -1
  99. package/dist/routes/terminal/live-sandbox.js +14 -11
  100. package/dist/routes/terminal/plan.d.ts +3 -0
  101. package/dist/routes/terminal/plan.js +8 -5
  102. package/dist/routes/terminal/sandbox.d.ts +4 -3
  103. package/dist/routes/terminal/sandbox.js +5 -5
  104. package/dist/routes/terminal/token-usage.d.ts +1 -2
  105. package/dist/routes/terminal/token-usage.js +4 -6
  106. package/dist/routes/terminal/types.d.ts +9 -1
  107. package/dist/run/bundle-shape.js +6 -1
  108. package/dist/run/bundle.d.ts +5 -3
  109. package/dist/run/contained-output.d.ts +53 -5
  110. package/dist/run/contained-output.js +141 -42
  111. package/dist/run/detail.d.ts +2 -1
  112. package/dist/run/detail.js +12 -7
  113. package/dist/run/escape-sequences.d.ts +11 -0
  114. package/dist/run/escape-sequences.js +93 -0
  115. package/dist/run/evidence-files.d.ts +8 -8
  116. package/dist/run/evidence-files.js +19 -23
  117. package/dist/run/locate.d.ts +32 -3
  118. package/dist/run/locate.js +45 -40
  119. package/dist/run/notes.d.ts +0 -8
  120. package/dist/run/notes.js +5 -51
  121. package/dist/run/outcomes.d.ts +24 -0
  122. package/dist/run/outcomes.js +19 -0
  123. package/dist/run/paths.d.ts +2 -0
  124. package/dist/run/paths.js +5 -1
  125. package/dist/run/preflight-receipts.js +13 -8
  126. package/dist/run/project.js +7 -4
  127. package/dist/run/reclaim-outcomes.d.ts +8 -6
  128. package/dist/run/reclaim-outcomes.js +10 -6
  129. package/dist/run/reclaim.d.ts +5 -0
  130. package/dist/run/reclaim.js +43 -16
  131. package/dist/run/run-clock.d.ts +50 -0
  132. package/dist/run/run-clock.js +66 -0
  133. package/dist/run/run.js +5 -2
  134. package/dist/run/sandbox-ids.d.ts +7 -2
  135. package/dist/run/sandbox-ids.js +27 -11
  136. package/dist/run/secrets.d.ts +2 -1
  137. package/dist/run/secrets.js +18 -22
  138. package/dist/run/stored-runs.js +14 -7
  139. package/dist/run/transient-comms-secrets.js +6 -17
  140. package/dist/run-study.js +22 -13
  141. package/dist/study/adapter-scorer-loader.js +6 -4
  142. package/dist/study/config.js +4 -1
  143. package/dist/study/discover.js +9 -6
  144. package/dist/study/init-paths.js +7 -5
  145. package/dist/study/keys.d.ts +2 -0
  146. package/dist/study/keys.js +2 -0
  147. package/dist/study/migrate/v2.js +2 -1
  148. package/dist/study/parse/actors.js +8 -0
  149. package/dist/study/persona-resolve.d.ts +2 -2
  150. package/dist/study/persona-resolve.js +9 -5
  151. package/dist/study/plan-participants.d.ts +4 -0
  152. package/dist/study/plan-participants.js +4 -0
  153. package/dist/study/plan-types.d.ts +2 -2
  154. package/dist/study/plan.d.ts +5 -3
  155. package/dist/study/plan.js +18 -9
  156. package/dist/study/preflight-probes.js +11 -3
  157. package/dist/study/preflight.js +9 -5
  158. package/dist/study/summary.d.ts +8 -2
  159. package/dist/study/summary.js +20 -22
  160. package/dist/study/types.d.ts +14 -0
  161. package/dist/study/validation.d.ts +5 -0
  162. package/dist/study/validation.js +8 -0
  163. package/dist/study/warnings.d.ts +19 -12
  164. package/dist/study/warnings.js +50 -17
  165. package/dist/subject/serve.d.ts +3 -0
  166. package/dist/subject/serve.js +4 -1
  167. package/dist/substrates/detached.js +14 -7
  168. package/dist/substrates/e2b/connection.d.ts +12 -0
  169. package/dist/substrates/e2b/connection.js +35 -0
  170. package/dist/substrates/e2b/desktop-executor.d.ts +1 -5
  171. package/dist/substrates/e2b/desktop-executor.js +45 -12
  172. package/dist/substrates/e2b/lifetime.d.ts +24 -4
  173. package/dist/substrates/e2b/lifetime.js +41 -4
  174. package/dist/substrates/e2b/sandbox.d.ts +4 -3
  175. package/dist/substrates/e2b/sandbox.js +13 -18
  176. package/dist/substrates/e2b/sdk.d.ts +20 -9
  177. package/dist/substrates/local/capacity.d.ts +18 -2
  178. package/dist/substrates/local/capacity.js +21 -2
  179. package/dist/substrates/local/runtime-host.js +21 -14
  180. package/dist/substrates/local/runtime.js +3 -11
  181. package/dist/tui/contract.d.ts +30 -5
  182. package/dist/tui/contract.js +4 -1
  183. package/dist/tui-app.js +150 -147
  184. package/dist/verify/artifacts.d.ts +9 -2
  185. package/dist/verify/artifacts.js +37 -13
  186. package/dist/verify/liveness.js +19 -5
  187. package/dist/verify/verify.js +54 -27
  188. package/docs/architecture/browser-control.md +10 -4
  189. package/docs/architecture/guest-desktop.md +7 -2
  190. package/docs/architecture/state-driven-executor.md +4 -2
  191. package/docs/contracts/run-bundle.md +18 -10
  192. package/docs/contracts/schemas.md +46 -29
  193. package/docs/contracts/study-analysis.md +10 -1
  194. package/docs/product/automatic-analysis.md +7 -1
  195. package/package.json +2 -2
@@ -3,18 +3,35 @@ import { ComputerUseExecutorError } from "../actors/computer-use/executor-error.
3
3
  // hand these strings to xdotool, which has a command language, so only names from these tables
4
4
  // ever reach it. The guest runtime ships this module, so it lives in src/guest/, which CI's
5
5
  // guest-desktop job and scripts/guest-desktop-proof.mjs key on.
6
+ //
7
+ // The tables take the names in OpenAI's computer-use key map, the names @e2b/desktop's press()
8
+ // mapped (the hosted desktop sent keys through it before), and every printable ASCII
9
+ // punctuation character. xdotool finds the key and the Shift level for a keysym itself, so `?`
10
+ // arrives as `question` with Shift held on an American English keymap.
6
11
  /** The modifiers a pointer action may hold, keyed by the upper-cased provider name. */
7
12
  const modifierNames = Object.freeze({
8
13
  CTRL: "ctrl",
9
14
  CONTROL: "ctrl",
10
15
  ALT: "alt",
16
+ OPTION: "alt",
11
17
  SHIFT: "shift",
12
18
  META: "super",
13
19
  SUPER: "super",
14
20
  CMD: "super",
21
+ COMMAND: "super",
22
+ WIN: "super",
23
+ WINDOWS: "super",
15
24
  });
16
25
  const keyNames = Object.freeze({
17
26
  ...modifierNames,
27
+ CONTROL_LEFT: "Control_L",
28
+ CONTROL_RIGHT: "Control_R",
29
+ ALT_LEFT: "Alt_L",
30
+ ALT_RIGHT: "Alt_R",
31
+ SHIFT_LEFT: "Shift_L",
32
+ SHIFT_RIGHT: "Shift_R",
33
+ SUPER_LEFT: "Super_L",
34
+ SUPER_RIGHT: "Super_R",
18
35
  ENTER: "Return",
19
36
  RETURN: "Return",
20
37
  TAB: "Tab",
@@ -24,11 +41,14 @@ const keyNames = Object.freeze({
24
41
  " ": "space",
25
42
  BACKSPACE: "BackSpace",
26
43
  DELETE: "Delete",
44
+ DEL: "Delete",
27
45
  INSERT: "Insert",
28
46
  HOME: "Home",
29
47
  END: "End",
30
48
  PAGEUP: "Prior",
31
49
  PAGEDOWN: "Next",
50
+ PAGE_UP: "Prior",
51
+ PAGE_DOWN: "Next",
32
52
  ARROWUP: "Up",
33
53
  ARROWDOWN: "Down",
34
54
  ARROWLEFT: "Left",
@@ -37,18 +57,45 @@ const keyNames = Object.freeze({
37
57
  DOWN: "Down",
38
58
  LEFT: "Left",
39
59
  RIGHT: "Right",
60
+ CAPS_LOCK: "Caps_Lock",
61
+ NUM_LOCK: "Num_Lock",
62
+ SCROLL_LOCK: "Scroll_Lock",
63
+ PAUSE: "Pause",
64
+ BREAK: "Pause",
65
+ PRINT: "Print",
66
+ MENU: "Menu",
67
+ "!": "exclam",
68
+ '"': "quotedbl",
69
+ "#": "numbersign",
70
+ $: "dollar",
71
+ "%": "percent",
72
+ "&": "ampersand",
73
+ "'": "apostrophe",
74
+ "(": "parenleft",
75
+ ")": "parenright",
76
+ "*": "asterisk",
40
77
  "+": "plus",
41
- "-": "minus",
42
- "=": "equal",
43
78
  ",": "comma",
79
+ "-": "minus",
44
80
  ".": "period",
45
81
  "/": "slash",
46
- "\\": "backslash",
82
+ ":": "colon",
47
83
  ";": "semicolon",
48
- "'": "apostrophe",
84
+ "<": "less",
85
+ "=": "equal",
86
+ ">": "greater",
87
+ "?": "question",
88
+ "@": "at",
49
89
  "[": "bracketleft",
90
+ "\\": "backslash",
50
91
  "]": "bracketright",
92
+ "^": "asciicircum",
93
+ _: "underscore",
51
94
  "`": "grave",
95
+ "{": "braceleft",
96
+ "|": "bar",
97
+ "}": "braceright",
98
+ "~": "asciitilde",
52
99
  });
53
100
  function lookup(table, key) {
54
101
  const upper = key.toUpperCase();
@@ -59,9 +106,16 @@ function chord(names) {
59
106
  throw new ComputerUseExecutorError("action_rejected", "not_dispatched");
60
107
  return names.join("+");
61
108
  }
109
+ /**
110
+ * A name that is itself a chord (`Control+a`, `ctrl++`) split at each plus sign that joins two
111
+ * names. A lone `+` and a trailing `+` after a joining one are the plus key.
112
+ */
113
+ function chordParts(key) {
114
+ return key.length > 1 ? key.split(/\+(?!$)/) : [key];
115
+ }
62
116
  /** A keypress chord in xdotool syntax. Unknown or repeated keys are refused before dispatch. */
63
117
  export function xdotoolChord(keys) {
64
- return chord(keys.map((key) => {
118
+ return chord(keys.flatMap(chordParts).map((key) => {
65
119
  if (/^[a-z0-9]$/i.test(key))
66
120
  return key.toLowerCase();
67
121
  if (/^F(?:[1-9]|1[0-2])$/i.test(key))
@@ -1,3 +1,12 @@
1
+ /** The vendor aliases `humanish keys set` accepts. A raw ENV_NAME is also accepted. */
2
+ export declare const KEY_VENDORS: string[];
3
+ /** The keys `humanish keys` lists, in its order, each with what humanish uses it for. */
4
+ export declare const LISTED_KEYS: {
5
+ name: string;
6
+ use: string;
7
+ }[];
8
+ /** The keys `humanish doctor` reports on every run, in its row order. */
9
+ export declare const DOCTOR_KEYS: string[];
1
10
  export interface ResolvedKeyFill {
2
11
  name: string;
3
12
  /** Human-readable source label (a path or command name), never a value. */
@@ -24,33 +24,56 @@ import { loadEnvFile } from "./env-file.js";
24
24
  import { cli } from "../cli/invocation.js";
25
25
  import { humanishConfigFile } from "../cli/user-config.js";
26
26
  import { OPENAI_EGRESS_PLACEHOLDER } from "../routes/terminal/runtime-auth.js";
27
- /** The only names implicit discovery may fill (and `humanish keys set` may store). Everything
28
- * else in an overlay/store file is ignored-and-named: a repo-planted NODE_OPTIONS/LD_PRELOAD
29
- * must never enter process env off a file the operator did not explicitly pass (an explicit
30
- * --dotenv remains the operator's own full-file load). */
31
- const KNOWN_PROVIDER_KEYS = [
32
- "OPENAI_API_KEY",
33
- "ANTHROPIC_API_KEY",
34
- "E2B_API_KEY",
35
- "GH_TOKEN",
36
- "GITHUB_TOKEN",
37
- "CODEX_API_KEY",
38
- "AGENTMAIL_API_KEY",
27
+ /**
28
+ * The provider keys, in the order `humanish keys set` names their vendors. Implicit discovery fills
29
+ * only these names and the user store holds only these. Everything else in an overlay or store file
30
+ * is ignored and named: a repo-planted NODE_OPTIONS/LD_PRELOAD must never enter process env off a
31
+ * file the operator did not explicitly pass (an explicit --dotenv remains the operator's own
32
+ * full-file load). ANTHROPIC_API_KEY is not listed, and doctor does not report it: a Claude Code
33
+ * participant runs on its own login and never receives it. CODEX_API_KEY is an alternative spelling
34
+ * of the OpenAI key for terminal studies. Doctor reports AGENTMAIL_API_KEY only under `--study`, for
35
+ * a study whose email connection names it, since a connection may name another variable.
36
+ */
37
+ const PROVIDER_KEYS = [
38
+ {
39
+ name: "OPENAI_API_KEY",
40
+ vendor: "openai",
41
+ listed: { order: 2, use: "participant model and analysis" },
42
+ doctor: true,
43
+ },
44
+ {
45
+ name: "E2B_API_KEY",
46
+ vendor: "e2b",
47
+ login: "e2b auth login",
48
+ listed: { order: 1, use: "hosted desktops" },
49
+ doctor: true,
50
+ },
51
+ { name: "ANTHROPIC_API_KEY", vendor: "anthropic" },
52
+ {
53
+ name: "GH_TOKEN",
54
+ vendor: "github",
55
+ login: "gh auth login",
56
+ listed: { order: 3, use: "private repository subjects" },
57
+ doctor: true,
58
+ },
59
+ { name: "GITHUB_TOKEN", spellingOf: "GH_TOKEN" },
60
+ { name: "CODEX_API_KEY", doctor: true },
61
+ { name: "AGENTMAIL_API_KEY", vendor: "agentmail", listed: { order: 4, use: "email in studies" } },
39
62
  ];
40
- const PROVIDER_KEY_SET = new Set(KNOWN_PROVIDER_KEYS);
63
+ const PROVIDER_KEY_SET = new Set(PROVIDER_KEYS.map((key) => key.name));
64
+ const providerKey = (name) => PROVIDER_KEYS.find((key) => key.name === name);
65
+ /** The vendor aliases `humanish keys set` accepts. A raw ENV_NAME is also accepted. */
66
+ export const KEY_VENDORS = PROVIDER_KEYS.flatMap((key) => key.vendor === undefined ? [] : [key.vendor]);
67
+ /** The keys `humanish keys` lists, in its order, each with what humanish uses it for. */
68
+ export const LISTED_KEYS = PROVIDER_KEYS.flatMap(({ name, listed }) => listed === undefined ? [] : [{ name, ...listed }])
69
+ .sort((a, b) => a.order - b.order)
70
+ .map(({ name, use }) => ({ name, use }));
71
+ /** The keys `humanish doctor` reports on every run, in its row order. */
72
+ export const DOCTOR_KEYS = PROVIDER_KEYS.flatMap(({ name, doctor }) => (doctor ? [name] : []));
41
73
  /** An env for a vendor CLI that discovery runs: the caller's, without any provider key name. */
42
74
  function withoutProviderKeys(env) {
43
75
  return Object.fromEntries(Object.entries(env).filter(([name]) => !PROVIDER_KEY_SET.has(name)));
44
76
  }
45
- /** `humanish keys set <vendor>` aliases; a raw ENV_NAME is also accepted. A Map, so a name such as
46
- * `constructor` is no alias. */
47
- const KEY_VENDOR_ALIASES = new Map([
48
- ["openai", "OPENAI_API_KEY"],
49
- ["e2b", "E2B_API_KEY"],
50
- ["anthropic", "ANTHROPIC_API_KEY"],
51
- ["github", "GH_TOKEN"],
52
- ["agentmail", "AGENTMAIL_API_KEY"],
53
- ]);
54
77
  const PROJECT_OVERLAY_RELATIVE = path.join(".humanish", "local", "provider.env");
55
78
  export function userKeyStorePath(env, deps = {}) {
56
79
  return humanishConfigFile(env, "keys.env", deps.homeDir);
@@ -230,21 +253,10 @@ function storeLabel(env, deps) {
230
253
  /** The nearest fill instruction for a missing key. Doctor rows use it, and it is appended to
231
254
  * *_KEYS_MISSING errors so the failure names the fix. */
232
255
  export function missingKeyHint(name) {
233
- switch (name) {
234
- case "E2B_API_KEY":
235
- return `run \`e2b auth login\`, or \`${cli("keys set e2b")}\``;
236
- case "GH_TOKEN":
237
- case "GITHUB_TOKEN":
238
- return `run \`gh auth login\`, or \`${cli("keys set github")}\``;
239
- case "OPENAI_API_KEY":
240
- return `run \`${cli("keys set openai")}\``;
241
- case "ANTHROPIC_API_KEY":
242
- return `run \`${cli("keys set anthropic")}\``;
243
- case "AGENTMAIL_API_KEY":
244
- return `run \`${cli("keys set agentmail")}\``;
245
- default:
246
- return `run \`${cli(`keys set ${name}`)}\``;
247
- }
256
+ const named = providerKey(name);
257
+ const key = named?.spellingOf === undefined ? named : providerKey(named.spellingOf);
258
+ const set = `\`${cli(`keys set ${key?.vendor ?? name}`)}\``;
259
+ return key?.login === undefined ? `run ${set}` : `run \`${key.login}\`, or ${set}`;
248
260
  }
249
261
  /** One shared suffix for *_KEYS_MISSING messages: where discovery looked, and what fills each
250
262
  * missing key. Pure text; never values. */
@@ -276,19 +288,18 @@ export async function probeKeySources(names, args) {
276
288
  ...(args.deps === undefined ? {} : { deps: args.deps }),
277
289
  });
278
290
  const bySource = new Map(fills.map((fill) => [fill.name, fill.source]));
291
+ const setInEnv = (name) => args.env[name] !== undefined && args.env[name]?.trim() !== "";
279
292
  return names.map((name) => {
280
- const inEnv = args.env[name] !== undefined && args.env[name]?.trim() !== "" && !placeholder(name);
281
- // GH_TOKEN and GITHUB_TOKEN are one credential with two spellings; a doctor row that says
282
- // "missing" while GITHUB_TOKEN sits in the env would be wrong.
283
- const aliasInEnv = name === "GH_TOKEN" &&
284
- args.env.GITHUB_TOKEN !== undefined &&
285
- args.env.GITHUB_TOKEN.trim() !== "";
293
+ const inEnv = setInEnv(name) && !placeholder(name);
294
+ // A second spelling of the key in the env, such as GITHUB_TOKEN for GH_TOKEN, is the same
295
+ // credential; a doctor row that says "missing" while it sits in the env would be wrong.
296
+ const spelling = PROVIDER_KEYS.find((key) => key.spellingOf === name && setInEnv(key.name))?.name;
286
297
  const source = inEnv
287
298
  ? args.dotenv?.names.includes(name)
288
299
  ? `--dotenv ${args.dotenv.path}`
289
300
  : "process env"
290
- : aliasInEnv
291
- ? "process env (GITHUB_TOKEN)"
301
+ : spelling !== undefined
302
+ ? `process env (${spelling})`
292
303
  : (bySource.get(name) ?? null);
293
304
  return {
294
305
  name,
@@ -301,9 +312,10 @@ export async function probeKeySources(names, args) {
301
312
  const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
302
313
  /** Resolve a `humanish keys set` target: a vendor alias or a raw env name. Null = invalid. */
303
314
  export function resolveKeyName(vendorOrName) {
304
- const alias = KEY_VENDOR_ALIASES.get(vendorOrName.toLowerCase());
305
- if (alias !== undefined)
306
- return alias;
315
+ const vendor = vendorOrName.toLowerCase();
316
+ const key = PROVIDER_KEYS.find((candidate) => candidate.vendor === vendor);
317
+ if (key !== undefined)
318
+ return key.name;
307
319
  return ENV_NAME.test(vendorOrName) ? vendorOrName : null;
308
320
  }
309
321
  /** Write one key into the user store (0700 dir, 0600 file). Only allowlisted provider names
@@ -10,6 +10,8 @@ export declare function keyStatus(args: {
10
10
  }): Promise<KeyStatusRow[]>;
11
11
  /** Names in the user store that the status does not list, such as ANTHROPIC_API_KEY. */
12
12
  export declare function otherStoredKeys(env: NodeJS.ProcessEnv, deps?: KeyResolutionDeps): string[];
13
+ /** The line `humanish keys` prints for one key: what it is for, and its source or how to add it. */
14
+ export declare function keyStatusLine(row: KeyStatusRow): string;
13
15
  export declare function formatKeyStatus(rows: readonly KeyStatusRow[], otherStored?: readonly string[]): string;
14
16
  interface MissingKeysOutcome {
15
17
  stored: string[];
@@ -1,33 +1,26 @@
1
1
  // `humanish keys`: which provider keys are set and where each comes from, through the same chain a
2
2
  // live command resolves. Never a value.
3
3
  import { cli } from "../cli/invocation.js";
4
- import { listUserKeys, probeKeySources, setUserKey, } from "./key-resolution.js";
5
- /**
6
- * The keys a person sets for humanish, in the order the status lists them and `keys set` asks for
7
- * them. ANTHROPIC_API_KEY is left out: a Claude Code participant runs on its own login and never
8
- * receives it. CODEX_API_KEY is an alternative spelling of the OpenAI key for terminal studies.
9
- */
10
- const STATUS_KEYS = [
11
- { name: "E2B_API_KEY", use: "hosted desktops" },
12
- { name: "OPENAI_API_KEY", use: "participant model and analysis" },
13
- { name: "GH_TOKEN", use: "private repository subjects" },
14
- { name: "AGENTMAIL_API_KEY", use: "email in studies" },
15
- ];
4
+ import { LISTED_KEYS, listUserKeys, probeKeySources, setUserKey, } from "./key-resolution.js";
16
5
  export async function keyStatus(args) {
17
- const probes = await probeKeySources(STATUS_KEYS.map((key) => key.name), {
6
+ const probes = await probeKeySources(LISTED_KEYS.map((key) => key.name), {
18
7
  cwd: args.cwd,
19
8
  env: args.env,
20
9
  ...(args.deps === undefined ? {} : { deps: args.deps }),
21
10
  });
22
- return probes.map((probe, index) => ({ ...probe, use: STATUS_KEYS[index].use }));
11
+ return probes.map((probe, index) => ({ ...probe, use: LISTED_KEYS[index].use }));
23
12
  }
24
13
  /** Names in the user store that the status does not list, such as ANTHROPIC_API_KEY. */
25
14
  export function otherStoredKeys(env, deps = {}) {
26
- const listed = new Set(STATUS_KEYS.map((key) => key.name));
15
+ const listed = new Set(LISTED_KEYS.map((key) => key.name));
27
16
  return listUserKeys(env, deps).filter((name) => !listed.has(name));
28
17
  }
18
+ /** The line `humanish keys` prints for one key: what it is for, and its source or how to add it. */
19
+ export function keyStatusLine(row) {
20
+ return `${row.name} (${row.use}): ${row.source === null ? `missing; ${row.hint}` : `set, from ${row.source}`}`;
21
+ }
29
22
  export function formatKeyStatus(rows, otherStored = []) {
30
- const lines = rows.map((row) => `${row.name} (${row.use}): ${row.source === null ? `missing; ${row.hint}` : `set, from ${row.source}`}`);
23
+ const lines = rows.map(keyStatusLine);
31
24
  if (otherStored.length > 0)
32
25
  lines.push(`Also in the user store: ${otherStored.join(", ")}. humanish does not use ${otherStored.length === 1 ? "it" : "them"} today.`);
33
26
  const missing = rows.some((row) => row.source === null);
@@ -5,7 +5,7 @@ import { createHash } from "node:crypto";
5
5
  import { constants as fsConstants } from "node:fs";
6
6
  import { lstat, open, realpath } from "node:fs/promises";
7
7
  import path from "node:path";
8
- import { readOpenedAtMost } from "../run/evidence-files.js";
8
+ import { readOpenedAtMost } from "../run/contained-output.js";
9
9
  import { isPathInside, isSafeRunIdSegment } from "../run/paths.js";
10
10
  /**
11
11
  * The file's bytes, or null when it cannot be read under the root's checks. With `maxBytes`, a
@@ -11,7 +11,7 @@ import { projectShareCheckedAnalysis } from "../analysis/sharing.js";
11
11
  import { loadAnalysis } from "../analysis/load.js";
12
12
  import { physicalCwdOf, runIdOf, bindExistingRunArtifactPaths, isSafeRunIdSegment, resolveLatestRunDirectory, validatePreparedRunArtifactPaths, } from "../run/paths.js";
13
13
  import { writeContainedOutputFile } from "../run/contained-output.js";
14
- import { loadRunBundlePrepared, readRunJsonIfExists } from "../run/locate.js";
14
+ import { loadRunBundlePrepared, readRunJsonIfExists, runJsonValue } from "../run/locate.js";
15
15
  import { readRunNotes } from "../run/note-files.js";
16
16
  import { RUN_STATUS_FILE } from "../run/status.js";
17
17
  import { verifyRunPrepared } from "../verify/verify.js";
@@ -106,8 +106,9 @@ export async function renderObserver(cwdInput, runInput, options = {}) {
106
106
  };
107
107
  // A run.json without an outcome is in progress, interrupted, or older than the field: its
108
108
  // status.json says which, and holds an older run's ok.
109
+ // A status.json it refuses says nothing, as a missing one does: the bundle's own facts show.
109
110
  const statusRecord = loaded.bundle.outcome === undefined
110
- ? await readRunJsonIfExists(preparedRunPaths, RUN_STATUS_FILE)
111
+ ? runJsonValue(await readRunJsonIfExists(preparedRunPaths, RUN_STATUS_FILE))
111
112
  : undefined;
112
113
  const observerData = buildObserverData(loaded.bundle, undefined, statusRecord);
113
114
  observerData.publicSafety.share = {