Files
hyperframes/docs/developers/harness-tool-context.mdx
T
Xuanru Li 257bf285ff feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review (#6400)
* feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review

The --skip-contrast-in-motion flag and HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION
env give way to a check-review capability in HF_TOOL_CONTEXT, so only a harness
turns it on. The skill text and manifest go back to main.

* fix(cli): satisfy comment ratchet for check capability
2026-10-26 07:42:50 +00:00

106 lines
4.4 KiB
Plaintext

---
title: "Running the CLI from an agent harness"
description: "How an agent harness declares what it supports through HF_TOOL_CONTEXT, and what the CLI reports back."
---
An agent harness (Claude Code, Codex, your own) runs `hyperframes` commands on the agent's behalf.
`HF_TOOL_CONTEXT` lets the harness tell one invocation what it supports, so the CLI can do more for
it: report the images it produced, or wait its turn instead of racing another command in the same
project. Without the variable, every command behaves exactly as it does in a terminal.
```bash
export HF_TOOL_CONTEXT='{"version":1,"capabilities":{
"events": {"transport": {"type": "ndjson-file", "path": "/tmp/call-42/events.ndjson"}},
"project-lock": {"waitMs": 90000}
}}'
npx hyperframes snapshot --at 1,3
```
The harness sets the variable for one tool call, reads what the call produced, and removes its files.
## The declaration
| Field | Value |
| --- | --- |
| `version` | `1`. Any other version, or a value that is not JSON, is treated as no declaration. |
| `capabilities` | An object keyed by capability name. Each value configures that capability. |
The CLI ignores capability names it does not know, and a known capability whose value is invalid, so
a harness can declare capabilities newer than the CLI it runs. The variable carries data only: no
capability runs code the harness names.
## `events`: what a command produced
```json
{ "transport": { "type": "ndjson-file", "path": "/absolute/events.ndjson" } }
```
The CLI appends one JSON object per line to `path`. It never creates the parent directory, and a
write that fails prints a warning without failing the command. Read the file after the command
exits; several commands in one shell call append to the same file.
Each line is an event:
```json
{
"version": 1,
"type": "artifact.created",
"command": "snapshot",
"artifact": { "role": "contact-sheet", "path": "/project/.hyperframes/artifacts/snapshots/…/contact-sheet.jpg", "mediaType": "image/jpeg" }
}
```
| Command | `role` | What it is |
| --- | --- | --- |
| `snapshot` | `frame` | Each captured frame (PNG) |
| `snapshot` | `contact-sheet` | Each page of the grid of frames made for review (JPEG) |
| `check` | `check-report` | The saved JSON report |
| `check` | `frame` | Each frame the browser check captured |
| `check` | `finding` | Each crop around a finding |
Paths are absolute and stay where the command saved them. What to do with an artifact is the
harness's decision: attach the contact sheet to the tool result, link the report, or ignore it.
Ignore event types and roles you do not know; new ones are added without a version change.
## `project-lock`: one browser command at a time
```json
{ "waitMs": 90000 }
```
`check` and `snapshot` take a lock on the project before they start a browser, and release it when
they finish. A second command waits up to `waitMs` milliseconds for the lock, then fails, naming the
command and process that hold it. A lock whose process has exited is taken over. A
`project.lock.break` left by a killed process is not: the timeout names that file and its process
id, and says to delete it to continue. The lock is the file `.hyperframes/project.lock`.
Choose `waitMs` below your tool call's own timeout, so a wait ends in the CLI's message rather than
the harness killing the call.
## `check-review`: review switches for `check`
```json
{ "skipContrastInMotion": true }
```
Each field is a boolean and defaults to `false`; a value that is not `true` is
off, and unknown fields are ignored. Without the capability, `check` output is
the same as in a terminal.
| Field | What it does |
| --- | --- |
| `skipContrastInMotion` | Leaves out contrast failures measured while a GSAP tween moves the text or an ancestor, or while the text is partly transparent (see [check](/packages/cli#check)). Counts, `ok` and persistence leave them out too. |
## Hooks that run code
Running your own command before or after a CLI command is not a `HF_TOOL_CONTEXT` capability and
never will be: anything that sets an environment variable could then run code. Such hooks, when they
exist, come only from configuration the person controls.
## Conformance
`packages/cli/src/utils/toolContext.test.ts` and `projectLock.test.ts` pin the parsing, event and
lock behavior above. A harness is correct when it sets the variable for each call it wants the
behavior on, reads `events` only after the call exits, and works the same when the CLI ignores a
capability.