hey-cli
AGENTS.md423 starsChanged 8 months agoMITView on GitHub
hey-cli is built to be driven by an agent as readily as by a person: every command that returns data answers --json, the exit codes are stable (hey help exit-codes), and hey commands --json describes the whole surface. This page covers the two integrations that ship with it: an agent skill for Claude Code and Codex, and an MCP server. hey-cli ships with an embedded agent skill so your coding agent can work with HEY on your behalf, and a…
- Reads credentials
In this repository3 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .claude/CLAUDE.mdCLAUDE.md
- heySkill
The file
Files
Coding agents and MCP
hey-cli is built to be driven by an agent as readily as by a person: every command that returns
data answers --json, the exit codes are stable (hey help exit-codes), and hey commands --json
describes the whole surface. This page covers the two integrations that ship with it: an
agent skill for Claude Code and Codex, and an MCP server.
Agent skill and Claude Code plugin
hey-cli ships with an embedded agent skill so your coding agent can work with HEY on your
behalf, and a Claude Code plugin (hey@37signals from the basecamp/claude-plugins
marketplace). The setup wizard connects every detected agent automatically; these commands
manage the integrations on their own:
hey setup claude # install the skill and the hey@37signals plugin for Claude Code
hey setup codex # install the shared skill for Codex
hey skill install # install the skill only (~/.agents/skills/hey, linked for detected agents)
hey setup agents # non-interactive: skill + a single detected agent (the installer uses this)
hey setup agents --remove # remove HEY's managed skills and Claude Code plugin
hey doctor # check skill and plugin health per detected agenthey setup agents never prompts and never guesses: with several agents detected it installs
the skill only and lists the hey setup <agent> choices. HEY_SETUP_AGENT=claude|codex|all|none
picks explicitly. HEY_NONINTERACTIVE=1 disables interactive sign-in for harnesses that
run hey under a pseudo-terminal. The installed skill is refreshed automatically the first
time a new hey release runs.
Codex discovers the shared ~/.agents/skills/hey skill directly. hey-cli does
not also copy it to ~/.codex/skills/hey, which would make Codex list the same
skill twice. Setup and version refresh remove that legacy path only when its
ownership marker proves an older hey-cli created it; user-authored files are
left untouched.
hey only ever writes skill directories it owns: each one it creates carries a
.managed-by-hey-cli marker, and install, replacement and automatic refresh all refuse a
hey skill directory (or symlink) without it — a hand-authored skill at one of those paths
is never overwritten or claimed. hey doctor flags an unmanaged baseline and how to adopt it.
Keychain access from agents
hey keeps your login in the system keyring, which on a Mac is the Keychain. Two things can keep an agent's commands from reading it, and signing in again fixes neither:
- Codex's sandbox hides the keyring from the commands it sandboxes, so a saved login
reads as missing there; hey's hint says so when
CODEX_SANDBOXis set. Let Codex run hey outside its sandbox: approve its escalation prompt, or allow read-only hey commands withprefix_rules in a file under~/.codex/rules/. - A process outside your Mac's desktop session, such as one started over SSH, is refused
by the Keychain: hey fails with
exit status 36(errSecInteractionNotAllowed) and a hint that a new login won't help. Codex runs its commands and MCP servers from a background server that keeps running after you quit Codex. If that server was started from such a session, every hey command andhey mcpfails this way, sandboxed or not, and a rules file can't help. Quit Codex, runcodex app-server daemon restartin Terminal on the Mac, and start Codex again;codex --no-daemonruns without the background server.
hey mcp reads the login before it answers the MCP handshake, so a client that can't use
it reports only that the connection closed. The reason is on the server's stderr, which
Codex keeps in its log.
MCP server
hey mcp runs an MCP (Model Context Protocol) server on stdin/stdout, serving HEY
boxes, search, threads, contacts, todos, calendars, and your identity as tools.
Sign it in once, then register it with any MCP client as a stdio server:
hey auth login --mcp # hey mcp's own sign-in (read, write and send)
claude mcp add hey -- hey mcp # Claude Code
hey mcp --read-only # serve only read-only actions
hey mcp --domains boxes,search # narrow the served tool surfaceIts own sign-in
hey mcp doesn't use the CLI's credential. hey auth login --mcp signs it in as its own
app, "HEY MCP (local)" in HEY's Connected Apps, asking to read, write and send but nothing
more: a model driving hey mcp can't reach your settings, forwarding, trash or HEY World,
which the CLI's own sign-in can. The credential is kept apart from the CLI's, in its own
keychain item (or its own entry in credentials.json when there is no keychain), and it
refreshes as that app. HEY_TOKEN never stands in for it. Without it, hey mcp exits and
says to run hey auth login --mcp; it doesn't fall back to the CLI's credential, even
read-only, because that one holds full access and --read-only is hey mcp's own rule,
not HEY's.
The sign-in uses a device code where HEY offers one (it prints a link and a code to check),
and the browser otherwise. If you uncheck sending on HEY's page, hey mcp saves drafts
but serves no way to send them; sign in again to allow it. Without write it serves only
reads. hey auth status shows both sign-ins, and hey auth logout --mcp revokes hey mcp's
at HEY and removes it, leaving the CLI signed in.
The app's client id is the same in every HEY. HEY_MCP_CLIENT_ID, or mcp_client_ids
in ~/.config/hey-cli/config.json keyed by HEY origin
({"mcp_client_ids": {"https://app.hey.com": "..."}}), overrides it. A repository's
.hey/config.json can't set it.
Each domain is one gateway tool (hey_boxes, hey_search, hey_threads,
hey_contacts, hey_todos, hey_calendar, hey_identity) dispatching actions
derived from the HEY SDK's API model; call an action named describe for any
action's parameter schema. Listings with more pages come back as
{"next_page": cursor, "results": ...} — pass the cursor back as the action's
page parameter. The posting-changes feed's last page comes back as
{"next_since": ..., "next_v": ..., "results": ...} — the cursor for the next
incremental poll, passed back as the action's since and v parameters.
Mutations are never retried automatically: a 429/503 on a write surfaces to
the caller rather than risking a duplicate delivery, so retry a failed write
yourself once you know it did not land. Logs go to stderr — stdout carries
the MCP wire protocol.
Drafts first, sends confirmed
create_message, create_reply and update_message, the three actions that can send
on HEY's API, always save a draft: the entry's status is
forced to drafted, and the entry takes only its recipients (entry.addressed). A
call that asks to send (any other entry.status), schedule (entry.scheduled_delivery
and its date and hour) or triage the thread in the same request (mark_done, pop,
scheduled_bubble_up) is refused before anything reaches HEY. Updating a draft that was
scheduled clears its schedule rather than letting it go out later. A save comes back as
{"drafted": true, "draft_id": ..., "location": ..., "app_url": ..., "note": ...}.
location is HEY's answer, the draft's resource, which in a browser opens its thread;
app_url is the page that opens the draft (/messages/<id>/edit on the Location's
origin), left out when the Location is relative or names anything but a draft. HEY acknowledges a
draft with 204 and a Location. Any other answer is reported as an error that says the
mail may have been sent, so check HEY before trying again. A save that fails with no
answer from HEY (a dropped connection, a timeout, a 5xx) may still have been saved, and
its error says to check Drafts before retrying.
Delivery is one action in a tool of its own: hey_send's send_draft, with the draft's
id. Each call asks you to confirm the email in your MCP client (an MCP elicitation),
showing the sending address, To, Cc and Bcc (a dash when empty), the subject, attachments,
images and the body's first 1,500 characters, with links' targets beside their text and
every control and invisible character escaped so a display name can't forge a line.
Whatever it can't show (the rest of a long body, what attachments hold, images, hidden
parts or formatting the text leaves out) is named on a Not shown here line beside the
draft's link in HEY. What you approve is what goes: the draft is fetched again after you
answer, and if its recipients, sender, subject, body or attachments changed it isn't
sent. Each draft is sent through a hey mcp process once; another attempt in the next 24
hours, even after a failure, is refused, and the send is never retried, so an unclear
outcome says to check Sent.
So when you ask your agent to send a draft, it calls send_draft straight away: the
confirmation is where you check what goes out, and saying yes in the chat doesn't send
anything. You can still have it show or revise the draft first as you like.
hey_send is served only when the sign-in was granted sending, never under --read-only,
and is listed only to an MCP client that can show a confirmation. A client that can't is
told to send each draft from its page in HEY (the draft save's app_url) or with
hey draft send <id>, and a call to send_draft from it answers with that link, as
edit_url, and sends nothing. send_draft also refuses a draft that is already
scheduled, has no recipients, or has a recipient with no address. Nothing
else a draft holds refuses it: how much your agent sends, and to whom, is yours to decide
once you've granted sending.
Earlier releases delivered mail when a call omitted entry.status; these actions now
save drafts only. describe for the draft actions serves the draft-only schema, and the
hey_threads tool description says the same. A person can still send any draft from HEY
or with hey draft send <draft_id>.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

