tnav is a Rust CLI that currently combines two working areas:
- interactive profile and authentication setup for API-key and OAuth-backed services
- LLM-backed shell command generation from natural-language prompts
The project is still early-stage, but the implemented flows are real: guided profile setup, secure secret storage, browser-based OAuth login, environment diagnostics, LLM provider management, model selection, and prompt-driven shell command execution.
Implemented today:
initauth api-keyauth logindoctorconnectmodel [MODEL]- bare prompt input such as
tnav show current directory - plain
tnavinteractive prompt mode
Command groups that parse but still return an unsupported message:
config show,config set,config path,config resetprofile list,profile add,profile remove,profile useauth logout,auth status,auth revoke
tnav connect currently supports named provider instances for:
ollama(default base URL:http://localhost:11434)openai(default base URL:https://api.openai.com/v1)openai-compatible(default base URL:http://localhost:1234)
The OpenAI-compatible path matches local servers such as LM Studio that expose the
OpenAI-style /v1 API.
Release packaging is configured for:
x86_64-unknown-linux-gnuaarch64-unknown-linux-gnux86_64-apple-darwinaarch64-apple-darwin
These are the Linux and macOS targets configured for cargo-dist release builds.
The shell installer URL only works when the published GitHub Release includes an uploaded
tnav-installer.sh asset. A manually created release or a release that only contains GitHub's
auto-generated source archives will still make releases/latest/download/tnav-installer.sh
return 404.
The current release pipeline generates that installer with cargo-dist 0.31.0.
For now, build from source:
cargo buildAfter the next successful GitHub Release is published, the shell installer will be available at:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/noizbuster/tnav/releases/latest/download/tnav-installer.sh | shtnav connect
tnav model
tnav show current directory
tnavtnav connectadds or manages an LLM provider instance.tnav modelselects a model for the active provider. If you pass a value, it saves that model directly; if you omit it,tnavqueries the provider and prompts you to choose.tnav show current directorysends a natural-language request to the configured LLM.- Plain
tnavopens an interactive prompt for the request text.
In interactive mode, plain tnav can guide you into connect and model setup automatically if nothing is configured yet.
tnav init
tnav auth login
tnav doctorTypical usage:
- run
tnav initto create a saved profile - if the profile uses OAuth, finish sign-in with
tnav auth login - if the profile uses API keys,
initcan capture one immediately andtnav auth api-keycan replace it later - run
tnav doctorto confirm config, keyring access, browser capability, localhost callback binding, and current auth state
init is an interactive wizard that writes profile config to config.toml and can store a secret in the system keyring.
The wizard currently supports these setup paths:
- API key only
- OAuth only
- both OAuth and API key
Provider choices exposed by the wizard today:
- API key profiles: OpenAI, Anthropic, or a custom provider name/base URL
- OAuth profiles: GitHub or a fully custom OAuth provider
For custom OAuth providers, the wizard prompts for:
- provider name
- optional base URL
- OAuth client ID
- authorization URL
- token URL
- optional revocation URL
- default scopes
- redirect host and redirect path
- whether the browser should open automatically during login
Stores or replaces an API key for the selected profile in secure storage. If a key already exists, tnav asks before overwriting unless --yes is provided.
Runs a PKCE-based OAuth authorization-code flow for OAuth profiles:
- load the selected profile
- validate provider configuration
- start a localhost callback server on a loopback address and ephemeral port
- build the authorization URL with PKCE and CSRF state
- open the browser automatically, or print the URL when browser launch is disabled or fails
- wait for the callback
- exchange the authorization code for tokens
- store token data and metadata in the system keyring
OAuth redirect hosts are validated as loopback-only values such as 127.0.0.1, localhost, or ::1.
doctor checks the current environment and selected profile state. The implemented checks include:
- whether profile config exists and loads
- whether the system keyring is reachable
- whether a browser opener appears to be available
- whether
tnavcan bind a localhost callback socket - whether the active or requested profile resolves correctly
- whether the expected API key or OAuth token is present
- whether stored OAuth metadata indicates an expired token
connect manages llm.toml through an interactive menu. The current flow can:
- add a new provider instance
- connect an existing provider instance
- edit a provider instance name or base URL
- replace the stored OpenAI API key for an OpenAI instance
- delete a provider instance
Multiple instances of the same provider type are supported through unique saved names.
Sets the model for the active LLM provider.
- with an argument: saves that model name directly
- without an argument: calls the active provider's model-list endpoint and prompts you to choose
The prompt flow currently does all of the following:
- builds an LLM request that includes shell, OS, OS version, and architecture context
- asks the configured provider for shell code
- streams the response in interactive mode when the provider supports streaming
- shows a command preview
- lets you execute, edit, or cancel
- executes approved commands via
sh -c
If no LLM provider or model is configured, interactive prompt mode can bootstrap you into tnav connect and tnav model first.
tnav intentionally separates non-secret config from secrets.
Profile/auth configuration is stored in config.toml, resolved through the directories crate under the com/noizbuster/tnav app identity.
Current contents include:
- active profile name
- per-profile provider name
- auth method (
api_keyoroauth) - optional base URL
- default scopes
- OAuth endpoints and client ID
- redirect host and redirect path
- UI preference for automatic browser opening
Commands that operate on this file honor --config.
LLM connection state is stored separately in llm.toml in the standard config directory.
Current contents include:
- active LLM provider instance name
- list of configured provider instances
- provider kind
- saved model name
- optional custom base URL
- request timeout value
The current implementation loads and saves llm.toml from the standard config location; there is no separate CLI flag to override it.
Secrets are stored via the system keyring using the tnav service name.
Stored secret types include:
- profile API keys
- OAuth access tokens
- OAuth refresh tokens
- OAuth token metadata used for expiry checks
- OpenAI provider API keys for
tnav connect
On Unix, config directories are created with 0700 permissions and config files with 0600 permissions where supported.
The CLI has partial non-interactive support in the current implementation.
tnav initrequires interactive answerstnav auth api-keyrequires an interactive key prompttnav connectis currently interactivetnav model some-modelcan be used directly if a provider is already configuredtnav <request>can run non-interactively when provider and model are already configured- plain
tnavrequires interactive prompt entry
- rerun with
--no-browserto print the authorization URL instead - install a browser opener utility on Linux or set
BROWSER - complete the browser step on the same machine because the callback listener is local
- rerun
tnav doctor - verify your desktop/session keyring is available
- API key storage, OAuth login, and OpenAI provider auth depend on working keyring access
- run
tnav connectto create an LLM provider instance - run
tnav modelto save a model for the active provider - retry the prompt request after both steps succeed
- confirm loopback networking is allowed on the current machine
- retry in an environment that can bind localhost sockets
- verify the configured redirect host is still a loopback address
There is currently no built-in tnav uninstall command.
If you installed tnav from a GitHub release with tnav-installer.sh, the default install path is $CARGO_HOME/bin or $HOME/.cargo/bin.
rm -f "${CARGO_HOME:-$HOME/.cargo}/bin/tnav"
rm -f "${XDG_CONFIG_HOME:-$HOME/.config}/tnav/tnav-receipt.json"- if you installed with
TNAV_INSTALL_DIRorTNAV_UNMANAGED_INSTALL, removetnavfrom that custom directory instead - if you no longer want the installer's PATH hook, remove any lines that source
"${CARGO_HOME:-$HOME/.cargo}/env"from.profile,.bashrc,.bash_profile,.bash_login,.zshrc, or.zshenv - if you use fish, also remove
~/.config/fish/conf.d/tnav.env.fish - only remove shared Cargo home files such as
"${CARGO_HOME:-$HOME/.cargo}/env"or"${CARGO_HOME:-$HOME/.cargo}/env.fish"if no other Cargo-installed tools depend on them
- delete
config.tomlandllm.tomlfrom thetnavconfig directory; on Linux and macOS this is typically${XDG_CONFIG_HOME:-$HOME/.config}/tnav - remove any saved
tnaventries from your system keyring if you want to clear stored API keys and OAuth tokens too
- if you only ran
cargo build, removetarget/ - if you installed with
cargo install --path ., runcargo uninstall tnav - if you manually copied the built binary somewhere on your
PATH, remove that copy directly
- Rust stable toolchain
- a working C compiler (
cc) for native dependencies
Install Rust with rustup if needed:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default stable
rustup updateThese match the current CI workflow:
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features
cargo check --all-featuresUseful local commands while developing:
cargo run -- --help
cargo run -- doctor
cargo run -- connect
cargo run -- model- CI runs format, clippy, tests, and
cargo check - tagged release builds use the dist-generated
Releaseworkflow fromcargo-dist0.31.0via thedistCLI - the
Releaseworkflow can also be started manually withworkflow_dispatch; pass a tag likev0.1.5to publish or leave the defaultdry-runvalue to validate planning only - release jobs authenticate GitHub release operations with the repository secret
PERSONAL_ACCESS_TOKEN - the pushed release tag must match the current package version in
Cargo.tomlordistwill refuse to announce the release - a successful release publish run will create the GitHub Release and attach per-target archives, checksums, and a shell installer
Useful local release-tooling checks:
dist manifest --tag vX.Y.Z --artifacts=all --no-local-paths --output-format=json
dist build --tag vX.Y.Z --target x86_64-unknown-linux-gnu --artifacts=all --allow-dirtySupervised by NoizBuster, Written by OpenCode

