Skip to content

Commit bf56c3c

Browse files
TUI: add inline /api-keys menu (#14472)
## Description Replaces the TUI-only `/add-api-key` and `/clear-provider-api-key` commands with a single inline `/api-keys` menu. The menu supports filtering providers, setting and clearing provider keys, connecting or disconnecting X Premium/SuperGrok, and toggling Warp-credit fallback. It reuses the shared TUI editor through the ownership model reviewed separately in downstack PR #14488. This is a pretty large change, but the large majority of it is UI changes. Reading the specs should be enough for this one (I'll also attach an in depth demo of the change below). ## Testing - [x] I have manually tested my changes locally with `./script/run` ## Screenshots/Video https://www.loom.com/share/853ce5b6278840c89ced6bfde7b53820 ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode Co-Authored-By: Oz <oz-agent@warp.dev> Co-authored-by: Oz <oz-agent@warp.dev>
1 parent a3a06f2 commit bf56c3c

26 files changed

Lines changed: 1758 additions & 861 deletions

File tree

app/src/search/slash_command_menu/static_commands/commands.rs

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::collections::HashMap;
22
use std::sync::LazyLock;
33

4-
use ai::LLMProvider;
54
use serde::{Deserialize, Serialize};
65
use uuid::Uuid;
76
use warp_core::features::FeatureFlag;
@@ -118,31 +117,15 @@ pub const NATURAL_LANGUAGE_DETECTION: StaticCommand = StaticCommand {
118117
auto_enter_ai_mode: false,
119118
argument: None,
120119
};
121-
pub const ADD_API_KEY: StaticCommand = StaticCommand {
122-
name: "/add-api-key",
123-
description: "Securely store a model-provider API key",
124-
kind: SlashCommandKind::AddApiKey,
125-
supported_surfaces: SlashCommandSurfaces::TuiOnly,
126-
availability: Availability::AI_ENABLED,
127-
auto_enter_ai_mode: false,
128-
argument: Some(Argument {
129-
hint_text: Some(LLMProvider::API_KEY_PROVIDER_VALUE_NAME),
130-
is_optional: false,
131-
should_execute_on_selection: false,
132-
}),
133-
};
134-
pub const CLEAR_API_KEY: StaticCommand = StaticCommand {
135-
name: "/clear-provider-api-key",
136-
description: "Remove a stored model-provider API key",
137-
kind: SlashCommandKind::ClearApiKey,
120+
121+
pub const API_KEYS: StaticCommand = StaticCommand {
122+
name: "/api-keys",
123+
description: "View and manage API keys",
124+
kind: SlashCommandKind::ApiKeys,
138125
supported_surfaces: SlashCommandSurfaces::TuiOnly,
139126
availability: Availability::AI_ENABLED,
140127
auto_enter_ai_mode: false,
141-
argument: Some(Argument {
142-
hint_text: Some(LLMProvider::API_KEY_PROVIDER_VALUE_NAME),
143-
is_optional: false,
144-
should_execute_on_selection: false,
145-
}),
128+
argument: None,
146129
};
147130
pub const THEME: StaticCommand = StaticCommand {
148131
name: "/theme",
@@ -940,8 +923,6 @@ impl Registry {
940923
fn all_commands(settings_mode: settings::SettingsMode) -> Vec<StaticCommand> {
941924
let mut commands = vec![
942925
ADD_MCP,
943-
ADD_API_KEY,
944-
CLEAR_API_KEY,
945926
ADD_PROMPT.clone(),
946927
ADD_RULE,
947928
AUTO_APPROVE,
@@ -950,6 +931,7 @@ fn all_commands(settings_mode: settings::SettingsMode) -> Vec<StaticCommand> {
950931
FEEDBACK.clone(),
951932
INDEX,
952933
INIT,
934+
API_KEYS,
953935
LOGOUT,
954936
MCP,
955937
OPEN_PROJECT_RULES,

app/src/search/slash_command_menu/static_commands/commands_tests.rs

Lines changed: 9 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -97,64 +97,25 @@ fn view_logs_command_is_registered_only_for_tui_mode() {
9797
}
9898

9999
#[test]
100-
fn add_api_key_command_is_tui_only_and_requires_a_provider() {
100+
fn api_keys_command_is_tui_only_and_has_no_arguments() {
101101
let command = all_commands(settings::SettingsMode::Tui)
102102
.into_iter()
103-
.find(|command| command.kind == SlashCommandKind::AddApiKey)
104-
.expect("expected /add-api-key to be registered in TUI mode");
105-
assert_eq!(command, ADD_API_KEY);
103+
.find(|command| command.kind == SlashCommandKind::ApiKeys)
104+
.expect("expected /api-keys to be registered in TUI mode");
105+
assert_eq!(command, API_KEYS);
106106
assert!(!command.auto_enter_ai_mode);
107107
assert_eq!(command.availability, Availability::AI_ENABLED);
108-
let argument = command
109-
.argument
110-
.as_ref()
111-
.expect("expected /add-api-key to require a provider");
112-
assert!(!argument.is_optional);
113-
assert!(!argument.should_execute_on_selection);
114-
assert_eq!(
115-
argument.hint_text,
116-
Some(LLMProvider::API_KEY_PROVIDER_VALUE_NAME)
117-
);
118-
assert!(
119-
argument
120-
.hint_text
121-
.is_some_and(|hint| hint.split('|').any(|provider| provider == "grok"))
122-
);
108+
assert!(command.argument.is_none());
109+
assert_eq!(command.description, "View and manage API keys");
123110
assert!(
124111
all_commands(settings::SettingsMode::Gui)
125112
.iter()
126-
.all(|command| command.kind != SlashCommandKind::AddApiKey)
127-
);
128-
}
129-
130-
#[test]
131-
fn clear_api_key_command_is_tui_only_and_requires_a_provider() {
132-
let command = all_commands(settings::SettingsMode::Tui)
133-
.into_iter()
134-
.find(|command| command.kind == SlashCommandKind::ClearApiKey)
135-
.expect("expected /clear-provider-api-key to be registered in TUI mode");
136-
assert_eq!(command, CLEAR_API_KEY);
137-
assert!(!command.auto_enter_ai_mode);
138-
assert_eq!(command.availability, Availability::AI_ENABLED);
139-
let argument = command
140-
.argument
141-
.as_ref()
142-
.expect("expected /clear-provider-api-key to require a provider");
143-
assert!(!argument.is_optional);
144-
assert!(!argument.should_execute_on_selection);
145-
assert_eq!(
146-
argument.hint_text,
147-
Some(LLMProvider::API_KEY_PROVIDER_VALUE_NAME)
113+
.all(|command| command.kind != SlashCommandKind::ApiKeys)
148114
);
149115
assert!(
150-
argument
151-
.hint_text
152-
.is_some_and(|hint| hint.split('|').any(|provider| provider == "grok"))
153-
);
154-
assert!(
155-
all_commands(settings::SettingsMode::Gui)
116+
all_commands(settings::SettingsMode::Tui)
156117
.iter()
157-
.all(|command| command.kind != SlashCommandKind::ClearApiKey)
118+
.all(|command| !matches!(command.name, "/add-api-key" | "/clear-provider-api-key"))
158119
);
159120
}
160121
#[test]

app/src/search/slash_command_menu/static_commands/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ pub enum SlashCommandKind {
5454
Agent,
5555
CloudAgent,
5656
AddMcp,
57-
AddApiKey,
58-
ClearApiKey,
57+
ApiKeys,
5958
AutoApprove,
6059
Statusline,
6160
ResetStatusline,

app/src/settings/ai.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,7 @@ define_settings_group!(AISettings, settings: [
17881788
default: false,
17891789
supported_platforms: SupportedPlatforms::ALL,
17901790
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
1791-
surface: settings::SettingSurfaces::GUI,
1791+
surface: settings::SettingSurfaces::ALL,
17921792
private: false,
17931793
storage_key: "CanUseWarpCreditsWithByok",
17941794
toml_path: "cloud_platform.third_party_api_keys.can_use_warp_credits_with_byok",

app/src/terminal/input/slash_commands/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,8 +1289,7 @@ impl Input {
12891289
SlashCommandKind::AutoApprove
12901290
| SlashCommandKind::Statusline
12911291
| SlashCommandKind::ResetStatusline
1292-
| SlashCommandKind::AddApiKey
1293-
| SlashCommandKind::ClearApiKey
1292+
| SlashCommandKind::ApiKeys
12941293
| SlashCommandKind::ViewLogs
12951294
| SlashCommandKind::Voice
12961295
| SlashCommandKind::NaturalLanguageDetection

0 commit comments

Comments
 (0)