Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Nov 8, 2026Hint - Context-aware assistant for developers
hint is a CLI agent for the directory you run it in. It reads your
project, answers questions about it, and — with your confirmation — edits
files and runs commands. It talks to any OpenAI-compatible API and can fall
back to a local Ollama.
Features
- A full-screen interactive session by default (a line REPL with
--plain); a one-shot answer with-p, plain text or JSON, for scripts - Tools the agent uses itself: read, list, glob, grep, edit and write files, run shell commands — every edit shown as a diff and every command confirmed before it runs
- Automatic context: an overview of the directory (respecting
.gitignore) and your own instructions from aHINT.md, global or per project - Conversations recorded as sessions you can continue, list, pick up by id and fork at any turn into a branch of their own
- Provider profiles with a default/fallback pair, configured from the
command line, environment variables or YAML files, and switchable
mid-conversation with
/profile - One core, several shells:
hint serveruns the agent as a local JSON-RPC service that other terminals join withhint --connect, andhint --mode rpcspeaks the same protocol on stdin/stdout for scripts and editors
Installation
Every release ships prebuilt binaries for Linux, macOS and Windows on
amd64 and arm64 (a Raspberry Pi 4/5 runs the linux_arm64 build) on the
Releases page. hint --version
tells which build you have.
hint uses ripgrep for its grep
and glob tools when it is installed and falls back to a pure-Go search
otherwise: recommended, not required. To answer anything it needs an
OpenAI-compatible endpoint or a local Ollama; see
Configuration.
apt (Ubuntu, Debian, Linux Mint, Pop!_OS, Raspberry Pi OS)
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://mryush.github.io/hint/hint.gpg | sudo tee /etc/apt/keyrings/hint.gpg >/dev/null
echo "deb [signed-by=/etc/apt/keyrings/hint.gpg] https://mryush.github.io/hint stable main" \
| sudo tee /etc/apt/sources.list.d/hint.list
sudo apt update
sudo apt install hint
One stable suite serves every release of every apt-based distribution
on amd64 and arm64: the binary is static and does not depend on the
distribution's libraries. The package depends on ripgrep, so apt installs
it too. Updates arrive with sudo apt update && sudo apt upgrade.
The Release file is signed; hint.gpg is the public half of the key
and signed-by limits it to this one repository. Pre-releases are not
published to the repository. A single .deb is also attached to each
release for dpkg -i.
Homebrew (macOS/Linux)
brew install --cask mrYush/tap/hint
The tap is mrYush/tap (the repository mrYush/homebrew-tap); the cask
installs the release binary and brings ripgrep with it. Pre-releases are
not published to the tap.
Scoop (Windows)
scoop bucket add hint https://github.com/mrYush/scoop-bucket
scoop install hint
The bucket is mrYush/scoop-bucket; the manifest installs the release
binary for amd64 and arm64 and brings ripgrep from Scoop's main bucket.
Pre-releases are not published to the bucket.
Release archive
Download the archive for your platform, check it against checksums.txt
from the same release, and put the binary on your PATH:
tar xzf hint_<version>_linux_arm64.tar.gz
sudo install hint /usr/local/bin/
hint --version
On Windows unpack the .zip and put hint.exe somewhere on %PATH%.
go install
go install github.com/mrYush/hint/cmd/hint@latest
hint --version then reports the module version the toolchain recorded.
Building from source
You need Go 1.26 or higher.
git clone https://github.com/mrYush/hint.git
cd hint
go build -o hint ./cmd/hint
./hint --version # dev (commit <hash>, built <commit time>)
Then copy the binary somewhere on your PATH (sudo cp hint /usr/local/bin/
on Linux and macOS). go build ./..., go test ./... and go vet ./...
are the checks CI runs; CONTRIBUTING.md has the rest.
Usage
Interactive session
Run hint with no arguments in your project. On a terminal it opens the
full-screen shell: the conversation scrolls above the input with answers
rendered as Markdown, a file edit or a command asks for permission in a
dialog that shows the diff or the command (y / n / a), and the
header names the session and the provider profile in use. Enter sends; a
newline is Shift+Enter where the terminal reports it (kitty, WezTerm,
Ghostty, iTerm2), Alt+Enter or Ctrl+J, and on any terminal a \ at the
end of the line before Enter. Ctrl+C while an answer is being written
stops that answer; Ctrl+D or /exit leaves, and the session is kept.
The conversation scrolls with ↑ on the input's first line and ↓ on its
last (a mouse wheel works the same way), Shift+↑/↓ by line, PgUp/PgDn by
page and Ctrl+Home/End to its ends; the header says how many lines are
below. Esc in an empty input (or Ctrl+↑) picks a message: ↑/↓ move
between messages, y copies the one picked, f forks the conversation
after that message's turn (see Sessions), Esc returns to the
input. One-letter answers and commands (y/n/a, j/k) work in a
Cyrillic layout too, in the TUI and at the line prompts: н is y.
Tab completes /commands and their arguments; /help lists them:
/sessions shows this directory's sessions as a tree and resumes one,
/new starts a fresh session, /profile switches the provider profile,
/repos manages explicitly attached repositories and folders (see Working with multiple roots),
/prompts lists MCP prompts,
/completion shows or sets when a model continues what you type (see
Completion of what you type), /setup and /config edit the
configuration, /copy and ctrl+y copy the last answer (or question,
code, all), /mouse switches to a mode where the wheel scrolls and a
click copies the block under it (off by default, so the terminal's own
text selection works).
hint --plain runs the same session as a line REPL, which is also what a
terminal that cannot draw the shell gets:
$ hint --plain
hint: interactive session; /help for commands, Ctrl-D or /exit to quit
> what does main.go do?
...
> /profile local
hint: answering with local/qwen2.5:7b from now on
> and where is it tested?
...
> /exit
Each line is one question; /help, /new, /profile and /exit are its
commands. Both shells are clients of the same protocol as hint serve.
One question
hint -p "How do I run this project?"
hint -p "Explain the directory structure of this project"
hint -p "What does the README.md file contain?"
hint -p prints the answer and exits, with the tool activity on stderr so
stdout stays the answer. For a script, --output json prints one object
instead:
hint -p "list the tests" --output json
# {"answer":"...","finish_reason":"stop","session_id":"3f2a...","usage":{...}}
A failed run still prints the object (with an error field) and exits with
status 1.
The older form hint "question" keeps working as a one-shot and prints a
note pointing at -p.
Run modes
hint can edit files in the working directory and run shell commands.
What it may do without asking is a per-run choice:
hint -p "add a --version flag" # --ask (default): every edit shows a
# diff, every command is shown, and
# you answer y / n / a(lways)
hint --auto-edit -p "fix the failing test" # edits apply silently, commands still ask
hint --yolo -p "..." # nothing asks; prints a loud warning
Answering a to a command remembers its program and subcommand for the
rest of the run: go test covers go test ./... but not go build, and
it is offered only for programs with subcommands (go, git, npm,
cargo, docker, …); anything else, and every edit, is confirmed each
time. File tools never leave the working directory in any mode; bash is
not confined, which is why --auto-edit keeps asking for it. Without a
terminal on stdin (a script, < /dev/null) anything that needs an answer
is denied.
One core, several shells
The agent runs as a core that every shell talks to over newline-delimited
JSON-RPC 2.0, even inside one process. hint serve runs that core on its
own, so a second terminal can join a conversation, or an editor can drive
it:
hint serve # the core on a Unix socket under $XDG_RUNTIME_DIR/hint
hint --connect -c # another terminal: continue the same session on it
hint --connect unix:/path/hint.sock # or name the socket (tcp:127.0.0.1:port on Windows)
hint --mode rpc # the same protocol on stdin/stdout, for a script or an editor
hint serve binds to the working directory it was started in; the tools
never leave it. Its socket is private to the user and guarded by a token
it writes next to the socket, which --connect reads. The protocol — the
handshake, sessions, turns and their events, permission questions, profile
switches and forks — is in docs/rpc.md.
Sessions
Every run is recorded, so a later run can pick the conversation up:
hint -c # continue the most recent session of this directory
hint -r # list this directory's sessions and ask which one
hint --session 3f2a # continue the session whose id starts with 3f2a
hint sessions # list this directory's sessions
hint --no-session -p "..." # record nothing
The session flags work for both the interactive session and -p.
/new, in the TUI and in hint --plain, starts another session without leaving the shell; a fresh hint does the same from outside it.
The session just left stays on disk and can be resumed, and the new one answers with the default profile rather than one /profile switched to.
A run started with --no-session keeps the new session off disk too.
A session can branch. In the TUI, f on a picked message forks the
conversation after that message's turn, and f on a row of /sessions
forks the whole session: a new session starts with the copied history,
the shell moves to it, and the original is left as it was. Forks are
listed under the session they came from, in the TUI as a tree and in
hint sessions with a forked from <id> after turn N note. Over RPC the
same is session.fork (docs/rpc.md).
Sessions are one JSONL file each under
~/.local/share/hint/sessions/<project>/ ($XDG_DATA_HOME is respected),
one per working directory; delete a file to forget the conversation. The
system prompt is not stored — each run builds it afresh — and neither are
a(lways) answers, so a continued session asks again before running
commands. The provider profile a session was switched to is recorded, so
-c continues with it. When the conversation outgrows the model's
context window it is summarized, and the summary is what a later run
continues from.
Profiles
A session answers with the default profile until told otherwise; the switch takes effect for the next question and stays with the session:
> /profile # the profiles the configuration has, the one in use marked
> /profile local # answer with the local profile from now on
> /profile vllm Qwen/Qwen2.5-Coder-7B-Instruct # that profile with another model it serves
In the TUI /profile alone opens a list to pick from, Tab completes the
names, and the header shows what is in use. Tab after a profile name
completes the model from what that profile's endpoint serves: the first
Tab on it asks the core (hint models does the same on the command
line), says so in the status line, and the list is kept for the rest of
the session; the next Tab offers the names. The same list completes the
value of /config set providers.<profile>.model. An endpoint that does
not answer the models request leaves the model a free value, with the
reason in the status line. A profile that cannot be used
(an openai profile without a key, an ollama profile without a model) is
listed with the reason and refused. The core re-reads the configuration
files on every switch, which is how hint setup and /setup take effect
without a restart. Switching fallback_provider off is not part of it:
the configured fallback stays behind whichever profile is in use, unless
it is the same one.
Completion of what you type
The TUI can ask a model to continue the question you are typing. The
continuation is shown in the status line and Tab takes it; any other key
drops it. It is off by default, because every request sends the text
typed so far and the last question of the conversation to a provider:
with a cloud profile that text leaves the machine on every request.
completion.mode picks when the shell asks:
| mode | when a request goes out |
|---|---|
off | never (the default) |
tab | only when you press Tab on a question; Tab with a suggestion on offer accepts it instead |
idle | after a pause in typing (completion.idle_after, 600ms), once the question is 12 characters or longer, at most once every two seconds and only after three more characters were typed |
both | either of the two |
tab costs a request per Tab and nothing else, which is why it is the
mode to start with. idle is the one that spends tokens without a key
press; /completion in the TUI shows how many requests and tokens the
session has spent so far, and /completion off|tab|idle|both changes the
mode for the session without touching the config. Three failed requests
in a row switch completion off for the session, with a notice.
By default the session's own profile and model answer, so nothing extra runs anywhere. A separate small model is optional:
completion:
mode: tab
profile: local # a profile from providers:, e.g. Ollama with a 1-3B model
model: qwen2.5:1.5b # optional: a model of that profile for completions only
warn_after: 2s # after this long the status line says the model is slow
Think twice about a second model on a small machine: Ollama keeps models
loaded, so a completion model beside the main one on a Raspberry Pi or
a laptop with little RAM can evict the main model and make every answer
start with a reload. On such a machine keep profile empty, or keep
completion off. A profile named here that does not exist, or cannot be
used, switches completion off with a warning at start rather than
stopping the run. Completions are never written to the session file.
Configuration setup
hint setup asks for the profile, base URL, model and the API key, and
writes them to ~/.config/hint/config.yaml (mode 0600, comments kept);
the key itself goes into the OS secret store when there is one, and the
file only points at it (see API keys).
When a provider rejects the key, hint says which config files it is
using and offers the same questions (in the TUI: /setup, with the key
masked). A project .hint/config.yaml is never written, so a key does
not end up in git.
A running session switches to the saved profile at once, so the next
question goes out with the new key; hint setup on its own is picked up
by the next start.
hint config changes one key at a time without an editor; every value
is checked against the key's type before anything is written, and the
file keeps its comments:
hint config # what the next start uses, keys masked
hint config keys # every key, default, HINT_* variable, flag
hint config set overview.depth 3 # the global file
hint config set --project providers.vllm.model Qwen/Qwen2.5-Coder-7B-Instruct # this repository only
hint config unset --project overview.depth # back to the global value
In the TUI the same is /config show | keys | set | unset, with Tab
completing the subcommand, the key and, where there is a closed set, the
value; /setup edits every field of a provider profile in one form. An
API key is set through /setup or hint setup only, where it is masked,
and never as a literal into the project file.
API keys and what the agent can see
Once entered, an API key is kept out of the agent's reach: not readable by its tools, not in what goes to the model, not in a session file. Only you see it again, through one command made for that.
Where the key lives. hint setup and /setup put the key into the
OS secret store — the Secret Service on Linux (GNOME Keyring, KWallet),
the Keychain on macOS, the Credential Manager on Windows — and write
api_key: keyring:<profile> into the config file, so the file holds no
secret. A key given as ${VAR} stays a reference to the environment, as
before. secrets.store in the config picks the policy: auto (the
default) uses the store when it answers and falls back to the 0600 file
with a warning in the save message; keyring insists and fails without
a store; file never asks — the choice for a server, a container or CI,
where no Secret Service runs. A profile that keeps a literal key in a
file while a store is available gets one warning at start; hint setup
moves it, nothing moves it behind your back.
hint config show-key vllm # the one way to see a key again: asks, prints it masked
hint config show-key vllm --reveal # in full; terminal only, no RPC method, no tool
What the agent cannot do. The bash tool and ripgrep behind glob
and grep start without the key variables: every ${VAR} a profile
refers to, the well-known ones (APIBAR_KEY, APIBAR_TOKEN,
OPENAI_API_KEY, HINT_API_KEY) and any variable whose value equals a
configured key, so env shows none of them. The file tools refuse
~/.config/hint, the session directory and the project's own
.hint/config.yaml even when the working directory contains them (a run
from your home directory); .hint/rules stays readable. Every tool
result is scrubbed against the configured keys before the model, the
transcript or the session file sees it, so a key that leaks some other
way arrives as ***. The file tools open everything through os.Root,
so a symlink swapped in between the check and the open cannot lead
outside the working directory either.
What stays open, and why. bash is not sandboxed: a command that
reads a 0600 file you own, or asks the secret store itself
(secret-tool lookup, security find-generic-password), works, because
it runs as you. With the key in the secret store the config file has
nothing to read; with the file fallback it does, which is what the
warning says. A model can also transform a value before printing it,
which defeats scrubbing. Closing those needs a sandbox for bash, which
is its own work package.
Models
hint models # what the default profile serves
hint --provider local models # what another profile serves
For an Ollama profile the list shows the installed models with their sizes;
for any other profile it is the endpoint's GET /models.
Project instructions
Put a HINT.md in your project to tell the assistant how to work there —
conventions, commands to run, things to avoid. AGENTS.md and CLAUDE.md
are read as compatible names, so a project that already keeps one for
another tool needs nothing new. hint reads the first of the three it
finds in each directory from the repository root down to the directory you
run it in, so a nearer file can refine the outer one.
Your own standing instructions go in ~/.config/hint/HINT.md
($XDG_CONFIG_HOME is respected); it is read before any project file.
All instruction files together are limited to 32 KiB by default, or a
sixteenth of the model's context window when the profile states one
(context_window), so a small local model is never handed more preamble
than it can read. When the files do not fit, nothing is dropped blindly:
- every file is guaranteed room for its outline, so a
HINT.mdnext to you is never skipped because a larger one above spent the budget; - a Markdown file over its share shows whole sections while they fit and
the rest as heading and first sentence, marked
[...]; a file without headings is cut at a line; - the model has an
instructionstool that returns any of these files in full or one section by heading, and answers "where does this rule come from" with apath:line— it reads the run's instruction files and nothing else, so a parent directory'sHINT.mdis reachable and../.envis not; - as a last resort, and only if you say so (
instructions.summarize: trueor--summarize-instructions), a file that does not fit even as an outline is summarized by the model, markedsummary="true"in the prompt, and the summary is cached under~/.cache/hint/instructions/($XDG_CACHE_HOMEis respected) until the file changes.
A warning on stderr names every file shown as less than itself.
A project that outgrows one file can split it. HINT.md stays a short
index and rule files under .hint/rules/*.md hold the detail:
---
title: Go style
paths:
- "**/*.go"
---
Keep errors last in a signature; run gofmt before every commit.
A rule with paths: is loaded only once a tool of the conversation reads
or writes a matching file; until then the prompt lists it by title, and
the model can read it early with the instructions tool. A rule without
paths: is loaded always, right after its directory's HINT.md. Patterns
use the syntax of the glob tool (**, {a,b}; a pattern without a
slash matches a file name at any depth), relative to the directory that
holds .hint. .cursor/rules/*.mdc files are read the same way
(globs:, description: and alwaysApply: are understood), so a team
that already keeps Cursor rules need not maintain a second tree. Rules
loaded this way stay loaded for the run, and a continued session (-c)
loads on its first request whatever the earlier run had loaded.
The limit, and the directory overview that goes into the same prompt, can be changed:
hint --instruction-budget 65536 ... # bytes for all instruction files (not scaled to the window)
hint --overview-depth 3 ... # directory levels listed; 0 lists nothing
hint --overview-entries 200 ... # cap on listed entries
hint --summarize-instructions ... # allow model-written summaries of oversized files
or, in a config file:
instructions:
budget: 65536
summarize: true
overview:
depth: 3
max_entries: 200
Configuration
Configuration is a set of named provider profiles plus a default/fallback pair. Sources, highest priority first:
- CLI flags (
--provider,--api-url,--api-key,--model,--context-window) - Environment variables (
HINT_*) - Project file
./.hint/config.yaml - Global file
~/.config/hint/config.yaml(respects$XDG_CONFIG_HOME)
First run
With no configuration at all, hint on a terminal says so and asks
whether to set it up now: the profile name and base URL (each has a
default, Enter accepts it), the model (required: a server serves what was
loaded into it, hint models lists them) and the API key, typed with the
echo off.
The answers go to ~/.config/hint/config.yaml (mode 0600) and the start
continues. hint setup asks the same questions at any time and edits the
file in place, keeping its comments. In a script or a pipe nothing is
asked: the error names hint setup and the other ways below.
config.example.yaml in the repository root lists
every key with its default and what it does; copy it to the global
path for settings that apply everywhere, or to .hint/config.yaml in a
repository for that project only. A test keeps it in step with the code:
it must load, and it must mention every key the loader reads.
Quick start
hint speaks the OpenAI Chat Completions dialect, so any server or
service that serves it is a provider: your own model behind
vLLM (or llama-server, LM Studio, TGI), a local
Ollama, OpenAI itself, or an
aggregator such as OpenRouter or
api-bar.ru. The quickest start is hint setup,
which asks for the base URL, the model and the key and writes the file
below. For a vLLM server on your network that is:
hint setup
# profile name [vllm]:
# base URL: http://inference.example.net:8000/v1
# model: Qwen/Qwen2.5-Coder-32B-Instruct
# API key, input hidden: ... # whatever vLLM was started with (--api-key), or empty
hint -p "How do I run this project?"
For a local Ollama the file is
shorter still — kind: ollama needs no key — and it is the usual
fallback behind a bigger model, so the agent keeps working offline:
providers:
- name: local
kind: ollama
model: qwen2.5:7b # ollama pull qwen2.5:7b; hint models lists what is installed
Without any file, two environment variables still work as a zero-config
path: OPENAI_API_KEY selects https://api.openai.com/v1, and
APIBAR_KEY the api-bar.ru aggregator (API_BAR_KEY, the earlier
spelling, still works with a warning). An openai profile that names no
base_url is taken to be a server on this machine at
http://localhost:8000/v1, where vLLM listens by default. Every profile
names its model: there is no default, because a server serves whatever
was loaded into it.
Configuration file
providers:
- name: vllm
kind: openai # OpenAI Chat Completions dialect
base_url: http://inference.example.net:8000/v1 # hint appends /chat/completions
api_key: ${VLLM_API_KEY} # $VAR / ${VAR} references are expanded; a vLLM
# started without a key accepts any value here
model: Qwen/Qwen2.5-Coder-32B-Instruct # a model the server serves (hint models lists them)
context_window: 32768 # tokens; sizes the compaction (optional)
- name: local
kind: ollama # same dialect, no key required
base_url: http://localhost:11434/v1
model: qwen2.5:7b
context_window: 8192
header_timeout: 15m # wait for response headers (default 60s)
- name: cloud # an aggregator works the same way:
kind: openai # OpenRouter, api-bar.ru, OpenAI itself
base_url: https://openrouter.ai/api/v1 # or https://api-bar.ru/route/openai
api_key: ${OPENROUTER_API_KEY}
model: anthropic/claude-sonnet-4
context_window: 200000
default_provider: vllm
fallback_provider: local
The project file overlays the global one profile-by-profile (matched by
name, non-empty fields win), so a project can override just the model or
key of a globally defined profile. A key the more local file does not
mention is inherited from the more general one. Do not put a literal API
key in the project file, it ends up in git: use ${VAR} there.
header_timeout bounds how long hint waits for the response headers of a
chat request; a Go duration such as 90s or 15m, units required.
A local server like llama-server sends them only after it has processed
the whole prompt, which on a Raspberry Pi takes minutes, so raise it for
such profiles.
max_tokens_field: max_completion_tokens is for OpenAI's reasoning models (o-series, gpt-5), which refuse max_tokens; the default max_tokens is what local servers understand.
MCP servers
mcp_servers: in the global config connects hint to servers that speak the Model Context Protocol; their tools are offered to the model as <server>.<tool> next to the built-ins, under the permission class the entry declares (execute by default):
mcp_servers:
filesystem:
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
permission: read
timeout: 30s
resources: ["file:///absolute/path/to/README.md"] # example; use a path this server may read
tools:
allow: ["read_text_file", "list_directory"]
github:
transport: http
url: https://api.githubcopilot.com/mcp/
headers:
Authorization: "Bearer ${GITHUB_TOKEN}"
permission: execute
A stdio server runs with the variables its entry declares plus a base stripped of hint's API keys; a project-added server gets only a minimal base (PATH, HOME, the locale).
HTTP headers support ${VAR}, are sent only to the configured origin, and a redirect to another host is refused before a credential can follow it.
The MCP schema budget is 48 KiB with a 20 KiB per-server cap; a server that does not fit is hidden as a whole and a warning names it, and tools.allow or tools.deny narrows a large server.
A project .hint/config.yaml may add a server but may not set env or headers; it stays disabled until hint mcp enable <name> is run in that repository, and any change to command, args, url, permission or resources resets that approval.
Resources named by resources load like instruction files, sharing their budget, outline and instructions tool.
Global resource URIs expand ${VAR} and redact the referenced values; project resource URIs stay literal.
Duplicate URIs are rejected, and a configured resource may contain at most 1 MiB of decoded text or blob content.
The model can read any URI on an enabled server with resource_read using server and uri; that result is untrusted tool data and never becomes an instruction.
Neither tools.allow/tools.deny nor the instruction-only resources list restricts resource_read; a resource over 50,000 decoded bytes is rejected rather than truncated.
Those size checks run after the SDK has buffered the inbound frame, which defaults to 16 MiB.
Empty resources and non-text configured blobs produce warnings instead of silent instruction entries.
/prompts lists server prompts in the TUI and plain REPL, and /<server>:<prompt> [args] expands one as a user turn with positional arguments.
Quote or backslash-escape an argument containing spaces.
Prompt user and assistant messages are flattened into that one user turn.
Prompt names containing a colon or whitespace cannot be slash commands and are skipped with a warning.
In the TUI, Tab completes prompt commands and asks the server for argument values through completion/complete, including already entered arguments as context.
MCP servers start on the first turn or an explicit prompt operation, not when hint launches, so a run that never uses MCP starts no process.
hint mcp list shows the configured servers and resource URIs without connecting.
hint mcp status connects, lists tools under the budget, reports configured resource counts without reading their contents, and checks each server with an MCP ping (the handshake is the check on servers that removed ping).
hint mcp enable <name> records the project-server approval.
Environment variables
export HINT_PROVIDER=<profile_name> # select the default profile
export HINT_FALLBACK_PROVIDER=<name>
export HINT_API_KEY=<key> # these four override the SELECTED
export HINT_API_URL=<base_url> # profile only; the fallback keeps
export HINT_MODEL=<model> # its own values
export HINT_CONTEXT_WINDOW=<tokens>
export HINT_INSTRUCTION_BUDGET=<bytes> # the limits of the project context
export HINT_OVERVIEW_DEPTH=<levels>
export HINT_OVERVIEW_ENTRIES=<count>
export HINT_DEBUG=true # same as --debug
CLI flags
hint --provider=local -p "question" # pick a profile for one run
hint --api-key=<key> --model=gpt-4o-mini -p "..." # override the selected profile
Migrating from the flat config
The pre-profiles format (api_url/api_key/model at the top level of
~/.config/hint.yaml or ./hint.yaml) is still read: the old paths work
with a deprecation warning, and the flat keys are mapped onto a single
profile named default. Move to the providers list at the new paths at
your convenience.
How it works
hintcaptures the working directory, reads the global and the project's instruction files, and lists the top levels of the directory, skipping hidden entries, dependency directories and whatever.gitignoreexcludes (inside a git repository, with git installed)- That context and the conversation so far go to the configured model
- The model answers, or asks for tools — reading files, searching, editing,
running commands — which
hintruns (after your confirmation where one is needed) and feeds back, until the answer is complete - The answer streams to your terminal, and the exchange is appended to the session
Troubleshooting
Common issues
-
Error "needs an API key"
- Make sure you've set up your API key through one of the configuration methods
- Check if the environment variable is properly set
-
Error connecting to the API
- Verify your internet connection
- Check if the API endpoint is correct and accessible
hint modelsis a quick way to see whether the endpoint answers
-
Empty or irrelevant responses
- Try rephrasing your question to be more specific
- Check if you're in the correct directory related to your question
Known issues
v0.3.2 ships with these confirmed bugs. They are backlog (decided
2026-10-11) and did not block this patch.
Details and the full audit:
docs/plan/review-2026-09-28-go-practices.md.
- A session file can become unreadable if
hintis interrupted at the exact end of a write; the nexthint -cthen fails to open it. Start a new session; the old file can be repaired by adding a newline after the last complete line. grepon a single file with more than 200 matches reports an error instead of a truncated list; search a directory or narrow the pattern.write_fileon a filehintcannot read shows the overwrite prompt as "too large to diff" instead of the read error.
Fixed in v0.3.2. Two agent-loop bugs, both present since
v0.2.0-alpha. A compaction summary was erased by the next rebuild of the
system prompt: it was stored as an ordinary system message right after the
prefix, and the prefix refresh replaced the whole leading run of system
messages, so the model continued with the prompt and the current exchange
only. The summary is now marked, and both the refresh and the next
compaction treat it as history rather than prompt. Separately, a reactive
compaction after a context-overflow error left the usage baseline pointing
past the shortened history, which crashed the turn with slice bounds out of range when the profile set context_window (or HINT_CONTEXT_WINDOW /
--context-window); the baseline is now reset after every compaction.
Fixed in v0.3.2. The UTF-8 truncation of
#41, present since
v0.2.0-alpha, cut prompts, instruction excerpts, tool arguments and long
tool-output lines at a byte count in the middle of a UTF-8 rune.
encoding/json then replaced the broken bytes with U+FFFD. Every such cut
now goes through one helper that stops on a rune boundary.
Fixed in v0.3.1. A gateway that answers a failed request with both a
short error and a longer message — the NestJS shape
{"error":"Unauthorized","message":"Invalid API key"} — was reported by
the error field alone. Present since v0.3.0. The message now comes
first, with the error as a suffix when the two differ.
Limits of this release. Three things are known and accepted for
v0.3.2 (unchanged from v0.3.1), not bugs to be fixed in a patch:
bashis not sandboxed. The file tools refuse hint's own config and session files, and every tool result is scrubbed against the configured keys, but an approved shell command runs as you and can read those files. A sandbox forbashis a later work package (see API keys).- Input completion stays off by default (
completion.mode: off). Every request sends the text typed so far and the last question of the conversation to the provider. Whether any other mode may become the default waits on the quality evaluation, which did not gate this release. - The OS secret store behind
api_key: keyring:<profile>is exercised on Linux. On macOS and Windows the same call goes to the Keychain and the Credential Manager, and those two paths are unverified for this release.secrets.store: filedoes not need a store.
Debug mode
--debug (or HINT_DEBUG=true) writes a trace of the run — the requests
sent, the responses, the tools called — to a file under
~/.local/state/hint/log/ ($XDG_STATE_HOME is respected), one file per
run; the path is printed at the start. API keys are masked in it. Attach
the file when reporting a problem.
hint --debug -p "Your question"
Contributing
Contributions are welcome! See CONTRIBUTING.md for the development setup, conventions, and how work is planned. The project roadmap lives in PLAN.md.
License
MIT