hmm

command module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 6 Imported by: 0

README ΒΆ

🫀 hmm

hmm (interj.) β€” what you type right before you ask.

CI Version

The first agent built on weft: one fast Go binary for quick-asking from the CLI β€” hmm "question", pipes in, streamed answers out β€” that can also create its own tools at runtime as sandboxed subprocess plugins and register them without rebuilding.

Why "hmm"?

Because it's the shortest honest name for what this is: the sound of a question forming. You're mid-thought in the terminal, something looks off, and hmm is literally what you mutter β€” so the command is the interjection: hmm "is this diff right?". Three keys, no subcommand, no ceremony β€” a question should cost less than the answer.

The project was born as bobina (the bobbin that feeds the weft β€” the weaving metaphor), then renamed: the command is what you type all day, so it got the three keys. The pre-rename BOBINA_* env vars and ~/.local/share/bobina tree still work (picked up automatically), and Wefty stays the mascot.

Status: v0.1.0 β€” one-shot ask, piped stdin, four core tools, sessions with -c resume, --json, conversation mode (interactive chat / stdin-line loop), and the plugin library (the agent writes and mounts its own tools mid-run, on weft v0.1.0's RawTool/ToolSource). All offline-tested (make test).

Run it

Install (Go 1.26+):

go install github.com/weftgo/hmm@v0.1.0
hmm --version             # hmm 0.1.0

Or build from source:

git clone https://github.com/weftgo/hmm
cd hmm && go build -o hmm .

Set it once β€” then just ask

hmm config init writes a template config; put your provider, model, and key in it and never type them again:

./hmm config init
$EDITOR ~/.config/hmm/config.toml
provider = "kimi"
kimi_model = "kimi-k2.6"    # models are per provider β€” they never leak across
kimi_key = "sk-..."         # account also offers kimi-k3, kimi-k2.7-code[-highspeed]
# zai_model = "glm-5.3-flash"   # optional: zai's default when unset
# zai_key = "..."
./hmm "say hi"          # no flags, no env vars
./hmm config            # shows what resolves from where (keys are never printed)

Precedence when several sources disagree: flag > env > config file > defaults. The file holds keys, so config init writes it 0600 β€” keep it that way.

See and pick models β€” hmm models lists what your account actually offers (fetched live, current default marked), and lets you choose:

./hmm models                 # lists the resolved provider's models
./hmm models --provider zai  # any provider
./hmm models --set kimi-k3   # persist the choice into the config
./hmm models                 # on a TTY: pick by number instead
./hmm --model kimi-k3 "hi"   # one run only, config untouched

Pick a provider (env key), then ask. --provider defaults to anthropic; zai is z.ai's GLM over their OpenAI-compatible endpoint.

# z.ai GLM β€” the quickest start
export HMM_ZAI_KEY='your-zai-key'      # or ZAI_API_KEY
./hmm --provider zai "say hi"          # glm-5.3-flash by default
./hmm --provider zai --model glm-5.3 "explain this channel semantics"

# Kimi (Moonshot) β€” OpenAI-compatible endpoint
export HMM_KIMI_KEY='your-kimi-key'    # or KIMI_API_KEY / MOONSHOT_API_KEY
./hmm --provider kimi "say hi"         # kimi-k2.6 by default

# Anthropic (default provider)
export ANTHROPIC_API_KEY='sk-...'
./hmm --model <model-id> "review this design"

# OpenAI, or any OpenAI-compatible gateway via OPENAI_BASE_URL
export OPENAI_API_KEY='sk-...'
./hmm --provider openai --model <model-id> "hi"

# Google
export GEMINI_API_KEY='...'
./hmm --provider google --model <model-id> "hi"

anthropic/openai/google have no built-in model default β€” pass --model or set HMM_MODEL. (HMM_PROVIDER also works as the provider fallback.)

Pipes are first-class β€” stdin is fenced into the prompt, capped at 64 KiB, and nothing ever blocks waiting for input:

git diff | ./hmm --provider zai "review this diff"
./hmm --provider zai "summarize" < notes.txt
dmesg | ./hmm --provider zai --yolo "what driver is failing?"   # --yolo enables run_command

Continue a conversation (every run is saved under ~/.local/share/hmm/sessions/):

./hmm -c "and what about error handling?"   # one-shot follow-up
./hmm --resume <id-prefix> "pick up where we left off"
./hmm -c                                    # …or keep chatting: interactive

Every one-shot ends with a hint (↻ hmm -c "follow-up" Β· hmm -c β€” continue this chat). With no prompt at all, hmm is a conversation: on a terminal it's an interactive chat β€” banner, β€Ί prompt, streamed answers, /exit or Ctrl-D to leave, Ctrl-C saves and exits β€” and a pipe drives the same loop, one prompt per line (echo "hi" | ./hmm). -c continues the last session from the current directory (each terminal its own thread); session ids are human-readable (amber-moth-482), and --resume accepts a unique prefix.

On a terminal, hmm runs its Charm layer (lipgloss + glamour + bubbles): the answer renders as markdown (theme follows your terminal's dark/light background; GLAMOUR_STYLE overrides), a spinner carries the live status (thinking Β· latest reasoning, βš™ tool calls, step finishes), and the activity feed is color-themed. Piped output stays byte-clean β€” raw markdown, plain feed, no spinner β€” so hmm "q" > file.md and | jq keep working. --quiet hides the narration on a TTY too.

Machine output β€” one weft event per line (the weft wire contract):

./hmm --json --provider zai "hi" | jq -s 'map(.type)'

Flags: --provider --model --system --max-steps --sequential --yolo --json --quiet --resume <id|prefix|path> -c --version

Exit codes: 0 ok Β· 1 run error Β· 2 usage Β· 130 interrupted (partial transcript still saved)

Safety stance: run_command requires a TTY y/N confirm, or --yolo to skip it; a piped run without --yolo refuses and tells the model why. Be clear about what that is: the y/N gates (and the --trust workspace gate) are policy and UX, not a security boundary β€” a compromised prompt can still try to talk its way past a human. The actual boundary is structural: model-written code runs as a subprocess with a timeout, never in-process; every plugin passes a selftest before it enters the library; and OS-level sandboxing is planned next. Don't run --yolo in a repo you don't trust.

The plugin library (the signature feature)

The agent can create its own tools mid-conversation and call them by name in the very next step β€” no rebuild, no restart (this rides two seams hmm drove into weft: RawTool and ToolSource):

./hmm --provider zai --yolo "you need a tool that base64-encodes its input β€” write it, install it, and prove it works"
./hmm -c "now encode 'hello weft'"        # the tool persists across runs

Plugins live in ~/.local/share/hmm/plugins/<name>/ β€” a plugin.json manifest plus source (go compiles at install; sh and python run verbatim). Every install is confirmation-gated (the TTY y/N prompt shows the plugin's code β€” approve what will actually run β€” or --yolo to skip), every plugin must pass its selftest before entering the library, and plugin code runs as a subprocess with a per-call timeout: args as JSON on stdin, stdout as the tool result.

Built on weft as its first real consumer; mascot: Wefty.

License

MIT β€” see LICENSE.

Documentation ΒΆ

Overview ΒΆ

Command hmm is a quick-ask agent for the command line, built on weft. main only wires the OS streams and signals; the CLI itself lives in internal/cli.

Directories ΒΆ

Path Synopsis
internal
agent
Package agent resolves provider configuration into a weft.Model and assembles the hmm agent: name, system prompt, core tools, loop policy.
Package agent resolves provider configuration into a weft.Model and assembles the hmm agent: name, system prompt, core tools, loop policy.
cli
Package cli is hmm's command line: flag parsing, stdin handling, the turn pump, session persistence, and exit codes.
Package cli is hmm's command line: flag parsing, stdin handling, the turn pump, session persistence, and exit codes.
config
Package config loads hmm's set-it-once configuration file β€” provider, model, and provider keys, so a plain `hmm "question"` needs no flags and no env vars.
Package config loads hmm's set-it-once configuration file β€” provider, model, and provider keys, so a plain `hmm "question"` needs no flags and no env vars.
models
Package models lists the models a provider account actually offers β€” `hmm models`.
Package models lists the models a provider account actually offers β€” `hmm models`.
plugins
Package plugins is hmm's tool library: model-written tools live as subprocesses behind a manifest, are mounted as first-class weft tools via weft.RawTool, and β€” thanks to weft.ToolSource β€” a plugin installed mid-run is callable by name in the next step.
Package plugins is hmm's tool library: model-written tools live as subprocesses behind a manifest, are mounted as first-class weft tools via weft.RawTool, and β€” thanks to weft.ToolSource β€” a plugin installed mid-run is callable by name in the next step.
procs
Package procs configures subprocess execution so that cancelling a command's context actually bounds the call.
Package procs configures subprocess execution so that cancelling a command's context actually bounds the call.
session
Package session persists every hmm run as a JSONL file in weft's message wire format: line 1 is a session header, then one weft Message per line, then a final usage line.
Package session persists every hmm run as a JSONL file in weft's message wire format: line 1 is a session header, then one weft Message per line, then a final usage line.
textutil
Package textutil trims byte-capped output back to UTF-8 rune boundaries.
Package textutil trims byte-capped output back to UTF-8 rune boundaries.
tools
Package tools defines hmm's core tools: read_file, list_dir, grep, and run_command.
Package tools defines hmm's core tools: read_file, list_dir, grep, and run_command.
ui
Package ui is hmm's Charm layer β€” every terminal write in the binary goes through it (the rule crush gives its TUI, scaled to an enriched CLI):
Package ui is hmm's Charm layer β€” every terminal write in the binary goes through it (the rule crush gives its TUI, scaled to an enriched CLI):

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL