harness-gui
v0.4.1
Published
A bridge between your program and a human — notify, show, confirm, select, form. Terminal, browser, or a native window.
Maintainers
Readme
harness-gui
A bridge between your program and a human.
Automation eventually hits a moment that needs a person — look at this, approve that, type
the code that shouldn't pass through a model. harness-gui owns that moment.
Five interactions, one protocol, several channels. Your code doesn't know or care whether the human answered in a terminal, a browser tab, or a native window.
Zero runtime dependencies.
npm i harness-guiimport { createInteract } from 'harness-gui'
const ui = createInteract()
await ui.notify({ title: 'Deploy finished', message: 'v2.4.0 is live' })
const r = await ui.confirm({
title: 'Drop 12 rows?',
message: 'Soft delete — recoverable from history.',
danger: true,
})
if (r.action !== 'accept') return
const form = await ui.form({
title: 'Sign in',
fields: [
{ name: 'code', label: 'Verification code', type: 'password', required: true },
],
})
// form.value.code never went through your LLM's contextOutcomes
Every interaction resolves to one of four actions — there is no throw-on-decline:
| action | Means |
|---|---|
| accept | The human answered. value is populated for select / form |
| cancel | The human declined, or closed the surface |
| timeout | Nobody answered within timeoutMs |
| unsupported | No channel could reach a human at all |
timeout and cancel are deliberately distinct: "nobody is there" and "the answer is no"
are different facts, and collapsing them makes failures hard to diagnose.
Approval gate
For tools that an LLM calls, requireApproval is the intended entry point:
import { requireApproval } from 'harness-gui'
await requireApproval({
action: 'delete rows',
title: 'Drop 12 rows?',
message: 'Soft delete — recoverable from history.',
preview: '| id | name |\n|---|---|\n| 41 | ACME |', // markdown, or a Content object
danger: true,
})
// throws ApprovalDeniedError unless a human said yespreview accepts markdown so attaching the payload is nearly free — "delete 12 rows" and
"delete these 12 rows" are different decisions, and if building the preview is a chore
people skip it.
Already have your own instance? Pass it, so one program doesn't end up with two surfaces:
const ui = createInteract()
await requireApproval({ ..., ui }) // per call
setUi(ui) // or once, process-wideA confirm: true tool parameter is not a guardrail — the model can fill that in itself,
and the error text usually teaches it how. A model can construct a request; it cannot
construct a human's approval.
Gated by HARNESS_GUI: off (default — no interaction at all), on (ask; proceed if no
channel can reach anyone), strict (ask; refuse if no channel can reach anyone). It
defaults to off because a library cannot tell whether anyone is watching, and guessing
wrong in a headless environment means hanging, not degrading.
Channels
Picked automatically by capability; register your own to override.
| Channel | Reaches the human via |
|---|---|
| scripted | canned answers — for tests |
| tty | the terminal |
| web | a loopback HTTP page, one-shot token, closes after |
| daemon | a single shared instance, so parallel callers don't each open a window |
The daemon can host the page in a native window instead of your browser, buying system
notifications, a tray presence, and surviving a window close. Point
HARNESS_GUI_APP=/path/to/Interact.app at a shell to enable it; without one, the browser
is used and the reason is logged.
Language
The library's own chrome (buttons, terminal prompts, fallback reasons) is English by
default; en and zh are built in. Your titles, messages and labels pass through
untouched.
createInteract({ locale: 'zh' }) // per instance
setLocale('zh') // per process
createInteract({ locale: { confirm: 'Ship it' } }) // partial override, rest stays EnglishHARNESS_GUI_LOCALE=zh works too. It does not follow LANG — a default has to be
predictable. Errors and daemon logs are always English so they stay searchable.
Also in this project
@harness-gui/mcp— the same five interactions as MCP tools, for agents and LLM hosts
Architecture, design rules, platform matrix and the inbound-invocation design: https://github.com/Morphicai/harness-gui
License
MIT
