humanish 0.110.1 → 0.110.2

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 (85) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +8 -7
  4. package/dist/actors/local-agent/cli.d.ts +6 -0
  5. package/dist/actors/local-agent/cli.js +8 -0
  6. package/dist/actors/local-agent/readiness.js +2 -1
  7. package/dist/cli/commands/observe.js +15 -2
  8. package/dist/cli/commands/run-signals.js +13 -4
  9. package/dist/cli/commands/runs.js +36 -13
  10. package/dist/cli/commands/setup.js +23 -14
  11. package/dist/cli/commands/study-route-computer-use.js +7 -3
  12. package/dist/cli/commands/study-route-open.js +1 -0
  13. package/dist/cli/commands/study-route-preview.js +1 -0
  14. package/dist/cli/commands/study-route-shared-world.js +4 -2
  15. package/dist/cli/commands/watch.js +14 -4
  16. package/dist/cli/doctor.js +9 -6
  17. package/dist/cli/findings.js +7 -6
  18. package/dist/cli/first-run-path.d.ts +27 -7
  19. package/dist/cli/first-run-path.js +112 -75
  20. package/dist/cli/invocation.d.ts +7 -0
  21. package/dist/cli/invocation.js +32 -0
  22. package/dist/cli/io.d.ts +3 -1
  23. package/dist/cli/io.js +6 -14
  24. package/dist/cli/observer-follow.d.ts +20 -0
  25. package/dist/cli/observer-follow.js +36 -1
  26. package/dist/cli/orientation.js +16 -14
  27. package/dist/cli/version.d.ts +1 -0
  28. package/dist/cli/version.js +11 -0
  29. package/dist/evidence/encoded-text.d.ts +9 -6
  30. package/dist/evidence/encoded-text.js +84 -19
  31. package/dist/evidence/redaction.d.ts +2 -0
  32. package/dist/evidence/redaction.js +123 -21
  33. package/dist/feedback/export-bundle.js +2 -2
  34. package/dist/keys/key-resolution.js +6 -5
  35. package/dist/observer/exposure.js +1 -1
  36. package/dist/observer/library.js +3 -2
  37. package/dist/observer/render.js +2 -1
  38. package/dist/observer/run-routes.js +8 -1
  39. package/dist/observer/serve.js +2 -1
  40. package/dist/routes/computer-use/plan.js +5 -0
  41. package/dist/routes/shared-world/plan.js +5 -1
  42. package/dist/routes/terminal/bundle.js +2 -1
  43. package/dist/routes/terminal/credentials.d.ts +5 -5
  44. package/dist/routes/terminal/credentials.js +14 -6
  45. package/dist/routes/terminal/plan.js +8 -0
  46. package/dist/routes/terminal/route.js +5 -4
  47. package/dist/routes/terminal/runtime-auth.d.ts +6 -0
  48. package/dist/routes/terminal/runtime-auth.js +5 -0
  49. package/dist/run/dry-run.js +2 -1
  50. package/dist/run/judge.js +2 -1
  51. package/dist/run/local-only-files.d.ts +13 -0
  52. package/dist/run/local-only-files.js +35 -0
  53. package/dist/run/reclaim.d.ts +5 -0
  54. package/dist/run/reclaim.js +34 -2
  55. package/dist/run/run-not-found.js +3 -2
  56. package/dist/run/sandbox-creates.js +5 -3
  57. package/dist/run/sandbox-receipts.d.ts +6 -5
  58. package/dist/run/sandbox-receipts.js +12 -7
  59. package/dist/study/agents-section.d.ts +21 -0
  60. package/dist/study/agents-section.js +86 -0
  61. package/dist/study/doctor.js +5 -4
  62. package/dist/study/init-templates.js +0 -95
  63. package/dist/study/init.js +31 -34
  64. package/dist/study/local-browser-host.d.ts +11 -0
  65. package/dist/study/local-browser-host.js +42 -0
  66. package/dist/study/parse/actors.js +9 -1
  67. package/dist/study/parse/subject.js +12 -0
  68. package/dist/study/parse/url-credentials.d.ts +9 -0
  69. package/dist/study/parse/url-credentials.js +50 -0
  70. package/dist/study/preflight-probes.js +2 -1
  71. package/dist/study/types.d.ts +8 -6
  72. package/dist/study/url-credentials.d.ts +6 -0
  73. package/dist/study/url-credentials.js +34 -0
  74. package/dist/substrates/e2b/sandbox.js +33 -15
  75. package/dist/substrates/local/firecracker-desktop.js +3 -2
  76. package/dist/substrates/local/runtime-host.js +5 -4
  77. package/dist/substrates/local/runtime.js +5 -4
  78. package/dist/tui/contract.js +5 -4
  79. package/dist/verify/liveness.js +2 -1
  80. package/dist/verify/verify.js +2 -1
  81. package/docs/architecture/terminal-product-route.md +14 -12
  82. package/docs/contracts/run-bundle.md +20 -4
  83. package/docs/contracts/schemas.md +6 -5
  84. package/package.json +4 -2
  85. package/skills/humanish/SKILL.md +22 -18
@@ -9,6 +9,7 @@ import { ownDesktopAllocation, } from "../desktop-session.js";
9
9
  import { runtimeDocker, runtimeExec, usesLima } from "./runtime-host.js";
10
10
  import { openLimaTunnel } from "./runtime-ssh.js";
11
11
  import { guestMediaConfigSchema } from "../../guest/media-config.js";
12
+ import { cli } from "../../cli/invocation.js";
12
13
  const docker = async (args) => (await runtimeDocker(args, {}, 60_000)).stdout.trim();
13
14
  const readLogs = async (id) => {
14
15
  const result = await runtimeDocker(["logs", "--tail", "100", id], {}, 10_000);
@@ -18,12 +19,12 @@ function checkDesktopOptions(options) {
18
19
  if (options.media !== undefined) {
19
20
  guestMediaConfigSchema.parse(options.media);
20
21
  if (options.assets.media !== true)
21
- throw new Error("This local runtime does not include media. Run humanish runtime setup --media.");
22
+ throw new Error(`This local runtime does not include media. Run ${cli("runtime setup --media")}.`);
22
23
  }
23
24
  if (options.recording !== undefined) {
24
25
  desktopRecordingConfigSchema.parse(options.recording);
25
26
  if (options.assets.media !== true)
26
- throw new Error("This runtime does not include the recorder. Run humanish runtime setup --media.");
27
+ throw new Error(`This runtime does not include the recorder. Run ${cli("runtime setup --media")}.`);
27
28
  }
28
29
  let url;
29
30
  try {
@@ -3,6 +3,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import path from "node:path";
5
5
  import { promisify } from "node:util";
6
+ import { cli } from "../../cli/invocation.js";
6
7
  const exec = promisify(execFile);
7
8
  export const LIMA_INSTANCE = "humanish-runtime";
8
9
  export const usesLima = (options = {}) => (options.platform ?? process.platform) === "darwin";
@@ -86,11 +87,11 @@ export async function limaStatus(options = {}) {
86
87
  version = (await hostExec("limactl", ["--version"], options)).stdout;
87
88
  }
88
89
  catch {
89
- throw new Error("Install Lima 2.2 or newer (brew install lima), then run humanish runtime setup. Docker Desktop is not required.");
90
+ throw new Error(`Install Lima 2.2 or newer (brew install lima), then run ${cli("runtime setup")}. Docker Desktop is not required.`);
90
91
  }
91
92
  const match = /\b(\d+)\.(\d+)\.\d+/.exec(version);
92
93
  if (!match || Number(match[1]) < 2 || (Number(match[1]) === 2 && Number(match[2]) < 2))
93
- throw new Error("Update Lima to 2.2 or newer (brew upgrade lima), then run humanish runtime setup.");
94
+ throw new Error(`Update Lima to 2.2 or newer (brew upgrade lima), then run ${cli("runtime setup")}.`);
94
95
  const output = (await hostExec("limactl", ["list", "--json"], options)).stdout.trim();
95
96
  const instance = output
96
97
  .split("\n")
@@ -104,8 +105,8 @@ export async function limaStatus(options = {}) {
104
105
  ready: instance?.status === "Running",
105
106
  exists: !!instance,
106
107
  message: instance
107
- ? "The humanish Lima host is stopped. Run humanish runtime setup to start it."
108
- : "Run humanish runtime setup to create the humanish Lima host and install the browser runtime.",
108
+ ? `The humanish Lima host is stopped. Run ${cli("runtime setup")} to start it.`
109
+ : `Run ${cli("runtime setup")} to create the humanish Lima host and install the browser runtime.`,
109
110
  };
110
111
  }
111
112
  export async function prepareLima(options = {}, progress) {
@@ -7,6 +7,7 @@ import { Readable, Transform } from "node:stream";
7
7
  import { pipeline } from "node:stream/promises";
8
8
  import { LOCAL_MEDIA_RUNTIME_RELEASES, LOCAL_RUNTIME_RELEASES } from "./runtime-release.js";
9
9
  import { limaStatus, loadRuntimeArchive, prepareLima, runtimeArchitecture, runtimeDocker, runtimeExec, usesLima, } from "./runtime-host.js";
10
+ import { cli } from "../../cli/invocation.js";
10
11
  const docker = async (args, options) => (await runtimeDocker(args, options)).stdout.trim();
11
12
  const runtimeRelease = (options) => options.release ??
12
13
  (options.media ? LOCAL_MEDIA_RUNTIME_RELEASES : LOCAL_RUNTIME_RELEASES)[runtimeArchitecture(options) ?? "amd64"];
@@ -80,7 +81,7 @@ export async function localRuntimeStatus(options = {}) {
80
81
  installed: false,
81
82
  message: lima
82
83
  ? "Docker is unavailable inside the humanish Lima host. Check limactl shell humanish-runtime -- sudo systemctl status docker; Docker Desktop is not used."
83
- : "Docker is unavailable. Install/start Docker Engine and give your account access, then run humanish runtime setup.",
84
+ : `Docker is unavailable. Install/start Docker Engine and give your account access, then run ${cli("runtime setup")}.`,
84
85
  };
85
86
  }
86
87
  const release = runtimeRelease(options);
@@ -108,7 +109,7 @@ export async function localRuntimeStatus(options = {}) {
108
109
  : {
109
110
  ok: true,
110
111
  installed: false,
111
- message: "Local runtime will download before the first live run. Run humanish runtime setup to prepare it now.",
112
+ message: `Local runtime will download before the first live run. Run ${cli("runtime setup")} to prepare it now.`,
112
113
  };
113
114
  }
114
115
  const image = images[0];
@@ -161,7 +162,7 @@ export async function prepareLocalRuntime(options = {}) {
161
162
  try {
162
163
  const response = await fetch(release.url, { signal });
163
164
  if (!response.ok || !response.body)
164
- throw new Error(`Runtime download failed (HTTP ${response.status}). Run humanish runtime setup to retry.`);
165
+ throw new Error(`Runtime download failed (HTTP ${response.status}). Run ${cli("runtime setup")} to retry.`);
165
166
  let bytes = 0;
166
167
  const hash = createHash("sha256");
167
168
  await pipeline(Readable.fromWeb(response.body), new Transform({
@@ -181,7 +182,7 @@ export async function prepareLocalRuntime(options = {}) {
181
182
  await loadRuntimeArchive(archive, { ...options, signal });
182
183
  const after = await localRuntimeStatus(options);
183
184
  if (!after.assets)
184
- throw new Error("Downloaded runtime did not provide the expected image. Run humanish runtime setup to retry.");
185
+ throw new Error(`Downloaded runtime did not provide the expected image. Run ${cli("runtime setup")} to retry.`);
185
186
  return after.assets;
186
187
  }
187
188
  finally {
@@ -12,6 +12,7 @@
12
12
  //
13
13
  // It also makes the UI testable without a terminal: a test hands `startTui` a fake index and reads
14
14
  // the frames back.
15
+ import { cli } from "../cli/invocation.js";
15
16
  /** Node version the Ink runtime requires (ink@7 declares `engines.node >= 22`). */
16
17
  export const TUI_MIN_NODE_MAJOR = 22;
17
18
  /**
@@ -41,12 +42,12 @@ export const TUI_BUNDLE_URL = new URL("../tui-app.js", import.meta.url);
41
42
  */
42
43
  export function terminalSurfaceMessage(state) {
43
44
  if (!state.supported) {
44
- return `\`humanish tui\` needs Node ${TUI_MIN_NODE_MAJOR}+ (this is ${state.nodeVersion}); every other command works here`;
45
+ return `\`${cli("tui")}\` needs Node ${TUI_MIN_NODE_MAJOR}+ (this is ${state.nodeVersion}); every other command works here`;
45
46
  }
46
47
  if (!state.bundlePresent) {
47
- return "`humanish tui` is not built in this checkout; run `pnpm build` (installed packages ship it prebuilt)";
48
+ return `\`${cli("tui")}\` is not built in this checkout; run \`pnpm build\` (installed packages ship it prebuilt)`;
48
49
  }
49
50
  return state.interactive
50
- ? "`humanish tui` opens the interactive surface for browsing studies and runs"
51
- : "`humanish tui` is the interactive screen a person uses to watch and start runs here. This shell is not a terminal, so pass it on to a person at one";
51
+ ? `\`${cli("tui")}\` opens the interactive surface for browsing studies and runs`
52
+ : `\`${cli("tui")}\` is the interactive screen a person uses to watch and start runs here. This shell is not a terminal, so pass it on to a person at one`;
52
53
  }
@@ -11,6 +11,7 @@ import { runLiveness } from "../run/run-index.js";
11
11
  import { parseSandboxReceipts, SANDBOX_RECEIPTS_ARTIFACT } from "../run/sandbox-receipts.js";
12
12
  import { RUN_STATUS_FILE } from "../run/status.js";
13
13
  import { plural } from "../run/text.js";
14
+ import { cli } from "../cli/invocation.js";
14
15
  /** The stable code every RUN_NOT_FINISHED warning starts with. */
15
16
  const RUN_NOT_FINISHED = "RUN_NOT_FINISHED";
16
17
  const GONE_STATES = new Set(["killed", "already-gone"]);
@@ -55,7 +56,7 @@ async function reclaimState(runPaths, runId) {
55
56
  // Matched by digest: a reclaim receipt names each sandbox by digest, and one written before it
56
57
  // did holds the raw id, which is digested here.
57
58
  const journaled = new Set((journal === null ? [] : parseSandboxReceipts(journal)).map((receipt) => sandboxIdDigest(receipt.sandboxId)));
58
- const command = `\`humanish reclaim --run ${runId}\``;
59
+ const command = `\`${cli(`reclaim --run ${runId}`)}\``;
59
60
  const receipt = reclaimReceipt(await readRunJsonIfExists(runPaths, RECLAIM_RECEIPT_ARTIFACT), runId);
60
61
  if (receipt === undefined)
61
62
  return {
@@ -19,6 +19,7 @@ import { rerunLineageFindings } from "./rerun.js";
19
19
  import { sharedWorldEvidenceFindings } from "./shared-world.js";
20
20
  import { subjectStateFindings, undeclaredSubjectStateWarnings } from "./subject.js";
21
21
  import { plural } from "../run/text.js";
22
+ import { cli } from "../cli/invocation.js";
22
23
  export const VERIFY_SCHEMA = "humanish.verify-result.v1";
23
24
  // The UNSCANNED_ARTIFACT reason names this many paths and counts the rest.
24
25
  const MAX_LISTED_UNSCANNED = 10;
@@ -387,7 +388,7 @@ function buildShareSafety(args) {
387
388
  ];
388
389
  reasons.push({
389
390
  code: "RAW_SANDBOX_ID",
390
- message: `Raw sandbox ids appear in ${files.sort().join(", ")}. Runs from 0.110 keep them only in sandbox-receipts.ndjson; \`humanish export --format bundle --redact-screenshots\` writes a copy without them.`,
391
+ message: `Raw sandbox ids appear in ${files.sort().join(", ")}. Runs from 0.110 keep them only in sandbox-receipts.ndjson; \`${cli("export --format bundle --redact-screenshots")}\` writes a copy without them.`,
391
392
  });
392
393
  }
393
394
  const rawStreamIds = rawScreenshotStreamIds(args.bundle, args.redactedShapeFrames);
@@ -1,7 +1,7 @@
1
1
  # Terminal-product route
2
2
 
3
3
  The live terminal-product route has shipped since `0.8.0`, with the in-sandbox runtime,
4
- command-scoped credential placement, exact-id cleanup proof, an interventions ledger, a cost and
4
+ explicit credential placement (an E2B egress proxy by default, or command-scoped), exact-id cleanup proof, an interventions ledger, a cost and
5
5
  no-spend ledger, caps, and product scoring and feedback hooks. The
6
6
  [original plan](https://github.com/danielgwilson/humanish/blob/main/docs/history/goals/terminal-product-lane/goal.md)
7
7
  holds the build order and the safety contract.
@@ -30,7 +30,7 @@ fail-closed cross-validation, and forward-declared warnings.
30
30
  | `subject.product` | `{ name, publicSurfaces[], install?, workdir?, upload? }`: the only world the agent sees |
31
31
  | `execution.target` | `e2b-terminal` (or absent → implied) |
32
32
  | `execution.terminal` | `{ transport: exec-stream, stdin: disabled }` |
33
- | `execution.runtimeAuth` | `openai-env` (default) or opt-in `openai-egress`; names-only durable evidence |
33
+ | `execution.runtimeAuth` | `openai-egress` (default) or opt-in `openai-env`; names-only durable evidence |
34
34
  | `execution.runtime.version` | Optional exact `@openai/codex` version; observed before keyed execution |
35
35
  | `actor.model` / `reasoningEffort` | Passed to Codex as `--model` (default `gpt-5.6-sol`) and `-c model_reasoning_effort`; declarations, not observed provider identity |
36
36
  | `caps` | `{ maxUsd, maxJobs, maxMinutes }`: the blast-radius budget; `maxUsd > 0` is refused: no adopter cost source exists until issue 347 lands |
@@ -86,11 +86,8 @@ product's no-spend boundary still needs separate interpretation.
86
86
 
87
87
  ## Runtime auth: raw-key placement and remaining provider access
88
88
 
89
- `execution.runtimeAuth: openai-env` remains the compatible default. It supplies
90
- `CODEX_API_KEY` command-scoped to Codex, with `OPENAI_API_KEY` also supplied when
91
- that was the host source. Child processes can read and use the raw key.
92
-
93
- Opt in to keeping the raw key outside the sandbox:
89
+ `execution.runtimeAuth: openai-egress` is the default: a study that declares no
90
+ `runtimeAuth` keeps the raw key outside the sandbox, as if it declared:
94
91
 
95
92
  ```yaml
96
93
  execution:
@@ -115,7 +112,7 @@ output and errors, including errors during sandbox creation.
115
112
  This mode supports the default OpenAI endpoint only. humanish explicitly sets Codex's
116
113
  built-in `openai` provider and `openai_base_url` to `https://api.openai.com/v1` for
117
114
  that invocation. It does not support a custom provider, proxy base URL, or regional
118
- endpoint under this mode. `openai-env` retains its existing command behavior. E2B
115
+ endpoint under this mode; a study that needs one declares `openai-env`. E2B
119
116
  header rules are a public-beta capability;
120
117
  `tests/routes/terminal/runtime-auth.test.ts` checks the local contract against the
121
118
  installed Desktop SDK (the lockfile has `@e2b/desktop` 2.4.0, resolving `e2b` 2.49.0).
@@ -136,6 +133,11 @@ requests can fail. E2B domain allowlists are routing controls rather than strict
136
133
  destination isolation on shared infrastructure. An existing exact OpenAI host
137
134
  rule is rejected instead of silently overwritten.
138
135
 
136
+ `execution.runtimeAuth: openai-env` is the opt-in alternative. It supplies
137
+ `CODEX_API_KEY` command-scoped to Codex, with `OPENAI_API_KEY` also supplied when
138
+ that was the host source. Child processes can read and use the raw key, and so can
139
+ anything the agent runs or a page it reads asks it to run.
140
+
139
141
  Evidence records the selected auth mode and the residual proxy capability.
140
142
  Resolved live actor traces use `keyPlacement: external` in `openai-egress`; the
141
143
  actor registry continues to describe the default `in-sandbox-command-scoped`
@@ -197,10 +199,10 @@ deadline.
197
199
 
198
200
  ## The original command-scoped safety contract
199
201
 
200
- The default mode **inverts** the credential-placement default of every other E2B route.
201
- On the computer-use route the model's key stays _outside_ the sandbox; here the
202
- agent-under-test runs _inside_ with a real `OPENAI_API_KEY`/`CODEX_API_KEY` and
203
- is **presumed exfiltratable**. The placement rule in invariants-and-defaults.md
202
+ The `openai-env` mode **inverts** the credential-placement default of every other E2B route.
203
+ On the computer-use route the model's key stays _outside_ the sandbox; under `openai-env` the
204
+ agent-under-test runs _inside_ with a real `OPENAI_API_KEY`/`CODEX_API_KEY` and is **presumed
205
+ exfiltratable**. The placement rule in invariants-and-defaults.md
204
206
  applies: _keys live where the keyed process runs, and nowhere else;
205
207
  blast radius is bounded by key scoping and budgets, not by hoping._
206
208
 
@@ -585,7 +585,9 @@ shareSafety:
585
585
  bundle is safe to promote into a public issue. Public promotion should branch on
586
586
  `shareSafety.status`:
587
587
 
588
- - `share_ready`: feedback draft commands may render public issue payloads;
588
+ - `share_ready`: feedback draft commands may render public issue payloads, and `observe --all --safe`
589
+ serves the run. The folder still holds `sandbox-receipts.ndjson` (raw sandbox ids) and `status.json`
590
+ (the recording pid); no Observer server serves them and bundle export omits them;
589
591
  - `local_only`: keep the run local; only supported redaction-only cases can produce a shareable derivative;
590
592
  `RAW_SANDBOX_ID` is one: a file other than `sandbox-receipts.ndjson` names one of the run's raw
591
593
  sandbox ids, which verify reads from the receipts and from any raw `providerResources[].id` in
@@ -658,6 +660,16 @@ trace references outside `screenshots/`. An unregistered
658
660
  `.mp4`, and a file or directory whose name contains `\`, are public-safety
659
661
  findings and block the run.
660
662
 
663
+ The patterns live in `src/evidence/redaction.ts`: provider key formats (OpenAI,
664
+ Anthropic, E2B, GitHub, GitLab, npm, Vercel, AWS, Google, Stripe, Slack,
665
+ SendGrid, Hugging Face), JWTs, bearer and basic `Authorization` values, private
666
+ key headers, database URLs with a password, the password in any URL's user info,
667
+ credential query and fragment parameters with a value of 16 characters or more,
668
+ credential-named upper-case variables set to a value of 16 characters or more
669
+ with a digit and a letter, an AWS secret access key next to its name, E2B URLs, and Linux,
670
+ macOS and Windows local paths. `tests/verify/secret-formats.test.ts` lists the
671
+ formats and the ordinary values that must not match.
672
+
661
673
  Before matching, the scan undoes JSON and JS escapes, percent-encoding and HTML
662
674
  character references, the decoding bundle export applies (`decodeEscapes`), and
663
675
  then JSON whitespace escapes and quoted-printable. It reads inside base64 runs of
@@ -673,9 +685,13 @@ wrapped across lines, and inside hex runs of 32 characters or more:
673
685
 
674
686
  `observer/index.html` is exempt from that base64 rule: serve renders it from
675
687
  `run.json`, export regenerates it, and it embeds the Observer's own base64 fonts.
676
- Its text is still scanned. Known limits: base64 split across separate strings,
677
- nesting deeper than three levels, and encodings a pattern scan cannot undo (such
678
- as encryption) still grade `share_ready`. `tests/verify/encoding-coverage.test.ts`
688
+ Its text is still scanned. A base64 run of up to 4096 characters is also read one
689
+ slash-separated piece at a time, and from just after each slash in its first 64
690
+ characters, so a URL path before an encoded key cannot shift its decoding. Known
691
+ limits: base64 split across separate strings, nesting deeper than three levels,
692
+ base64 that holds a slash itself and starts more than 64 characters into a longer
693
+ run, and encodings a pattern scan cannot undo (such as encryption) still grade
694
+ `share_ready`. `tests/verify/encoding-coverage.test.ts`
679
695
  records each case.
680
696
 
681
697
  Real email receiving adds `publication.restrictions: [real-communications]` and
@@ -279,7 +279,7 @@ instruction?, target?, entry?, host?, reasoningEffort?, stopWhen?, dwell? }` bec
279
279
  an interactive PTY would overstate the mechanism (a duplex PTY
280
280
  transport does not ship). `terminal.stdin` defaults to `disabled`
281
281
  (`sent`/assisted input is rejected until the interventions ledger + a
282
- non-comparable marker exist). `runtimeAuth` (`openai-env`, the default, or `openai-egress`)
282
+ non-comparable marker exist). `runtimeAuth` (`openai-egress`, the default, or `openai-env`)
283
283
  declares the agent's runtime-auth channel, recorded as names only. On a live run, the engine
284
284
  resolves the registered terminal descriptor and requires
285
285
  `keyPlacement: in-sandbox-command-scoped` before creating a sandbox. With `openai-env`
@@ -1068,9 +1068,9 @@ contract.
1068
1068
  ## Terminal Cost Ledger And No-Spend Proof
1069
1069
 
1070
1070
  The terminal-product route (`src/routes/terminal/route.ts`), under the default
1071
- `runtimeAuth: openai-env`, passes a real provider key only to the in-sandbox agent
1072
- command; `openai-egress` gives the command a placeholder and keeps the key in an
1073
- external E2B header transform. Neither mode puts it in sandbox-global env or
1071
+ `runtimeAuth: openai-egress`, gives the in-sandbox agent command a placeholder and
1072
+ keeps the key in an external E2B header transform; `openai-env` passes a real
1073
+ provider key only to that command. Neither mode puts it in sandbox-global env or
1074
1074
  metadata, so the no-spend claim must be derived from a ledger, never asserted. The
1075
1075
  live run writes both to `terminal-ledgers.json` (a `cost` block + a
1076
1076
  `noSpendProof` block, additive to `humanish.terminal-ledgers.v1`).
@@ -1186,7 +1186,8 @@ the CLI's run command when that signal stops a live run outside post-run
1186
1186
  analysis. It first prints one line saying it is stopping and refuses any further sandbox
1187
1187
  create. It then kills the E2B sandboxes the run's `sandbox-receipts.ndjson` names, each sandbox
1188
1188
  a create in flight reports as its id arrives, and each one E2B lists with the run's owner tags,
1189
- writes `reclaim-receipt.json` as `humanish reclaim` does, and exits
1189
+ writes `reclaim-receipt.json` as `humanish reclaim` does, rewrites each run file that names one of
1190
+ the run's raw sandbox ids with the marker and digest, as a finished run's publish does, and exits
1190
1191
  128+n. The bundle is not finished: run.json keeps its last live flush, with
1191
1192
  `outcome: { state: interrupted, ok: false, signal, at }` when the run had
1192
1193
  flushed one, and status.json gets no `outcome`. A `running` record that stopped refreshing still reads as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.110.1",
3
+ "version": "0.110.2",
4
4
  "description": "Synthetic user research for apps, CLIs, and agent-facing product flows.",
5
5
  "keywords": [
6
6
  "cli",
@@ -56,11 +56,12 @@
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsc -p tsconfig.build.json && pnpm --filter humanish-observer build && pnpm --filter humanish-tui build && node scripts/finish-build.mjs",
59
- "check": "pnpm format:check && pnpm lint && pnpm knip && pnpm prose:check && pnpm vocabulary:check && pnpm docs:check && pnpm site-css:check && pnpm typecheck && pnpm test && pnpm tui:test && pnpm build && pnpm guest-media:startup:proof && pnpm cli:startup:test && pnpm cli:preflight:test && pnpm cli:dotenv:test && pnpm cli:peer:test && pnpm tui:smoke",
59
+ "check": "pnpm format:check && pnpm lint && pnpm knip && pnpm prose:check && pnpm vocabulary:check && pnpm docs:check && pnpm site-css:check && pnpm typecheck && pnpm test && pnpm tui:test && pnpm build && pnpm guest-media:startup:proof && pnpm cli:startup:test && pnpm cli:preflight:test && pnpm cli:dotenv:test && pnpm cli:peer:test && pnpm cli:skill:test && pnpm tui:smoke",
60
60
  "cli:startup:test": "node scripts/terminal-startup-exit-proof.mjs",
61
61
  "cli:preflight:test": "node scripts/task-route-preflight-proof.mjs",
62
62
  "cli:dotenv:test": "node scripts/dotenv-proof.mjs",
63
63
  "cli:peer:test": "node scripts/peer-install-proof.mjs",
64
+ "cli:skill:test": "node scripts/skill-first-run-proof.mjs",
64
65
  "humanish": "tsx src/cli.ts",
65
66
  "pack:dry-run": "npm pack --dry-run",
66
67
  "prepack": "pnpm build",
@@ -74,6 +75,7 @@
74
75
  "tui:smoke": "node scripts/tui-smoke.mjs",
75
76
  "tui:test": "pnpm --filter humanish-tui test",
76
77
  "release:dogfood": "node scripts/release-dogfood.mjs",
78
+ "bench": "tsx bench/run.ts",
77
79
  "browser-control:proof": "node scripts/browser-control-proof.mjs",
78
80
  "docs:generate": "tsx scripts/generate-cli-docs.ts",
79
81
  "docs:check": "tsx scripts/generate-cli-docs.ts --check && tsx scripts/check-doc-paths.ts && tsx scripts/check-doc-symbols.ts && tsx scripts/check-doc-comments.ts && tsx scripts/check-doc-study-fields.ts",
@@ -289,7 +289,6 @@ Useful commands:
289
289
  ```bash
290
290
  npx humanish study list
291
291
  npx humanish study show first-run
292
- npx humanish watch first-run
293
292
  npx humanish run first-run --json --no-open
294
293
  ```
295
294
 
@@ -377,25 +376,30 @@ inbox page. See `docs/contracts/schemas.md` for the full `comms:` shape and
377
376
  ## First Proof Run
378
377
 
379
378
  Run the no-credentials path first. It is a dry run: humanish writes a synthetic
380
- run bundle and tests no target app behavior:
379
+ run bundle and tests no target app behavior. Every command in this block
380
+ finishes and exits, needs no key and spends nothing; lines marked
381
+ `# live only` need keys and spend money, so run them only when the user has
382
+ approved a live run. `run` prints the run id; use it in place of `<id>`.
381
383
 
382
384
  ```bash
383
- npx humanish doctor
384
- npx humanish watch
385
- npx humanish verify --run latest --json
386
- npx humanish feedback issue --run latest --repo example/app --format markdown
387
- ```
388
-
389
- For CI or non-interactive proof:
390
-
391
- ```bash
392
- npx humanish watch --json --no-open
385
+ npx humanish doctor --json
393
386
  npx humanish run first-run --json --no-open
387
+ npx humanish verify --run <id> --json
388
+ npx humanish review --run <id> --json
389
+ npx humanish observe --run <id>
390
+ npx humanish run try-live --json --no-open # live only
391
+ npx humanish feedback issue --run <id> --repo example/app --format markdown # live only
394
392
  ```
395
393
 
396
- The feedback command prints a public-safe Markdown draft. It must not call the
397
- GitHub API, require a token, update Projects, use provider credits, or claim
398
- product behavior proof from a dry run.
394
+ `observe` prints the path of the run's Observer page for the person you are
395
+ working for. Without an interactive terminal, `observe` and `watch` print the
396
+ result and exit; at a person's terminal they serve the Observer until Ctrl-C.
397
+
398
+ `feedback issue` drafts from a live run only and refuses a dry run with
399
+ `HUMANISH_FEEDBACK_REQUIRES_LIVE_RUN`. Its Markdown draft is checked for
400
+ secret, key, token and local-path shapes; it is not checked for personal data. It
401
+ must not call the GitHub API, require a token, update Projects, use provider
402
+ credits, or claim product behavior proof from a dry run.
399
403
 
400
404
  When the target app can run locally, prove a known browser path with a
401
405
  scripted-browser study after starting the app on loopback. The study replays a
@@ -414,8 +418,8 @@ no browser.
414
418
  # in another terminal, start the target app on 127.0.0.1 or localhost
415
419
  npx humanish study show <study> --json
416
420
  npx humanish run <study> --json --no-open
417
- npx humanish verify --run latest --json
418
- npx humanish observe --run latest --open # for a person; it serves until Ctrl-C
421
+ npx humanish verify --run <id> --json
422
+ npx humanish observe --run <id> --open # for a person; at their terminal it serves until Ctrl-C
419
423
  ```
420
424
 
421
425
  Do not use `humanish watch --count ...` as a substitute for a scripted-browser
@@ -442,7 +446,7 @@ Do not paste values into files, prompts, run bundles, issue drafts, or logs.
442
446
  Load local values only at invocation time:
443
447
 
444
448
  ```bash
445
- npx humanish watch .humanish/studies/local-live.yaml --dotenv .humanish/local/provider.env
449
+ npx humanish run .humanish/studies/local-live.yaml --dotenv .humanish/local/provider.env --json --no-open
446
450
  ```
447
451
 
448
452
  When choosing dogfood targets, prefer apps, CLIs, or agent-facing tools with a