hmm is a small terminal-native wrapper around codex exec.
It lets you ask Codex something from your shell, keeps a sticky conversation for the life of the terminal, and renders Codex's JSONL stream as compact terminal output instead of a full coding TUI.
hmm "I can never remember the flags for tar. Make this directory a tarball."
hmm --write "now do it"
hmm "now scp that to my server"The goal is a terminal clippy: unobtrusive, shell-shaped, visually distinct, and able to use Codex tools without becoming a second agent harness.
This repo currently implements a portable shell v1:
- quoted prompt arguments
- stdin, heredoc, and file prompt input
- a small multiline compose prompt for interactive use
- sticky sessions scoped to the current terminal
codex exec --jsonbackend- compact rendering for assistant output, shell tool use, and token usage
- safety presets for read-only, workspace-write, and danger mode
Required:
codexjq- POSIX-ish
/bin/sh
Optional:
tputfor colorsperlfor prettier thousands separators in token countsshasumfor stable session keys, withcksumfallback
tput is optional and broadly deployed on normal macOS/Linux systems through
terminfo/ncurses. If it is missing, or output is not a TTY, hmm falls back to
plain text.
From a cloned checkout:
./install.shOne-line install from the latest GitHub release:
curl -fsSL https://github.com/mwunsch/hmm/releases/latest/download/install.sh | shThis installs:
~/.local/bin/hmm
~/.local/libexec/hmm-*
~/.local/share/man/man1/hmm.1
If ~/.local/bin is not on your PATH, the installer prints the line to add.
Installer environment variables:
HMM_PREFIX install prefix, default: ~/.local
HMM_BIN_DIR override binary directory, default: $HMM_PREFIX/bin
HMM_MAN_DIR override man page directory, default: $HMM_PREFIX/share/man/man1
HMM_REPO=url GitHub-style repo URL, default: https://github.com/mwunsch/hmm
HMM_REF=name branch name for HMM_REPO archives, default: latest release
HMM_TARBALL_URL explicit source archive URL for curl installs
To pin an install to a specific release archive:
curl -fsSL https://raw.githubusercontent.com/mwunsch/hmm/main/install.sh \
| HMM_TARBALL_URL=https://github.com/mwunsch/hmm/releases/download/v0.2.0/hmm.tar.gz shFor development installs from a branch:
curl -fsSL https://raw.githubusercontent.com/mwunsch/hmm/main/install.sh \
| HMM_REF=main shUse quoted strings for simple prompts:
hmm "how do I flush DNS cache on macOS?"
hmm "what does docker COPY do differently from ADD?"
hmm --write "create a tarball of this directory"
hmm --new "explain rsync include and exclude rules"Use stdin or a heredoc for punctuation-heavy or multiline prompts:
hmm <<'EOF'
Hey, here I can type whatever I'd like, right?
Can you explain `find . -name "*.log" -mtime +7 -delete`?
EOFUse --file / -f for durable prompts:
hmm --file prompt.md
hmm -f prompt.mdUse - to read the prompt from stdin explicitly:
hmm --file - < prompt.mdUse --host / --ssh to inspect a remote machine through local SSH:
hmm --host m@my-nas "can you list out what's in my downloads dir?"
hmm --ssh prod "what is using the most disk?"Remote mode still runs hmm, Codex, and jq locally. The remote host only
needs working SSH access. Codex is instructed to use commands like
ssh -- m@my-nas '<remote command>' and to treat paths, processes, logs,
services, disks, and packages as remote unless you say otherwise.
By default, remote mode asks Codex to use read-only inspection commands. Passing
--write allows remote changes when needed; passing --danger disables Codex
approvals and sandboxing locally and allows clearly requested destructive remote
commands.
Because SSH needs outbound network access, --host runs Codex shell commands
with Codex's workspace-write sandbox plus network access enabled. The remote
machine is still treated as read-only by default through the prompt instructions;
use --write when you want to allow remote changes.
If no prompt arguments are provided and stdin is a TTY, hmm opens a small
in-terminal compose prompt:
ββ hmm compose
β Type or paste your prompt. Enter adds a line. Ctrl-D sends. Ctrl-C cancels.
β
β first line
β second line
β°β send
The compose prompt is intentionally lightweight and portable. Enter adds lines;
Ctrl-D sends; Ctrl-C cancels. It stays in the terminal, does not open $EDITOR,
and does not use a full TUI framework.
--new, --reset Start a fresh terminal session
--show Show the current terminal session id
--model, -m <model> Use a Codex model for this turn
--profile, -p <name> Use a Codex config profile
--config, -c <k=v> Pass a Codex config override
--file, -f <file> Read the prompt from a file (- for stdin)
--host, --ssh <host> Inspect a remote host over local ssh
--write Allow workspace writes for this turn
--danger Bypass Codex approvals and sandboxing
--quiet Print only assistant messages
--verbose Show extra event details
--json Print raw Codex JSONL events
--color <mode> auto, always, or never
--no-color Disable color
--no-spinner Disable the thinking indicator
--instructions <text> Override the default hmm instruction prefix
--no-instructions Send the prompt without hmm's instruction prefix
--version Show hmm version
Common Codex passthrough flags such as --enable, --disable, --image,
--cd, --add-dir, --ignore-user-config, and --ignore-rules are also
accepted before the prompt.
hmm keeps one sticky Codex thread per terminal.
The session key is based on the parent shell process and current TTY. The current session id is stored at:
${XDG_STATE_HOME:-$HOME/.local/state}/hmm/sessions/<key>
This intentionally does not include the current working directory. You can move around in a terminal session and keep talking to the same assistant.
Remote sessions are scoped by host, so local context and each --host target get
separate sticky threads in the same terminal.
Use hmm --new or hmm --reset to forget the current terminal's thread.
Use hmm --show to print the stored Codex thread id.
Default mode is read-only:
hmm "explain what this repo does"Internally that passes Codex:
--config sandbox_mode="read-only"
For edits or local command execution that writes to the workspace:
hmm --write "create a tarball of this directory"That passes:
--config sandbox_mode="workspace-write"
For fully unsandboxed automation:
hmm --danger "do the broad local task I just described"That passes:
--dangerously-bypass-approvals-and-sandbox
--danger is intentionally explicit and prints a warning. hmm does not try to
replace Codex's permission system; it provides small, memorable presets over it.
hmm prefixes each turn with a short instruction that biases Codex toward terse
terminal-assistant behavior:
Answer briefly and directly. Prefer the smallest useful answer. Do not inspect
files or run tools unless needed to answer or act.
Override it for one call with --instructions <text>, set a default with
HMM_INSTRUCTIONS, or disable it with --no-instructions.
hmm always runs Codex in JSONL mode and renders the stream.
Normal mode shows:
> shell: /bin/zsh -lc pwd
ok shell completed
done
45.2k in / 37 out Β· 45.2k total Β· 2.1s
The renderer is deliberately compact:
- while Codex is thinking, a spinner and yellow
hmmmm...indicator grow across the terminal with a dim(thinking)label - assistant messages are bold when color/style is available
- shell tool use is shown as dim start/completion lines
- token usage and elapsed time are shown at the end when Codex emits usage data
--no-spinnerdisables the thinking indicator--verboseshows extra event details--jsonbypasses rendering and prints raw Codex JSONL
Context utilization is shown only when hmm knows the model's context window.
Unknown models show token counts without a percentage.
Stream behavior:
- assistant text and compact tool visualization go to stdout
- token usage, elapsed time, warnings, and errors go to stderr
--jsonprints raw Codex JSONL to stdout and suppresses the usage footer
hmm does not try to defeat shell parsing. Quote prompts that contain shell
syntax, or use stdin, heredocs, files, or the compose prompt.
These are safe patterns:
hmm "what does --force do in git clean?"
hmm 'what is the literal meaning of $PATH?'
hmm <<'EOF'
Explain `cmd`, $(substitution), pipes |, redirects >, and quotes "like this".
EOFThe quoted heredoc delimiter, <<'EOF', prevents the shell from expanding
$VARS, command substitutions, globs, and backticks inside the block.
bin/hmm
parses hmm flags
manages terminal session state
invokes libexec/hmm-codex
pipes JSONL into libexec/hmm-render
libexec/hmm-session
computes the per-terminal session key
saves, shows, and resets Codex thread ids
libexec/hmm-codex
is the only script that knows how to call codex exec
normalizes safety presets and passthrough Codex flags
libexec/hmm-render
consumes Codex JSONL
renders assistant text, shell tool use, and usage data
install.sh
installs the CLI and private helpers
Codex remains responsible for tools, MCP, skills, sandboxing, approvals, and
model execution. hmm is just the inline terminal adapter.
libexec is intentional Unix packaging language: these are private executable
helpers used by bin/hmm, not user-facing commands or generic project scripts.
Run the test suite with:
tests/runThe tests do not invoke real Codex. They put a fake codex executable at the
front of PATH, feed deterministic JSONL into the renderer, and isolate session
state under a temp directory.
Current coverage includes:
- shell syntax for all scripts
- local installer behavior
--helpwithout writable temp space- session save/show/reset behavior
--reset --showordering- renderer output for shell events, assistant text, usage, and unwritable metadata
- default instruction prefix and
--no-instructions - stdin, heredoc, and file prompt input
- direct
bin/hmmcontinuity --writeand--dangerCodex flags- Codex exit-status propagation
CI runs tests/run on pushes and pull requests across Ubuntu and macOS.
To publish a versioned GitHub Release:
git tag v0.2.0
git push origin v0.2.0The release workflow runs the test suite, then creates a GitHub Release for the
tag and uploads install.sh and hmm.tar.gz assets. Use HMM_TARBALL_URL to
pin installs to a specific release tarball.
MIT. See LICENSE.