feat(desktop): disconnect external (CLI-managed) providers

External providers (Claude Code) store creds outside Hermes, so the
disconnect API refuses them. The backend now hands the GUI a per-OS
`disconnect_command` that clears the credential the same way the CLI's
logout does (macOS Keychain entry + ~/.claude/.credentials.json), and
the misleading "use claude setup-token" hint is corrected.

Settings → Providers offers a Disconnect button for these: it confirms,
leaves Settings, and runs the removal command in the embedded terminal
via a new runInTerminal() (queues onto $terminalInjection; the terminal
pane flushes and clears it once its session is live). The expanded list
also gets its own "Other providers" header so it no longer reads as
grouped under "Connected". API-managed providers keep the one-click
(trash) disconnect.
This commit is contained in:
Brooklyn Nicholson
2026-06-16 00:08:21 -05:00
parent 0e81d2fb71
commit a0ec4f52b9
13 changed files with 178 additions and 21 deletions
+9 -1
View File
@@ -476,13 +476,21 @@ def test_oauth_catalog_marks_external_providers_not_disconnectable():
assert resp.status_code == 200, resp.text
providers = {p["id"]: p for p in resp.json()["providers"]}
# Qwen: external and not auto-removable, and we don't know a clear command,
# so it stays a manual hint with no runnable disconnect command.
assert providers["qwen-oauth"]["flow"] == "external"
assert providers["qwen-oauth"]["disconnectable"] is False
assert "provider's CLI" in providers["qwen-oauth"]["disconnect_hint"]
assert providers["qwen-oauth"]["disconnect_command"] is None
# Claude Code: still not API-disconnectable, but we hand the GUI a runnable
# command (clears the keychain entry / credentials file) so it can offer a
# one-click "run in terminal" disconnect.
assert providers["claude-code"]["flow"] == "external"
assert providers["claude-code"]["disconnectable"] is False
assert "provider's CLI" in providers["claude-code"]["disconnect_hint"]
assert providers["claude-code"]["disconnect_hint"]
cmd = providers["claude-code"]["disconnect_command"]
assert cmd and ".claude/.credentials.json" in cmd
def test_external_oauth_disconnect_rejected_before_auth_mutation(monkeypatch):