Changelog

Every release of the Servonaut CLI, straight from GitHub.

Atom feed

v2.30.0rc3 #

Pre-release

What's Changed

  • ci(leak-guard): allow OpenSSH key types and synthetic Team Vault test ids by @zoltanam in #219
  • feat(ai): show the hosted AI money balance and each turn's cost by @zoltanam in #220
  • feat(teams): add the Team Vault and SSH certificate authority by @zoltanam in #221
  • ci(standalone): build with the Python version the size baseline records by @zoltanam in #222
  • feat(vault): guide new users to their next vault step by @zoltanam in #223
  • feat(vault): bind personal vault keys to custom servers by @zoltanam in #224
  • feat(vault): resolve exposures only once the old key is gone by @zoltanam in #225
  • fix(vault): keep signing after a device creates its SSH key by @zoltanam in #226
  • fix(vault): clearer CLI key import, vault names and certificate errors by @zoltanam in #227
  • feat(ui): Servonaut colour themes and full colour in the desktop app by @zoltanam in #228
  • feat(ui): theme colours for all TUI text, and window edges that match the theme by @zoltanam in #229
  • fix(ca): say SSH certificates are coming soon when the service has them off by @zoltanam in #230
  • fix(ca): let the CLI carry a team SSH CA rollover through by @zoltanam in #231
  • feat(vault): pick vaults, keys, teams and servers instead of typing ids by @zoltanam in #232
  • feat(login): say which client and platform is asking to sign in by @zoltanam in #233
  • fix(vault): show progress on slow vault actions and guide device recovery by @zoltanam in #235

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.29.0...v2.30.0rc3

View on GitHub

v2.30.0rc2 #

Pre-release

What's Changed

  • ci(leak-guard): allow OpenSSH key types and synthetic Team Vault test ids by @zoltanam in #219
  • feat(ai): show the hosted AI money balance and each turn's cost by @zoltanam in #220
  • feat(teams): add the Team Vault and SSH certificate authority by @zoltanam in #221
  • ci(standalone): build with the Python version the size baseline records by @zoltanam in #222
  • feat(vault): guide new users to their next vault step by @zoltanam in #223
  • feat(vault): bind personal vault keys to custom servers by @zoltanam in #224
  • feat(vault): resolve exposures only once the old key is gone by @zoltanam in #225
  • fix(vault): keep signing after a device creates its SSH key by @zoltanam in #226
  • fix(vault): clearer CLI key import, vault names and certificate errors by @zoltanam in #227
  • feat(ui): Servonaut colour themes and full colour in the desktop app by @zoltanam in #228
  • feat(ui): theme colours for all TUI text, and window edges that match the theme by @zoltanam in #229
  • fix(ca): say SSH certificates are coming soon when the service has them off by @zoltanam in #230
  • fix(ca): let the CLI carry a team SSH CA rollover through by @zoltanam in #231
  • feat(vault): pick vaults, keys, teams and servers instead of typing ids by @zoltanam in #232
  • feat(login): say which client and platform is asking to sign in by @zoltanam in #233
  • fix(vault): show progress on slow vault actions and guide device recovery by @zoltanam in #235

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.29.0...v2.30.0rc2

View on GitHub

v2.30.0rc1 #

Pre-release

What's Changed

  • ci(leak-guard): allow OpenSSH key types and synthetic Team Vault test ids by @zoltanam in #219
  • feat(ai): show the hosted AI money balance and each turn's cost by @zoltanam in #220
  • feat(teams): add the Team Vault and SSH certificate authority by @zoltanam in #221
  • ci(standalone): build with the Python version the size baseline records by @zoltanam in #222
  • feat(vault): guide new users to their next vault step by @zoltanam in #223
  • feat(vault): bind personal vault keys to custom servers by @zoltanam in #224
  • feat(vault): resolve exposures only once the old key is gone by @zoltanam in #225
  • fix(vault): keep signing after a device creates its SSH key by @zoltanam in #226
  • fix(vault): clearer CLI key import, vault names and certificate errors by @zoltanam in #227
  • feat(ui): Servonaut colour themes and full colour in the desktop app by @zoltanam in #228
  • feat(ui): theme colours for all TUI text, and window edges that match the theme by @zoltanam in #229
  • fix(ca): say SSH certificates are coming soon when the service has them off by @zoltanam in #230
  • fix(ca): let the CLI carry a team SSH CA rollover through by @zoltanam in #231
  • feat(vault): pick vaults, keys, teams and servers instead of typing ids by @zoltanam in #232
  • feat(login): say which client and platform is asking to sign in by @zoltanam in #233
  • fix(vault): show progress on slow vault actions and guide device recovery by @zoltanam in #235

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.29.0...v2.30.0rc1

View on GitHub

v2.29.0 #

What's Changed

  • fix(desktop): separate the desktop app's launcher from the terminal shortcut by @zoltanam in #202
  • feat: multiple accounts per cloud provider by @zoltanam in #203
  • fix(tui): keep the fleet memory hint to one line by @zoltanam in #207
  • fix(tui): finish loading a settings panel before asking to discard changes by @zoltanam in #210
  • fix(tui): never ask to discard changes in a settings panel that has not loaded by @zoltanam in #211
  • fix(cli): apply connection rules and check names against every account by @zoltanam in #209
  • docs(security): add private vulnerability reporting policy by @zoltanam in #214
  • fix(storage): find OVH buckets in every region by @zoltanam in #215
  • fix(settings): edit each OVH and Hetzner account in one place by @zoltanam in #216
  • fix(ci): raise master's version on the current master when promoting by @zoltanam in #217
  • docs: install from the latest release instead of the default branch by @zoltanam in #218

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.28.0...v2.29.0

View on GitHub

v2.29.0rc1 #

Pre-release

What's Changed

  • fix(desktop): separate the desktop app's launcher from the terminal shortcut by @zoltanam in #202
  • feat: multiple accounts per cloud provider by @zoltanam in #203
  • fix(tui): keep the fleet memory hint to one line by @zoltanam in #207
  • fix(tui): finish loading a settings panel before asking to discard changes by @zoltanam in #210
  • fix(tui): never ask to discard changes in a settings panel that has not loaded by @zoltanam in #211
  • fix(cli): apply connection rules and check names against every account by @zoltanam in #209
  • docs(security): add private vulnerability reporting policy by @zoltanam in #214
  • fix(storage): find OVH buckets in every region by @zoltanam in #215
  • fix(settings): edit each OVH and Hetzner account in one place by @zoltanam in #216
  • fix(ci): raise master's version on the current master when promoting by @zoltanam in #217
  • docs: install from the latest release instead of the default branch by @zoltanam in #218

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.28.0...v2.29.0rc1

View on GitHub

v2.28.0 #

What's Changed

  • fix(runtime): correct pipx updates, terminal launch and relay errors by @zoltanam in #138
  • fix(config): keep backups beside their config, and isolate the test suite by @zoltanam in #166
  • fix(mcp): enforce guard tiers and keep standard-tier commands read-only by @zoltanam in #146
  • fix(ssh): never read a remote command, path or host as a local ssh option by @zoltanam in #160
  • fix(ssh): connect to a custom server's own port everywhere by @zoltanam in #152
  • fix(relay): bound the listener's shutdown and release its lock reliably by @zoltanam in #153
  • fix(aws): correct CloudWatch time windows and filters, and CloudTrail row selection by @zoltanam in #157
  • fix(relay): stop on an expired session and refresh a rejected hub token by @zoltanam in #161
  • fix(auth): revoke a session only on the API's verdict, and share refreshes safely by @zoltanam in #165
  • fix(mcp): audit every call to the OVH read tools by @zoltanam in #172
  • fix(app): stop a Settings crash, fix CLI hints and add endpoint overrides by @zoltanam in #151
  • fix(scan): scan custom servers, and fix notes, the ssh command and the log browser by @zoltanam in #163
  • fix(ai): keep chat turns alive through tool prompts, and say what happens by @zoltanam in #168
  • fix(config): make config backups and restores reliable, and keep cache ages sane by @zoltanam in #169
  • fix: memory sharing, saved database profiles and cached instance lookups by @zoltanam in #176
  • fix(providers): stop manager crashes, report failed refreshes, and confirm disruptive power actions by @zoltanam in #178
  • fix(demo): hide real identifiers on every screen, act on the real servers, and scrub bug reports by @zoltanam in #179
  • fix(ssh): verify server host keys, trusting a server on first connection by @zoltanam in #167
  • fix(auth): refuse API, MCP and relay URLs that would send tokens in clear text by @zoltanam in #175
  • fix: stop screen headers and voice stops from failing mid-transition, and steady timing-sensitive tests by @zoltanam in #181
  • fix: redraw fleet table rows once column widths are known, and steady the end-to-end journeys by @zoltanam in #184
  • ci(release): cut release candidates and publish only after approval by @zoltanam in #182
  • fix(ui): fit CloudWatch filters, Top IPs and server actions on small terminals by @zoltanam in #186
  • docs: install release candidates, and installer fixes by @zoltanam in #187
  • fix(voice): download speech models from pinned, checksum-verified sources by @zoltanam in #190
  • fix(ui): usable Settings, memory and IP Ban screens on small terminals by @zoltanam in #191
  • fix(ui): dim behind dialogs, fit the fleet table and footer, accurate help keys by @zoltanam in #189
  • fix(install): install only released versions; attach the install scripts to releases by @zoltanam in #200

New Contributors

  • @dependabot[bot] made their first contribution in #143

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.27.0...v2.28.0

View on GitHub

v2.28.0rc2 #

Pre-release

What's Changed

  • fix(runtime): correct pipx updates, terminal launch and relay errors by @zoltanam in #138
  • fix(config): keep backups beside their config, and isolate the test suite by @zoltanam in #166
  • fix(mcp): enforce guard tiers and keep standard-tier commands read-only by @zoltanam in #146
  • fix(ssh): never read a remote command, path or host as a local ssh option by @zoltanam in #160
  • fix(ssh): connect to a custom server's own port everywhere by @zoltanam in #152
  • fix(relay): bound the listener's shutdown and release its lock reliably by @zoltanam in #153
  • fix(aws): correct CloudWatch time windows and filters, and CloudTrail row selection by @zoltanam in #157
  • fix(relay): stop on an expired session and refresh a rejected hub token by @zoltanam in #161
  • fix(auth): revoke a session only on the API's verdict, and share refreshes safely by @zoltanam in #165
  • fix(mcp): audit every call to the OVH read tools by @zoltanam in #172
  • fix(app): stop a Settings crash, fix CLI hints and add endpoint overrides by @zoltanam in #151
  • fix(scan): scan custom servers, and fix notes, the ssh command and the log browser by @zoltanam in #163
  • fix(ai): keep chat turns alive through tool prompts, and say what happens by @zoltanam in #168
  • fix(config): make config backups and restores reliable, and keep cache ages sane by @zoltanam in #169
  • fix: memory sharing, saved database profiles and cached instance lookups by @zoltanam in #176
  • fix(providers): stop manager crashes, report failed refreshes, and confirm disruptive power actions by @zoltanam in #178
  • fix(demo): hide real identifiers on every screen, act on the real servers, and scrub bug reports by @zoltanam in #179
  • fix(ssh): verify server host keys, trusting a server on first connection by @zoltanam in #167
  • fix(auth): refuse API, MCP and relay URLs that would send tokens in clear text by @zoltanam in #175
  • fix: stop screen headers and voice stops from failing mid-transition, and steady timing-sensitive tests by @zoltanam in #181
  • fix: redraw fleet table rows once column widths are known, and steady the end-to-end journeys by @zoltanam in #184
  • ci(release): cut release candidates and publish only after approval by @zoltanam in #182
  • fix(ui): fit CloudWatch filters, Top IPs and server actions on small terminals by @zoltanam in #186
  • docs: install release candidates, and installer fixes by @zoltanam in #187
  • fix(voice): download speech models from pinned, checksum-verified sources by @zoltanam in #190
  • fix(ui): usable Settings, memory and IP Ban screens on small terminals by @zoltanam in #191
  • fix(ui): dim behind dialogs, fit the fleet table and footer, accurate help keys by @zoltanam in #189
  • fix(install): install only released versions; attach the install scripts to releases by @zoltanam in #200

New Contributors

  • @dependabot[bot] made their first contribution in #143

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.27.0...v2.28.0rc2

View on GitHub

v2.28.0rc1 #

Pre-release

What's Changed

  • fix(runtime): correct pipx updates, terminal launch and relay errors by @zoltanam in #138
  • fix(config): keep backups beside their config, and isolate the test suite by @zoltanam in #166
  • fix(mcp): enforce guard tiers and keep standard-tier commands read-only by @zoltanam in #146
  • fix(ssh): never read a remote command, path or host as a local ssh option by @zoltanam in #160
  • fix(ssh): connect to a custom server's own port everywhere by @zoltanam in #152
  • fix(relay): bound the listener's shutdown and release its lock reliably by @zoltanam in #153
  • fix(aws): correct CloudWatch time windows and filters, and CloudTrail row selection by @zoltanam in #157
  • fix(relay): stop on an expired session and refresh a rejected hub token by @zoltanam in #161
  • fix(auth): revoke a session only on the API's verdict, and share refreshes safely by @zoltanam in #165
  • fix(mcp): audit every call to the OVH read tools by @zoltanam in #172
  • fix(app): stop a Settings crash, fix CLI hints and add endpoint overrides by @zoltanam in #151
  • fix(scan): scan custom servers, and fix notes, the ssh command and the log browser by @zoltanam in #163
  • fix(ai): keep chat turns alive through tool prompts, and say what happens by @zoltanam in #168
  • fix(config): make config backups and restores reliable, and keep cache ages sane by @zoltanam in #169
  • fix: memory sharing, saved database profiles and cached instance lookups by @zoltanam in #176
  • fix(providers): stop manager crashes, report failed refreshes, and confirm disruptive power actions by @zoltanam in #178
  • fix(demo): hide real identifiers on every screen, act on the real servers, and scrub bug reports by @zoltanam in #179
  • fix(ssh): verify server host keys, trusting a server on first connection by @zoltanam in #167
  • fix(auth): refuse API, MCP and relay URLs that would send tokens in clear text by @zoltanam in #175
  • fix: stop screen headers and voice stops from failing mid-transition, and steady timing-sensitive tests by @zoltanam in #181
  • fix: redraw fleet table rows once column widths are known, and steady the end-to-end journeys by @zoltanam in #184
  • ci(release): cut release candidates and publish only after approval by @zoltanam in #182
  • fix(ui): fit CloudWatch filters, Top IPs and server actions on small terminals by @zoltanam in #186
  • docs: install release candidates, and installer fixes by @zoltanam in #187
  • fix(voice): download speech models from pinned, checksum-verified sources by @zoltanam in #190
  • fix(ui): usable Settings, memory and IP Ban screens on small terminals by @zoltanam in #191
  • fix(ui): dim behind dialogs, fit the fleet table and footer, accurate help keys by @zoltanam in #189
  • fix(install): install only released versions; attach the install scripts to releases by @zoltanam in #200

New Contributors

  • @dependabot[bot] made their first contribution in #143

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.27.0...v2.28.0rc1

View on GitHub

Older releases

v2.26.2

What's Changed

  • Refuse a second release on the same day by @zoltanam in #105
  • Cut releases from a workflow by @zoltanam in #107
  • Release only when something that ships has changed by @zoltanam in #108
  • fix: preserve OVH targets while redacting demo views by @zoltanam in #109
  • fix: simplify OVH monitoring and preserve dashboard metrics by @zoltanam in #110

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.26.1...v2.26.2

View on GitHub
v2.26.1

Highlights

A follow-up to the CloudTrail paging that shipped in 2.26.0.

  • Next now leaves you where you were. Reading to the end of the loaded events and pressing Next pulled the next page from the window, then reset the table to page one — so everything you had already read had to be paged through again to get back. Paging into a long window is the whole point of Next, so it now keeps your place, and any filter you had picked stays applied.

Upgrade with pipx upgrade servonaut.

What's Changed

  • Keep your place in CloudTrail when Next loads more events by @zoltanam in #104

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.26.0...v2.26.1

View on GitHub
v2.26.0

Highlights

CloudTrail is the story of this release. The browser failed every fetch on many accounts, its filters had to be typed from memory, and combining two of them quietly returned the wrong rows. All three are fixed, and reading a long window no longer means waiting for it.

CloudTrail

  • The browser failed every fetch on some accounts. Event resource entries do not always carry a type, and the parser assumed they did, so a single such event ended the fetch with an error and an empty table. AssumeRole events from instance profiles have exactly that shape, which makes this common on any account using instance roles or Systems Manager.
  • Filters are pickers now. Event, user and resource type are built from the events you fetched, each option showing how many times it occurs, sorted by frequency. Choosing one narrows what is on screen immediately. Each picker also offers to type a value, for something further back in the window than the events you have loaded; a typed value is searched for across the whole range.
  • Combining filters used to lie. The lookup API honours only the first filter you send and silently ignores the rest, so asking for an event name and a user returned rows matching just one of them. Filters are now applied faithfully, and they combine, which the API on its own cannot do. This applies to the agent tool as well.
  • Reading on. The fetch stops at a cap so it stays fast, but Next now reads the next page instead of going dead at the end, about two seconds for another 500 events, and everything you load joins the pickers. The cap default is 500, and an existing config is migrated from the old 100 on first launch, keeping a backup. A number you chose yourself is left alone.
  • Machine names instead of hex. An instance-role session is named after the instance id, so the user column read as a wall of identifiers. An id Servonaut recognises now shows the instance's name; filtering still works on the id behind it.

Demo mode

  • The AWS launch wizard, the IP Ban screen and CloudTrail no longer show real key-pair names, security-group names, ban-configuration names or instance ids while demo mode is on.
  • A scripted chat conversation can be replayed instead of calling the AI gateway, for recordings and offline demos. See docs/demo-mode.md.

Also fixed

  • The log viewer honours a server's memory opt-out when choosing which log to open first, instead of using the cached list.

Upgrade with pipx upgrade servonaut. Your configuration is migrated automatically on first launch, and the previous file is kept alongside it.

What's Changed

  • fix: log viewer honours memory opt-outs when choosing its first log by @zoltanam in #100
  • feat: demo mode can replay a scripted chat conversation by @zoltanam in #101
  • fix: CloudTrail browser failures and demo-mode gaps in the AWS screens by @zoltanam in #102
  • feat: CloudTrail filters you can pick or type, paging that keeps going, and combining that no longer lies by @zoltanam in #103

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.25.4...v2.26.0

View on GitHub
v2.25.4

Highlights

This release fixes a problem for everyone who installed the MCP server into Claude Code with servonaut --mcp-install claude on v2.25.1 or later. Variables that were not set when the agent started reached the server as empty strings. That made every AWS call from the MCP server fail and, on the first instance lookup after the cache had expired, replaced the instance cache with an empty list, so the TUI showed no AWS instances until a forced refresh.

  • The MCP server now ignores empty forwarded environment variables at startup and logs which ones it ignored.
  • A failed AWS or OVH inventory fetch keeps the previous cache instead of overwriting it. A partial fetch is shown but not persisted.
  • The instance list and the MCP list_instances tool say when they are showing cached rows.
  • Empty SERVONAUT_API_URL and SERVONAUT_MCP_URL overrides fall back to the defaults.

Upgrade with pipx upgrade servonaut and let your agent restart the MCP server. No reinstall of the agent configuration is needed; the cache refills on the next successful fetch, or immediately with R in the TUI.

Also in this release: demo mode keeps every action on the real server while the screen stays redacted, Ctrl+P gains a "Go to …" entry for every sidebar section, and the TUI honours --config.

What's Changed

  • fix: ignore empty forwarded env vars and never persist a failed inventory fetch by @zoltanam in #99

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.25.3...v2.25.4

View on GitHub
v2.25.3

What's Changed

  • feat: improve memory summaries and sync UX by @zoltanam in #96

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.25.2...v2.25.3

View on GitHub
v2.25.2

What's Changed

  • fix: stabilize scan results and memory dashboard state by @zoltanam in #95

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.25.1...v2.25.2

View on GitHub
v2.25.1

What's Changed

  • fix(findings): card titles, confirm-modal labelling, undo plan, and non-AWS IP blocking by @zoltanam in #93
  • fix(mcp): harden cross-client reliability by @zoltanam in #94

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.25.0...v2.25.1

View on GitHub
v2.25.0

What's Changed

  • feat(voice): spoken replies and hands-free voice conversation mode by @zoltanam in #91

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.24.4...v2.25.0

View on GitHub
v2.24.4

What's Changed

  • feat(mcp): add codex and agy install targets by @zoltanam in #90

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.24.3...v2.24.4

View on GitHub
v2.24.3

What's Changed

  • fix(mcp): name the secret-store backend in DB tool messages by @zoltanam in #89

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.24.2...v2.24.3

View on GitHub
v2.24.2

What's Changed

  • test(mcp): make the SDK guard fail only on real incompatibilities by @zoltanam in #87
  • fix(s3): honour the target region for bucket and object operations by @zoltanam in #88

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.24.1...v2.24.2

View on GitHub
v2.24.1

Patch release fixing an MCP server that could not start on fresh installs.

Fixed

MCP server failed to start on new installs. The mcp extra allowed any SDK version, so recent installs of servonaut[mcp] or servonaut[all] resolved the 2.x SDK, which removed the server API this release builds on. servonaut --mcp then exited with:

AttributeError: 'Server' object has no attribute 'list_tools'

The dependency is now capped below 2.x, so installs resolve a working SDK. Support for the 2.x SDK will come in a later release.

If your MCP server stopped starting, upgrading to this version fixes it. To repair an existing environment without upgrading:

pipx runpip servonaut install 'mcp<2'

Soft-cap badge named the model it downgraded to. When a Servonaut AI user hit the soft token cap, the chat panel's stats bar read downgraded to <model id>. It now reads downgraded to faster model.

Also in this release

Coverage for the MCP SDK surface the server depends on, so an incompatible SDK is caught by the test suite rather than at start-up.

View on GitHub
v2.24.0

Voice input

You can now dictate into the AI chat panel instead of typing. Press the microphone button in the chat input row, or ctrl+t while the input is focused.

Speech is transcribed entirely on your machine — audio never leaves the workstation and no transcription service is contacted.

The feature is opt-in and downloads nothing by default. Enable it in Settings → AI → Voice Input, which walks through the four things it needs (Python packages, the PortAudio system library, a microphone, and model weights) and offers the action for whichever is missing.

Two engines to choose from

Whisper — records, then transcribes. Smaller download, very accurate.

pip install 'servonaut[voice]'

Nemotron streaming — decodes as the audio arrives, so words appear in the input box while you are still speaking, and already-shown words are never rewritten. Larger download, with a configurable chunk size trading a little accuracy for how soon words appear.

pip install 'servonaut[voice-streaming]'

Both also need the PortAudio system library (sudo apt install libportaudio2, brew install portaudio) and a one-time model download, both surfaced in the settings panel.

Review before sending, by default

The transcript lands in the input box for you to check and send. It is never sent automatically unless you switch on Send dictation automatically — off by default, because the assistant can run commands and a misheard word would otherwise reach it unedited.

Recordings are capped (60 seconds by default), and a dictation that hits the cap tells you the tail was dropped rather than silently truncating. Dictated text is redacted in demo mode like every other on-screen surface.

Model disk management

Speech models are several hundred megabytes each. The settings panel lists every model on disk with its size, marks which one is in use, and offers to remove the rest. Switching engine or model size prompts directly afterwards, naming what is now unused and how much removing it would reclaim — nothing is deleted without confirmation.

Full guide: docs/voice-input.md

View on GitHub
v2.23.8

What's Changed

  • feat(mcp): add read-only security_audit probe by @zoltanam in #82
  • feat(remediation): add rate_limit / rate_limit_path WAF executors by @zoltanam in #78
  • feat(remediation): add enable_service, reload_service, disable_service executors by @zoltanam in #79
  • feat(mcp): add read-only service_state probe by @zoltanam in #80
  • feat(remediation): add raise_container_memory executor by @zoltanam in #81
  • feat(remediation): add set_config_value + fix_permissions executors by @zoltanam in #83

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.23.7...v2.23.8

View on GitHub
v2.23.7

Added: one-click restart and start for Docker containers

When a proactive scan finds a container that's unhealthy, crash-looping, or a stopped compose service, Servonaut can now restart or start it for you — with the same safety flow as every other remediation: a server-signed preview, a dry-run that inspects the container before anything changes, and a typed confirmation before the live command runs. The action only ever targets the exact container named in the finding's evidence.

Proven end-to-end against a live server before release.

View on GitHub
v2.23.6

Added: one-click restart for a failed systemd service

When a proactive scan finds a systemd unit in a failed state, Servonaut can now restart it for you — with the same safety flow as every other remediation: a server-signed preview, a dry-run that checks the unit before anything changes, and a typed confirmation before the live systemctl restart runs. The action only ever targets the exact unit named in the finding's evidence.

Proven end-to-end against a live server before release.

View on GitHub
v2.23.5

Fixed

The MCP server no longer disconnects when a tool errors. An unhandled exception inside a tool handler (for example a transient network failure) could tear down the stdio connection and drop the whole session. The dispatch now catches handler failures and returns a bounded error result, so a single failing call no longer takes down the server — only that call is affected. Cooperative cancellation still propagates as before.

Upgrade: pipx upgrade servonaut (or pip install -U servonaut).

View on GitHub
v2.23.4

Added

Undo for a completed remediation — when a finding's remediation is revertible, its detail view now shows an "Undo remediation…" button that reverses it.

  • Runs the same server-signed preview → typed-confirm → execute → poll flow as remediations: nothing changes until you confirm the exact reversal command.
  • Reuses the typed-RUN confirmation gate; a dry-run option previews the reversal without touching anything.
  • The button appears only when the server reports a finding's remediation as revertible, so it never renders an action that can't run.

Upgrade: pipx upgrade servonaut (or pip install -U servonaut).

View on GitHub
v2.23.3

Added

unblock_ip remediation executor — the inverse of block_ip, so an IP ban applied by the proactive-remediation flow can be reverted.

  • Same AWS-vs-on-box split as block_ip, dispatched on the ban method: AWS methods (WAF / security group / NACL) go through the IP-ban service, which re-resolves the rule from the IP; on-box methods (nftables / ufw / firewalld) remove the box firewall rule over SSH and verify the IP is no longer in the ruleset.
  • Idempotent: unbanning an IP that isn't banned succeeds (the goal state already holds) rather than erroring, so repeating the operation or reverting an expired ban is safe.
  • On-box argv is built locally from the validated IP; dry-run reports what would change and mutates nothing. The remediation verb allowlist stays closed — unknown verbs are refused.

Upgrade: pipx upgrade servonaut (or pip install -U servonaut).

View on GitHub
v2.23.2

Performance

Settings opens ~8× faster — roughly 4 seconds down to about half a second.

Opening Settings built and mounted all 24 panels (~1100 widgets) up front, then immediately hid 23 of them. Because each mount re-applies the stylesheet against a growing widget tree, the cost was closer to (number of mounts × tree size) than to 24 × one panel — so you paid for every panel to render just to look at one.

Panels are now built the first time you select them, and cached from then on:

before after
Open Settings ~3996 ms ~524 ms
First visit to a panel — 335–647 ms
Revisit — 98–242 ms

The cost is moved rather than erased: you pay for a panel the first time you open it, and never for the panels you don't. Every panel stays reachable and searchable before it's built, per-panel Save and the unsaved-changes guard are unaffected.

Upgrade: pipx upgrade servonaut (or pip install -U servonaut).

View on GitHub
v2.23.1

Fixed

Background fleet memory auto-scan now actually runs.

With auto-scan enabled, server memories could stay stale indefinitely — the scan never ran a single cycle. The loop slept the full configured interval (24h by default) before its first scan, and the last-run marker lived only in memory, resetting on every launch. Since the app rarely stays open for a whole interval, each restart restarted the clock and the first cycle never arrived.

  • Persistent schedule — the last-run timestamp is now stored in ~/.servonaut/memory_scan_state.json (atomic write, fails soft), so the schedule survives restarts.
  • Catch-up on launch — after a short startup grace, if the interval has already elapsed since the last run, the scan runs immediately instead of waiting out another full interval. A cycle skipped because the instance list hasn't loaded retries shortly after rather than burning a full interval.
  • Live status updates — a background cycle now updates the Fleet Memory table in real time (per-instance progress + repopulate) and refreshes the instance-list memory column, without interrupting you with a dialog. The Fleet Memory status line shows a real "next in ~Xh" countdown that persists across restarts.

Upgrade: pipx upgrade servonaut (or pip install -U servonaut). Shortly after your next launch, any stale servers get a catch-up memory scan automatically.

View on GitHub
v2.23.0 — v2.23.0 — Proactive Detection & Remediation

What's Changed

  • feat(findings): proactive-monitoring thin client — inbox, scan, triage by @zoltanam in #59
  • feat(findings): block_ip remediation verb — local IPBanService dispatch by @zoltanam in #63
  • feat(mcp): disk_usage + pending_updates probes, journal_errors failed-units by @zoltanam in #64
  • fix(findings): send method= on block_ip preview + execute by @zoltanam in #66
  • test(catalog): converge disk_usage + pending_updates into the server-catalog fixture by @zoltanam in #65
  • feat(mcp): fail2ban-awareness in auth_log_summary by @zoltanam in #67
  • feat(findings): on-box block_ip methods (nftables/ufw/firewalld) by @zoltanam in #68
  • fix(mcp): decode JSON-string body in api_request POST by @zoltanam in #69

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.22.0...v2.23.0

View on GitHub
v2.22.0

What's Changed

  • DB vault: discover containerized app DBs + per-site coverage & management by @zoltanam in #62

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.21.0...v2.22.0

View on GitHub
v2.21.0

What's Changed

  • feat(bw-ssh): import ~/.ssh keys into the vault + Bitwarden-backed connect for CLI/MCP by @zoltanam in #60
  • test(mcp): genericize a real fixture IP to a documentation range by @zoltanam in #61

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.20.2...v2.21.0

View on GitHub
v2.20.2

What's Changed

  • feat(memory): background fleet auto-scan + server-backed auto-sync + live scan indicators by @zoltanam in #56
  • fix(team): resolve local config via config_manager in share/pull forms by @zoltanam in #57
  • fix(ssh): keepalives + configurable timeouts to stop long-op disconnects by @zoltanam in #58

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.20.1...v2.20.2

View on GitHub
v2.20.1

What's Changed

  • refactor(css): split app.css into ordered styles/ tree + dead-rule cleanup by @zoltanam in #55

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.20.0...v2.20.1

View on GitHub
v2.20.0

What's Changed

  • feat(settings): master/detail side-menu with full config coverage by @zoltanam in #54

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.19.7...v2.20.0

View on GitHub
v2.19.7

What's Changed

  • Glama listing prep: MCP Dockerfile + claimable maintainer in glama.json by @zoltanam in #50
  • docs: add Glama card badge to README by @zoltanam in #51
  • docs: add demo.mp4 alongside demo.gif by @zoltanam in #52
  • feat(config): store SSH key paths as home-relative for portable configs by @zoltanam in #53

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.19.6...v2.19.7

View on GitHub
v2.19.6

What's Changed

Two independent hosted-AI chat fixes (PR #49):

Configurable max_tool_rounds for hosted AI

  • New config key chat_max_tool_rounds (Optional[int], None = use the server default), sent as max_tool_rounds on the chat request and clamped server-side. Lets you raise the agentic tool-loop cap for long investigations.
  • Deliberately a new key rather than reusing chat_max_tool_iterations (which governs only the local/user-keyed provider loop and is persisted as 10 in existing configs).
  • Omitted from the request body when unset, so servers that don't support it ignore the key harmlessly.

Dangerous-tool confirm modal no longer silently denies

  • Pressing Enter with an empty or wrong value on the typed-confirm "RUN" modal previously dismissed it as a denial with no feedback. Now the only ways to deny are Escape or Cancel.
  • An exact RUN (tolerant of surrounding whitespace, still case-sensitive) confirms; any other submitted value keeps the modal open with an inline hint.

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.19.5...v2.19.6

View on GitHub
v2.19.5

What's Changed

  • Demo-mode hardening, chat input fixes, tool-call circuit breaker, README demo GIF by @zoltanam in #48

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.19.4...v2.19.5

View on GitHub
v2.19.4

What's Changed

  • feat(relay): execute AI chat tool calls in the headless connect listener by @zoltanam in #47

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.19.3...v2.19.4

View on GitHub
v2.19.3

What's Changed

  • fix(ovh): per-type VPS monitoring calls + loud failure on empty metrics by @zoltanam in #46

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.19.2...v2.19.3

View on GitHub
v2.19.2

What's Changed

  • feat(mcp): list server in the official MCP Registry by @zoltanam in #44
  • fix(mcp): route ovh_monitoring for manually registered OVH servers by @zoltanam in #45

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.19.1...v2.19.2

View on GitHub
v2.19.1

What's Changed

  • fix(cli): graceful Ctrl+C — Cancelled. + exit 130 instead of tracebacks by @zoltanam in #43

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.19.0...v2.19.1

View on GitHub
v2.19.0

What's Changed

  • feat(cli): headless device-flow login/logout; fail-loud empty AI chat responses by @zoltanam in #41
  • fix(cli): align AI chat/top-up with live gateway; buffered chat defaults tools off by @zoltanam in #42

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.18.0...v2.19.0

View on GitHub
v2.18.0

What's Changed

  • Memory: annotation sync + agent findings, with injection-resistant context framing by @zoltanam in #40

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.17.3...v2.18.0

View on GitHub
v2.17.3

What's Changed

  • ci: add Leak Guard to block internal info entering PRs by @zoltanam in #38
  • chore: scrub internal detail from tracked files (public-repo hygiene) by @zoltanam in #39
  • feat(mcp): opt-in destructive aws_call behind mandatory two-phase confirm by @zoltanam in #37

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.17.2...v2.17.3

View on GitHub
v2.17.2

What's Changed

  • feat(mcp): generic AWS passthrough, Logs Insights, control-plane STS factory by @zoltanam in #36

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.17.1...v2.17.2

View on GitHub
v2.17.1

What's Changed

  • fix(update): reliable in-app updater + resilient version lookup by @zoltanam in #35

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.17.0...v2.17.1

View on GitHub
v2.17.0

What's Changed

  • feat: incident-response tooling (traffic/topology/RDS/WAF/DB diagnostics + mitigation) by @zoltanam in #34

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.16.3...v2.17.0

View on GitHub
v2.16.3

What's Changed

  • fix(update): sidebar update button never appears on authenticated machines by @zoltanam in #33

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.16.2...v2.16.3

View on GitHub
v2.16.2

What's Changed

  • fix: CloudWatch IP info on click, update-button emoji, full sidebar icon consistency by @zoltanam in #32

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.16.1...v2.16.2

View on GitHub
v2.16.1

Fixes

Config Sync — correct passphrase prompt on restore / new machine (#31)

Restoring a synced config snapshot on a brand-new machine no longer mis-prompts you to set a new passphrase. The prompt now correctly shows "Enter Sync Passphrase" for restores (and any device that already has a passphrase), reserving the type-it-twice "Set Sync Passphrase" flow for the genuine first push to an account with zero snapshots.

Previously the prompt keyed off the local probe file, so a fresh machine (no probe) was asked to create a new passphrase — which could never decrypt an existing snapshot, surfacing as a confusing "wrong passphrase / encryption error" even with the correct passphrase.

Crypto is unchanged; cross-machine decryption was always correct given the right passphrase.

View on GitHub
v2.16.0

What's Changed

  • feat(server-actions): per-instance dashboard with live stats, memory snapshot, and inline browse by @zoltanam in #30

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.15.0...v2.16.0

View on GitHub
v2.15.0 — v2.15.0 — chat catalog consumer + dangerous-floor escalation (PR5')

Chat catalog consumer + dangerous-floor escalation — PR5'

Final CLI-side PR of the dynamic-tool-catalog migration. Pairs with the web-side PR1' / PR3' / PR4' work shipped to staging earlier today (2026-05-26 UTC). This release flips capabilities.supports_dynamic_catalog: true on the connect handshake — the signal the hosted backend gates the CHAT_CATALOG_EXPANSION_ENABLED flag-flip on.

What's new

57 new chat-routable tool dispatches. services/ai_tool_bridge.py::_LOCAL_TOOL_HANDLERS now routes every non-relay tool in the server-side catalog to its handler in ServonautTools. Once the staging flag flips, the hosted AI chat panel can invoke any of the following without tool_unavailable errors:

  • AWS describe (6): aws_list_regions, aws_list_amis, aws_list_instance_types, aws_list_key_pairs, aws_list_subnets, aws_list_security_groups
  • AWS lifecycle standard (3): aws_start_instance, aws_stop_instance, aws_reboot_instance
  • AWS lifecycle dangerous (2): aws_run_instances, aws_terminate_instance (MCP-relay surface only — chat surface gates these per the server tier filter + ApprovalService UX deferred to follow-up)
  • S3 read (2): s3_list_buckets, s3_list_objects
  • S3 mutations dangerous (8): s3_create_bucket, s3_delete_bucket, s3_upload_object, s3_delete_object, s3_copy_object, s3_move_object, s3_generate_presigned_url, s3_download_object
  • AWS observability + IP ban (7): cloudwatch_list_log_groups, cloudwatch_get_log_events, cloudwatch_top_ips, cloudtrail_lookup_events, ip_ban_list_configs, ip_ban_list_banned, ip_ban_set
  • Log fetch (1): get_logs
  • Hetzner read + power (8): hetzner_list_servers, hetzner_list_server_types, hetzner_list_ssh_keys, hetzner_power_on, hetzner_power_off, hetzner_shutdown, hetzner_reboot, hetzner_create_ssh_key
  • Hetzner lifecycle dangerous (3): hetzner_create_server, hetzner_delete_server, hetzner_delete_ssh_key
  • OVH read + lifecycle (11): ovh_monitoring, ovh_list_ips, ovh_firewall_rules, ovh_ssh_keys, ovh_snapshots, ovh_dns_records, ovh_billing, ovh_invoices, ovh_start_instance, ovh_stop_instance, ovh_reboot_instance
  • OVH lifecycle dangerous (2): ovh_create_instance, ovh_delete_instance
  • Memory (4): get_server_memory, list_server_memories, build_server_memory, refresh_server_memory

Dangerous-floor escalation is now ACTIVE

The 18-pattern regex floor shipped dormant in v2.14.0 (PR2'). v2.15.0 wires it into the dispatch path:

  • Every tool_call arriving via ai_tool_bridge.handle_tool_call passes through _FloorDangerousMixin._floor_dangerous(name, server_tier) before guard enforcement.
  • If the tool name matches one of the destructive-pattern regexes AND the server-asserted tier is below dangerous, the call is escalated to dangerous tier and the mismatch is audit-logged to ~/.servonaut/mcp_audit.jsonl with reason dangerous_floor_escalation.
  • This is defense-in-depth — a buggy server catalog or wire-shape regression can no longer silently downgrade a destructive operation. The CLI refuses to take the server's word on what counts as dangerous.

This also caught one legitimate pre-existing miss: run_command was historically classified standard for chat-surface dispatch despite being arbitrary shell exec. v2.15.0 escalates it to dangerous automatically. The associated audit-row test was updated to reflect the corrected tier.

Capability bit

capabilities.supports_dynamic_catalog: true on the connect handshake. Hosted backends gate their dynamic-catalog activation on this bit — older CLIs (v2.14.x and earlier) keep receiving the original 9-tool chat surface; v2.15.0+ CLIs receive the expanded catalog.

tool_catalog SSE event handler

services/ai_providers/servonaut_provider.py now handles the tool_catalog SSE event emitted by /api/ai/chat/stream. Audit-only in v2.15.0 — the handler logs receipt to mcp_audit.jsonl with reason tool_catalog_received plus catalog_version, surface, and tool_count. The static _LOCAL_TOOL_HANDLERS map remains source of truth for dispatch; the audit row proves the wire works for a future PR to consume the catalog dynamically without changing v2.15.0 behaviour.

Install / upgrade

pipx install servonaut --force
# or
pipx upgrade servonaut

Full diff

v2.14.0...v2.15.0

What's next

The dynamic-tool-catalog migration is feature-complete on both sides. Staging-flip protocol:

  1. v2.15.0 deployed to PyPI (this release)
  2. Operator runs pipx upgrade servonaut on the staging-connected CLI
  3. v2.15.0 CLI heartbeat carries capabilities.supports_dynamic_catalog: true
  4. Operator flips CHAT_CATALOG_EXPANSION_ENABLED=true on the staging web env var (no redeploy needed, ~10s container restart)
  5. End-to-end test: ask the hosted AI chat "list my EC2 regions" — the 40 newly-chat-exposed tools dispatch through this CLI's ai_tool_bridge to the local mcp/tools.py handlers and return real responses

Once staging E2E is verified, production flag-flip happens on the user's schedule.

View on GitHub
v2.14.0 — v2.14.0 — dynamic-catalog CLI consumer (PR2')

Dynamic-catalog CLI consumer — PR2'

This release is the CLI half of the dynamic-tool-catalog migration. The server side (PR1' on the web backend) shipped to staging on 2026-05-26 00:23 UTC; this release wires the CLI to emit the new connect/heartbeat fields and adds defense-in-depth for any future server-emitted catalog.

No user-visible behaviour change in this release — all wire changes are additive and the server doesn't yet act on the new fields. Defense-in-depth is dormant until v2.15.x (PR5'). The catalog-drift CI gate is the immediate value: any future CLI tool addition now fails CI until the server-side catalog accepts it.

What's new (wire-format v1.0)

The CLI now emits two enriched messages to /api/cli/heartbeat:

// First message on every (re)connect
{
  "type": "cli.handshake",
  "version": "2.14.0",
  "cli_release_channel": "stable",        // or "beta" / "dev"
  "providers_configured": ["aws", "hetzner", "ovh"],
  "capabilities": {"supports_dynamic_catalog": false}
}

// Every 30s afterwards
{"type": "cli.heartbeat", "providers_configured": ["aws", "hetzner"]}
  • providers_configured — array of bare provider names ("aws", "hetzner", "ovh"). Re-resolved on every heartbeat from the running ServonautApp's service state, so mid-session credential changes propagate within ~30s without forcing a reconnect.
  • cli_release_channel — read from the SERVONAUT_RELEASE_CHANNEL env var. Defaults to "stable", falls back to "dev" for editable / pipx-install-from-source builds. Lets server-side telemetry segment usage by release channel.
  • version — servonaut.__version__. Lets the server gate on minimum-CLI-version requirements without a forced upgrade.
  • capabilities.supports_dynamic_catalog — hardcoded false for v2.14.x. Flips to true in v2.15.x (PR5') when the CLI starts honouring a server-emitted tool_catalog SSE event.

Old CLIs without these fields stay fully functional — server-side resolution defaults to "advertise everything for this user," matching pre-PR1' behaviour. Additive contract, no breakage.

Defense-in-depth: dangerous-tool pattern floor

New module servonaut.services.dangerous_tool_floor ships an 18-regex floor that forces the dangerous guard tier for any tool whose name matches a known-destructive pattern:

  • AWS lifecycle: aws_run_instances, aws_terminate_instance
  • S3 mutations + presigned URL: s3_create_bucket, s3_delete_*, s3_upload_object, s3_copy_object, s3_move_object, s3_generate_presigned_url
  • Hetzner / OVH create + delete (prefix-pattern — auto-catches future tools)
  • Cross-provider destructive: deploy, provision, security_scan, run_command, transfer_file, ip_ban_set

When the CLI starts consuming server-emitted catalogs in v2.15.x, any tool whose server-claimed tier is below dangerous AND whose name matches this floor will be escalated to dangerous and the mismatch audit-logged to ~/.servonaut/mcp_audit.jsonl with reason dangerous_floor_escalation. Belt-and-suspenders posture: server catalog can be wrong; CLI refuses to take its word on destructive ops.

Catalog-drift CI gate

tests/test_catalog_drift.py is the new forcing function. Every CI run asserts:

set(server_catalog_v1.json["tools"]) == set(tool_schemas.TOOL_SCHEMAS) - CATALOG_EXCLUDED_CLI_ONLY

where CATALOG_EXCLUDED_CLI_ONLY is the 7-tool set of CLI-local-only meta tools that intentionally don't appear in the hosted-MCP catalog (whoami, relay_status, relay_reconnect, api_request, mcp_tool_call, check_status, get_server_info). Anyone adding a new tool to the CLI must either land it in the server-side catalog in the same PR (via the web-side resolver update) or extend CATALOG_EXCLUDED_CLI_ONLY with a justification — drift fails CI loudly.

Install / upgrade

pipx install servonaut --force
# or
pipx upgrade servonaut

Full diff

v2.13.0...v2.14.0

What's next

PR3' (web-side providers_configured consumer) kicks off on the web backend. Once it deploys to staging, hosted MCP starts filtering its catalog by which providers the user has configured on at least one connected CLI. PR4' (chat-surface unification, brings the same dynamic catalog to the hosted AI chat panel) and PR5' (CLI consumes server-emitted catalog + escalation wired through _floor_dangerous) follow.

View on GitHub
v2.13.0 — v2.13.0 — AWS provider, S3 object storage, 21 new MCP tools

AWS provider, end-to-end

Servonaut now manages AWS EC2 as a first-class provider alongside Hetzner and OVH — full lifecycle in the TUI and full coverage on the MCP server.

TUI

  • AWS sidebar section with ⚙ Manage (per-instance start / stop / reboot / terminate), 📊 CloudWatch, 🔒 IP Ban Manager, and 🔍 CloudTrail — the AWS-specific log + security tools previously lived under their own "Logs & Security" section; they're now grouped where they belong.
  • EC2 launch wizard — full create-instance flow with region / AMI / instance type / keypair / subnet / security-group selection, AMI search with debounce, and Name tag pre-fill.
  • S3 / Object Storage screens for AWS, Hetzner Object Storage, and OVH Object Storage — bucket list, object browser, upload / download / copy / move / delete, presigned-URL generation, breadcrumb navigation. All three providers share the same screen (ObjectStorageScreen(provider=...)).
  • Provider setup wizards now own the S3 form — Hetzner setup adds a Step 4 (Object Storage), OVH setup adds a Step 7. Each setup screen also rebuilds the S3 service on save so the sidebar Object Storage button appears immediately without a restart.
  • Region dropdowns replace free-form text inputs on all three S3 forms. AWS = 21 regions, Hetzner = 3, OVH = 9. Default values selected so the form is never invalid.
  • Docked Save Settings button at the bottom of the Settings screen — no more hunting for Ctrl+S after scrolling through twelve sections.

MCP server (+21 tools)

11 AWS EC2 tools + 10 S3 tools (provider-parameterised across aws / hetzner / ovh):

Tier Tools
readonly aws_list_regions, aws_list_amis, aws_list_instance_types, aws_list_key_pairs, aws_list_subnets, aws_list_security_groups, s3_list_buckets, s3_list_objects
standard aws_start_instance, aws_stop_instance, aws_reboot_instance, s3_download_object
dangerous aws_terminate_instance, aws_run_instances, s3_create_bucket, s3_delete_bucket, s3_upload_object, s3_delete_object, s3_copy_object, s3_move_object, s3_generate_presigned_url

All mutating tools go through the existing audit trail (~/.servonaut/aws_audit.jsonl for AWS lifecycle, ~/.servonaut/mcp_audit.jsonl for the MCP layer). s3_generate_presigned_url is classified dangerous despite being a read — the returned URL is a bearer credential and is masked in the audit row (only the JSON-RPC return value to the agent contains the URL itself).

Sidebar conditional visibility

OVH and Hetzner sections were previously hidden entirely when the compute service wasn't configured — which broke the new "S3-only" use case (configure object-storage credentials without setting up the provider's compute API). Now: section visible when either compute OR S3 is configured, with the compute-only buttons hiding when only S3 is set up. AWS section is always visible per the original design; only its Object Storage button is gated on credential availability.

Fix: config-sync data loss

A separate bug found while testing this PR — Hetzner api_token and OVH application_key / application_secret / consumer_key were being silently wiped on config-sync pull because the preservation loop didn't recurse into nested dataclasses at depth ≥2 and the copy at the top was shallow. Both fixed with deep-copy + materialising depth-aware preservation. Regression tests added.

Under the hood

  • New services/object_storage_service.py — single ObjectStorageService class parameterised by provider; SSRF-guarded endpoint validation (https-only, no @-userinfo, no path/query/fragment, alternate IPv4 encodings rejected via socket.inet_aton), path-traversal validation against (cwd, ~, ~/Downloads).
  • New services/object_storage_factory.py — single build_object_storage_services(config) shared between the TUI app and the headless MCP server so credential-handling logic never drifts between the two surfaces.
  • New services/aws_audit.py — JSONL audit trail with O_NOFOLLOW | O_CREAT | O_APPEND mode 0o600 — symlink-resistant + private by default.
  • New services/object_storage_regions.py — single source of truth for the dropdown options + defaults.
  • config.json is now written atomically with mode 0o600 + O_NOFOLLOW; a leftover symlink at the target is detected and refused rather than silently followed.

Install / upgrade

pipx install servonaut --force
# or for an existing install
pipx upgrade servonaut

Full diff

v2.12.0...v2.13.0

View on GitHub
v2.12.0 — v2.12.0 — Bitwarden Password Manager SSH integration

Bitwarden Password Manager SSH integration

Servonaut now resolves SSH keys from your Bitwarden Password Manager vault through a server-side ref system. Personal and team-scoped refs are supported; local `~/.ssh` remains a fallback.

New CLI commands

```bash servonaut ssh # connect via BW-resolved key (or local fallback) servonaut servers verify # probe the BW key + report verify status to the audit log ```

The resolution chain walks personal → team → local in that order. BW key bodies live in a 0600 ephemeral tmpfile under `~/.servonaut/tmp/` that's wiped + unlinked on exit; a 24-hour sweeper handles crash-recovery on next startup.

New TUI surfaces (v2 default)

  • "SSH" column on the instance table — at-a-glance verify-status badge. Renders "—" when no ref is stored or when `ssh_verified_at` is NULL per the server contract.
  • R. Manage SSH Ref action — add / edit / delete the per-instance BW item ref. Modal lets you set `item_id`, optional `collection_id`, optional `vault_url`.
  • V. Verify SSH action — runs the local probe and POSTs the result. Updates the row in place.
  • Settings → Bitwarden SSH Vault section — configure your personal BW vault wiring (vault URL, default collection).
  • Both R and V are top-level shortcuts on InstanceListScreen — no drilling into the actions menu required.

Discoverability fixes

  • The actions menu now has three entry points: press Enter, press O (Open), or double-click a row. The footer always shows `o Actions` so users can discover the menu.
  • DataTable was previously consuming Enter without surfacing it — this is fixed via the new `on_data_table_row_selected` handler.

Mercure relay listener

  • Subscribes to both `/cli/{uid}/commands` (legacy) and `/cli/{uid}/ai-tool-calls` (new, PR #74 server-side) on one SSE connection.
  • Bounded LRU dedup (256 entries, 5-min TTL) keyed by `tool_call_id` so the dual-publish transition window doesn't cause double-execution.

Wire contract guards (regression-tested)

  • `ssh_credential_ref` is the body field — never `ref`. Test asserts `"ref" not in body`.
  • `ssh_verified_at` is NULL whenever status ≠ `verified` — enforced client-side too, even if the server somehow returns a timestamp on a non-verified status.
  • Provider whitelist `aws|ovh|hetzner` + instance_id regex `[A-Za-z0-9_\-]{1,64}` validated client-side so malformed inputs fail locally instead of round-tripping.

Test count

3985 passing, 5 skipped (+327 new tests covering all surfaces).

Coordination

Backend epic landed in prod earlier this week. CLI side is feature-complete. See #27 for the full PR.

View on GitHub
v2.11.0

Complete team management UX + Shared Configs v1. Coordinated CLI half of servonaut.dev PR #66 (prod-deployed earlier today).

Highlights

Team Management Screen — wrapped sections in curved-border cards (matches Settings/Manage UX), moved inline forms in-context inside their parent section, fixed forms expanding to fill the viewport, added Cancel button on every form.

Members surface

  • Role Select widget (Viewer / Member / Admin); admin hidden for non-owners
  • Status column with countdown ("Invited (expires in 5d)")
  • Resend Invite action (new endpoint)
  • Copy Accept URL action (fallback when email fails)
  • Change Role action (PUT route, no /role suffix)
  • 402/422 error branching with billing-aware copy (owner vs member)
  • Hide Create Team when caller already owns a team

Shared Servers

  • Instance-picker form replaces the old blind "share first instance" placeholder
  • Optional Username + Port inputs (prefilled from custom servers)
  • Fix wire-format mismatch: hostname not host, {\"data\":[]} envelope
  • Share All button with dedupe + continue-on-failure

Shared Configs v1 (new)

  • Push your sanitised local config (connection profiles, scan rules, custom servers) to share with the team
  • Pull team's baseline with diff preview before applying
  • Strips local filesystem paths (ssh_key, bastion_key) with warning
  • Versioned (server keeps 100 per team)

Seat-cap display

  • "Seats: X / Y" line in team header (X = accepted + pending, matches server-side accounting)
  • Special copy when no active Teams subscription

Under the hood

  • APIClient.put() (was missing — completes the post/patch/put trio)
  • TeamService.update_role route fix (was silently 404ing)
  • Rename user_id → member_id (TeamMember.id, not User.id; diverges for pending invites)
  • New: resend_invite, list_team_configs, get_latest_team_config, push_team_config
  • relay_listener now skips non-CommandType events at DEBUG (was logging ERROR for every AI tool call mirrored on the relay channel)
  • chat_panel defensive args parsing for AI tool calls + DEBUG dump for wire-shape divergence

Tests

110 team-bucket tests passing (added 56 new); 37 relay tests passing (added 2 new). Full suite: 3666 passed, 5 skipped, 0 failed.

Future work (planned)

Two planning notes saved for follow-up releases:

  • Connect-to-Shared-Server — SSH "Connect" button on Shared Servers table (depends on team-secrets key resolution)
  • Shared Configs v2 — three waves: Trust+Safety (auto-snapshot before Apply, restore historical), Granular Control (per-section selective import, merge/overlay modes), Two-Way Visibility (Mercure push notifications, admin drift dashboard)
View on GitHub
v2.10.2

What's Changed

  • fix(entitlements): align teams plan handling and derive config_sync from quota by @zoltanam in #25

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.10.1...v2.10.2

View on GitHub
v2.10.1

What's Changed

  • feat(mcp): gate server-memory tools + document AWS tools in README by @zoltanam in #24

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.10.0...v2.10.1

View on GitHub
v2.10.0

What's Changed

  • feat(mcp): add CloudWatch, CloudTrail, and IP-ban tools by @zoltanam in #23

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.9.4...v2.10.0

View on GitHub
v2.9.4

What's Changed

  • Demo-mode redaction, config-load hardening, and OVH credential feedback by @zoltanam in #21

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.9.3...v2.9.4

View on GitHub
v2.9.3

What's Changed

  • docs(readme): refresh screenshots with current TUI by @zoltanam in #17
  • chore: remove internal planning docs from public repo by @zoltanam in #18
  • chore(positioning): adopt new headline across README and pyproject by @zoltanam in #19
  • feat(secrets): MVP — pluggable provider, Bitwarden + Local backends, in-TUI control by @zoltanam in #20

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.9.2...v2.9.3

View on GitHub
v2.9.2

Auth refresh-race + relay 401 split-brain fix (re-ship of v2.9.1)

v2.9.2 contains the same code fix as v2.9.1 (which failed to publish — its publish.yml was missing the optional provider SDKs needed by the test gate).

What's fixed

Two compounding bugs were logging users out repeatedly — on CLI restart and mid-session — even when the refresh_token was still valid:

  1. Refresh race in AuthService.refresh_token. Concurrent 401-retries presented the same refresh_token to /api/oauth/refresh. First rotated it; second hit a revoked token and got 400 invalid_grant. Blunt non-200 handler killed the session. Fix: per-process asyncio.Lock + disk re-read inside the lock to dedup concurrent refreshes, plus _classify_refresh_failure so only 400 invalid_grant / 401 / 403 marks the session dead (429 / 5xx / network errors stay transient).
  2. Local 1h clock killed the UI. AuthToken.is_authenticated gated on time.time() >= expires_at → CLI flagged "not connected" after 1h without ever firing the call that would have triggered refresh. Relaxed: server is the source of truth via the 401-retry path.
  3. Relay split-brain. RelayListener owns its own httpx.AsyncClient and bypassed APIClient's refresh-on-401, so the heartbeat fired session_expired instantly on a stale access_token while the login screen correctly reported "logged in". New _authed_request helper threads through heartbeat / mercure-token / command-result and refreshes-and-retries once before declaring the session expired.

Workflow fix (the reason v2.9.2 exists)

publish.yml now installs [test,hetzner,ovh,mcp] to match ci.yml. Without this, the optional-SDK-using tests block every release-triggered publish.

Coordination

Backend agreed to add server-side row locking on the refresh-token row + a short idempotency window in TokenService::refreshToken as belt-and-braces (covers cross-process and shared-auth.json-over-Dropbox cases).

Tests

7 new auth tests + 2 new relay tests. Full suite 2913 passing.

View on GitHub
v2.9.1

Fix: OAuth refresh-race and relay 401 split-brain

Two compounding bugs were logging users out repeatedly — on CLI restart and mid-session — even when the refresh_token was still valid for 29 more days.

What was broken

  1. Refresh race. Concurrent 401-retries (chat stream + status poll, parallel memory-sync batches, etc.) both presented the same refresh_token to /api/oauth/refresh. First call rotated and revoked it; second call hit a revoked token and got 400 invalid_grant. The blunt non-200 handler treated that as a dead session and kicked the user out.
  2. Local 1h clock killed the UI. AuthToken.is_authenticated gated on time.time() >= expires_at, so once the access_token's 1h TTL elapsed the CLI showed "not connected" without ever firing the call that would have triggered refresh.
  3. Relay split-brain on restart. RelayListener owns its own httpx.AsyncClient (the SSE subscription has to hold it open), so it bypassed APIClient's refresh-on-401. On a fresh CLI start with a locally-stale access_token, the heartbeat hit 401 and fired session_expired immediately — while the login screen, reading the relaxed is_authenticated, correctly reported "logged in". Hence the mismatched indicators.

What changed

  • AuthService.refresh_token: per-process asyncio.Lock + disk re-read inside the lock to dedup concurrent refreshes (adopts a newer disk token without hitting the network). New _classify_refresh_failure splits responses three ways:
    • 200 → success, persist, clear sticky revoked flag.
    • 401/403/400 invalid_grant → set _refresh_grant_revoked, UI asks user to re-authenticate.
    • 429 / 5xx / 400 with other codes / network error → transient, keep credentials, next 401-retry will try again.
  • AuthToken.is_authenticated no longer gates on local expiry — server is the source of truth via the 401-retry path. Stale access_tokens get healed transparently.
  • RelayListener got a refresh_callback parameter and _authed_request helper. Heartbeat / mercure-token / command-result POSTs now refresh-and-retry on 401 before declaring session_expired.
  • validate_token only deletes auth.json when the revoked flag is set — transient failures no longer nuke the file.

Tests

  • 7 new auth tests: lock/dedup path, concurrent refresh serialisation, invalid_grant flagging, 429/5xx/network-error transient handling, success-clears-flag.
  • 2 new relay tests: heartbeat 401 + successful refresh → no session_expired; heartbeat 401 + failed refresh → session_expired still fires.
  • Full suite: 2913 passing.

Coordination

Server-side fix coordinated with servonaut-web-backend — they'll follow up with SELECT ... FOR UPDATE row locking on the refresh-token row + a short idempotency window in TokenService::refreshToken as belt-and-braces. Not load-bearing now that the client serialises, but covers the cross-process and shared-auth.json-over-Dropbox cases.

View on GitHub
v2.9.0 — v2.9.0 — Per-provider managers + full MCP lifecycle parity

Highlights

Per-provider manager screens. New ⚙ Manage entries under both Hetzner and OVH in the sidebar — each opens a provider-scoped table with a state-aware action toolbar:

  • Hetzner → ⚙ Manage — Create / Power on / Shutdown / Power off / Reboot / Delete. Start enables only on stopped servers, Shutdown / Power off / Reboot only on running ones.
  • OVH → ⚙ Manage — Create / Start / Stop / Reboot / Delete, routed automatically across Cloud / VPS / dedicated bare-metal per row's provider_type.

The unified InstanceListScreen stays unchanged as the search-and-SSH fast path.

MCP tool lifecycle parity. 10 new tools so AI agents have the same surface as the UI:

Provider New tools
Hetzner power_on, power_off, shutdown, reboot (standard tier), delete_ssh_key (dangerous)
OVH create_instance, delete_instance (dangerous), start_instance, stop_instance, reboot_instance (standard)

Power management at the standard guard tier; create/delete at dangerous. Every mutating tool's description carries an explicit "Confirm with the user before calling" cue, plus a top-level confirmation-protocol prompt in the MCP server's instructions block (summarise → state args → wait for affirmative reply).

Total tool count: 39 (up from 24 in the README's prior count — hetzner_* was missing from the docs entirely).

Hetzner SSH keys screen. New Hetzner → 🔑 SSH Keys surface with full CRUD against the Hetzner Cloud project's key registry — the same registry the create-server wizard injects from. Replaces the prior CLI-hint placeholder.

OVH SSH keys screen migrated. Was managing /me/sshKey (account-level, used by VPS / dedicated). Now manages /cloud/project/{id}/sshkey — the registry the cloud-create wizard reads from. Adding a key actually shows up in the wizard now.

OVH cloud-create wizard fixes.

  • Region-first picker. Region is a Select populated from GET /cloud/project/{id}/region; on change, flavors and images reload filtered. Picking a flavor in the wrong region is impossible by construction (was the source of Flavor X could not be found errors).
  • Datacentre labels. Region dropdown reads GRA11 — Gravelines, France instead of bare codes. Stable mapping for ~13 known prefixes; unknown codes fall back gracefully.
  • Pricing surfaced. _extract_ovh_price helper parses all three OVH price-block shapes (text / value / legacy priceInUcents); flavor table grows Hourly / Monthly columns and the confirm modal includes the cost line.
  • Hide dead regions. Regions with zero deployable flavors are filtered out of the picker; available: false flavors are hidden from the table within otherwise-active regions.

Notable bug fixes

  • fix(relay) — login wrote auth tokens but never relay.{base_url, mercure_url}, so freshly-logged-in users tripped a spurious "MCP relay URLs not configured" toast. New RelayManager.ensure_configured() derives URLs from the API base (prod splits api/apex; staging single-host) and persists.
  • fix(screens) — Textual 8.x rejects push_screen_wait outside a worker context. Audited every callsite; six screens were latently broken and would've crashed the moment a user clicked a destructive action button. Mechanical fix across hetzner_create, ovh_cloud_create, ovh_resize, ovh_snapshots, ovh_firewall, ovh_ip_management.
  • fix(custom-servers) — Save / Cancel row was clipped on small terminals (sat at the end of #add_form, off-screen on a 24-row terminal). Now docked to the screen bottom, mirroring the wizard pattern.
  • fix(ovh-storage) — OVHStorageScreen._get_project_ids reached for a non-existent config_manager.config attribute, always returned [] regardless of what was configured. Bug was unique to this one screen — verified by grep across the codebase.
  • fix(hetzner-create) — wizard relied on the service-side fallback to default_hetzner_ssh_key; if that wasn't set the footgun guard fired with a generic message. Now pre-selects the configured default in the keys table + adds an actionable submit-time check pointing at the right Settings field.
  • fix(ovh-ip) — global .hidden { display: none } rule was missing; several screens (instance_list memory banner, ovh_ip_management forms, ovh_firewall) toggled classes="hidden" to no effect. Fixed at the root.

UX polish

  • Sidebar relay indicator moved from the dock-bottom row to the brand block (under "Server Manager"), aligned to the same left margin.
  • Curved-border style language applied uniformly to every OVH-screen DataTable + the new manager surfaces. Action toolbars match the Snapshot Manager visual rhythm.
  • OVH IP Management restructured — each form now in its own card-styled Container so show/hide is atomic; removes the gaps the user reported between sections.

Tests

~150 new tests landed across the release covering: new service methods (HetznerService.{power_on,power_off,shutdown,reboot,delete_ssh_key,list_locations,list_images}, OVHCloudService.list_regions, flavor available + price field shapes), 15 region-label parametrised cases, all 11 new MCP tools (dispatch / guard rejection / validation / api-error wrapping), Hetzner setup screen dropdown seeding/refresh/saved-deprecated value preservation, relay URL derivation and ensure_configured.

Upgrade

pipx install --force servonaut
# or
pip install --upgrade servonaut

Full diff: https://github.com/zb-ss/servonaut/compare/v2.7.0...v2.9.0

View on GitHub
v2.8.0

What's Changed

  • feat(bug-report): in-TUI bug reporter with consent + dual channels by @zoltanam in #15

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.7.0...v2.8.0

View on GitHub
v2.7.0

What's Changed

  • feat: hosted Servonaut AI provider, chat memory injection, security hardening by @zoltanam in #14

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.6.0...v2.7.0

View on GitHub
v2.6.0

What's Changed

  • docs(config): correct RelayConfig example URLs to match prod topology by @zoltanam in #12
  • feat(memory): server memory engine + cloud Memory Sync by @zoltanam in #13

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.5.0...v2.6.0

View on GitHub
v2.5.0

Multi-provider expansion + the foundation for the Servonaut Cloud account and MCP relay. No breaking API changes.

Highlights

OVHcloud is the second first-class provider

Dedicated servers, VPS, and Public Cloud all managed from the same instance list as AWS EC2. Guided setup via servonaut --setup-ovh, and a sidebar section for DNS zones, IP management, snapshots, block storage, billing, SSH keys, and Public Cloud instance creation. 206 new tests covering the full OVH surface.

Servonaut Cloud account

Sign in from the TUI's Account screen to enable:

  • Config sync — client-encrypted snapshots of your config.json pushed to / pulled from servonaut.dev. Passphrase never leaves your client.
  • MCP relay (foundation) — Servonaut now establishes a Mercure SSE connection to servonaut.dev on login. The backend sees the CLI as online and can dispatch MCP tool calls through the relay channel; the agent-facing dispatcher and hosted tool surface ship in an upcoming release.

MCP relay infrastructure

The TUI opens an in-process relay listener for the lifetime of the TUI window. For always-on reachability, run servonaut connect --bg — the CLI and TUI cooperate over ~/.servonaut/relay.lock so they can't both listen at once.

New servonaut connect subcommand family:

  • --bg / --stop / --status — background listener + backend-view status (flags divergence when heartbeats aren't landing).
  • --reconnect — heal a stale SSE socket (stop + start).
  • --force-bg — SIGUSR1 a TUI's in-process listener to hand over to a detached bg listener.

MCP tool additions

New tools registered on the local MCP server:

  • Session: whoami (no token leak), api_request (authenticated REST proxy with 401-refresh, 1 MiB cap, 30/min rate limit, header allowlist).
  • Relay: relay_status (backend view), relay_reconnect (heal stale connection, idempotent on healthy), mcp_tool_call (JSON-RPC 2.0 wrapper for hosted MCP — used by future hosted tools).
  • OVH (read-only introspection): ovh_ssh_keys, ovh_invoices, ovh_firewall_rules. Additional OVH tools exist but are being refined and will be featured in a later release.

The built-in TUI chat now delegates to the same ServonautTools implementation as the MCP server — single source of truth in src/servonaut/mcp/tool_schemas.py. Fixes a bug where the chat couldn't see OVH or custom servers.

Security

  • ~/.servonaut/auth.json chmod'd to 0600 with atomic writes. Older world-readable files are silently fixed on next load.
  • Relay lifecycle log (~/.servonaut/logs/relay.log) redacts authorization / token / secret / api_key values and detects Bearer / JWT content on unrecognised keys.

Smaller wins

  • Per-host extra_ssh_options for legacy boxes (HostKeyAlgorithms=+ssh-rsa, custom keepalives).
  • Login modal widened and the verification URL wrapped in a real OSC 8 hyperlink so clicks open the full URL regardless of wrapping.
  • Unreleased plan features (Premium AI / GCP / Azure / Teams) filtered from the login screen until they ship on the CLI.

Upgrade notes

  • No config migration required — all new keys are optional with defaults.
  • Users who previously ran servonaut connect --bg manually can keep doing so; the TUI will detect the existing listener and report external listener (PID N) instead of starting a duplicate.
  • Free-tier users see an "Upgrade to connect" indicator in the sidebar after logging in — no Mercure fetch is attempted, so there's no network cost to seeing the indicator.

Under the hood

  • 1599 tests (up from 718 at v2.4.13). New suites: test_relay_lock, test_relay_manager, test_relay_log, test_mcp_whoami, test_mcp_api_request, test_mcp_relay_reconnect, test_mcp_relay_status_and_tool_call, test_main_relay, plus expansions to test_auth_service and test_chat_tools.
  • Tool registration centralised: adding a new tool = one entry in tool_schemas.py + one method on ServonautTools. Chat and MCP surfaces pick it up automatically.

Full changelog: https://github.com/zb-ss/servonaut/compare/v2.4.13...v2.5.0

View on GitHub
v2.4.13
v2.4.12
v2.4.11

What's Changed

  • fix(mcp): support custom servers in all MCP tool operations by @zoltanam in #8

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.4.10...v2.4.11

View on GitHub
v2.4.10
v2.4.9
v2.4.8
v2.4.7

What's Changed

  • Replace main menu with persistent sidebar navigation by @zoltanam in #7

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.4.6...v2.4.7

View on GitHub
v2.4.6
v2.4.5
v2.4.4
v2.4.3
v2.4.2
v2.4.1
v2.4.0
v2.3.6

What's Changed

  • Fix CI: drop Python 3.8 support, require Python >=3.10 for Textual 8.x compatibility

Full Changelog

https://github.com/zb-ss/servonaut/compare/v2.3.5...v2.3.6

View on GitHub
v2.3.5

What's Changed

Bug Fixes

  • Fix keystroke skipping — SSH subprocesses no longer steal terminal stdin (stdin=DEVNULL)
  • Preserve search filter on background instance refresh
  • Dynamic version display on main menu (from package metadata)
  • Fix duplicate "Analyzing..." text in AI analyzer
  • Fix DataTable rendering in CloudWatch/CloudTrail browsers (Select widgets were consuming all vertical space)
  • Fix IP ban form visibility in Settings (switched to programmatic display toggling)

Features & Improvements

  • CloudWatch Logs Browser — Select dropdowns for region/log group/time range, auto-discovery of log groups, pagination (100/page), fetch all events for accurate IP counting
  • CloudTrail Event Browser — Select dropdowns for region and time range, pagination (100/page), fetch all events (up to 10k)
  • IP Ban Manager — Side-by-side layout for banned IPs and audit log, ban count column from audit history, copy selected IP (y) or use it in input (Enter)
  • Settings IP Ban Config — AWS resource discovery (WAF IP sets, Security Groups, NACLs), Select dropdowns for region/method, better placeholders and examples
  • AI Analyzer — Auto-copy mouse selection to clipboard
  • Textual 8.x upgrade — Updated from 0.40.0 to 8.0.2

Full Changelog

https://github.com/zb-ss/servonaut/compare/v2.3.0...v2.3.5

View on GitHub
v2.3.0

What's Changed

  • Rename project to Servonaut by @zoltanam in #4
  • Enhanced log viewer, AI analysis, and secret externalization by @zoltanam in #5
  • feat: add agentic tool-use loop to chat panel by @zoltanam in #6

Full Changelog: https://github.com/zb-ss/servonaut/compare/v2.1.0...v2.3.0

View on GitHub
v2.1.0

What's New

Consolidated Config Directory (~/.ec2-ssh/)

All runtime files are now organized under a single ~/.ec2-ssh/ directory:

File Purpose
~/.ec2-ssh/config.json Main configuration
~/.ec2-ssh/cache.json Cached instance list
~/.ec2-ssh/keywords.json Keyword scan results
~/.ec2-ssh/command_history.json Saved commands & history
~/.ec2-ssh/logs/ Application logs & wrapper scripts

Automatic migration: If you have existing files at the old locations (~/.ec2_ssh_config.json, etc.), they are moved automatically on first startup. No manual action needed.

Saved Commands & Persistent History

  • Persistent command history — commands you run in the overlay are saved across sessions. Reopen the overlay and press Up/Down to recall previous commands.
  • Save favorites (Ctrl+S) — save frequently-used commands with a custom name
  • Command picker (Ctrl+R) — search and select from saved commands and recent history with real-time filtering
  • Per-instance + global history — Up/Down arrows recall commands for the current server; the picker shows commands across all servers
  • Keybinding hints bar — the command overlay now shows available shortcuts at a glance

Scan Progress Indicator

  • Per-server progress feedback during keyword scans with instance names shown as they complete

Keyboard Shortcuts (Command Overlay)

Key Action
Ctrl+R Open command picker (saved + recent)
Ctrl+S Save current command to favorites
↑ / ↓ Navigate command history
Ctrl+C Stop running command
Escape Close overlay

Full Changelog

https://github.com/zb-ss/ec2-ssh/compare/v2.0.0...v2.1.0

View on GitHub
v2.0.0

EC2 Connect v2.0.0

Complete rewrite as a modern Terminal User Interface (TUI) powered by Textual.

Features

  • Interactive TUI with mouse and keyboard support
  • List and search EC2 instances across all AWS regions
  • SSH into instances — launches in new terminal window with auto-detected emulator
  • Run remote commands via overlay panel with real-time streaming output
  • Browse remote file systems — interactive file tree navigation
  • SCP file transfer — upload/download files and directories
  • Keyword-based server scanning — search file contents across instances
  • Bastion host / jump server support via ProxyJump or ProxyCommand
  • SSH key management with auto-discovery and per-instance configuration
  • Instance caching with stale-while-revalidate for fast startup
  • Fully configurable via ~/.ec2_ssh_config.json

Install

pipx install ec2-tui

Or use the one-liner installers:

# Linux / macOS
curl -sSL https://raw.githubusercontent.com/zb-ss/ec2-ssh/master/install.sh | bash

# Windows (PowerShell)
irm https://raw.githubusercontent.com/zb-ss/ec2-ssh/master/install.ps1 | iex

Requirements

  • Python 3.8+
  • AWS CLI configured with ec2:DescribeInstances and ec2:DescribeRegions permissions
  • SSH client
View on GitHub