mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 01:48:20 -10:00
fix(commands): plugin provider sign-in moves from /login to /connect, with /disconnect; /login and /logout stay Codewhale-account only
Follow-up to merged PR #6950 (@LIghtJUNction). /login reverts to status|account|key and points plugin sign-in at /connect <provider>; /logout no longer takes a provider. New /connect [provider] opens the provider picker or starts plugin PKCE; /disconnect <provider> removes the local grant. Both are protected built-ins. Picker, OAuth hints and English strings say connected/disconnected; CmdConnectDescription and CmdDisconnectDescription added to all 15 locales; docs/PLUGIN_PROVIDERS.md and features.toml rows updated. Evidence: nextest of the command, locale and plugin tests 236 run, 236 passed; feature_registry 2 passed; check-tui-locale-parity PASS (2525 keys). Not run: clippy, PTY tests, a real browser OAuth flow, hosted CI. Refs #6950 Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
754b46e138
commit
0ff4ffd494
23 files changed
+150
-65
No files matched your search
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "La mascota funciona amb una versió anterior. Reinicia-la després d’actualitzar Codewhale per triar l’estil de balena i la llengua del text.",
|
||||
"PluginOAuthBrowser": "Prem Retorn per iniciar sessió al navegador o Esc per tornar. No cal cap clau API.",
|
||||
"PluginOAuthReady": "{provider}: sessió iniciada. Tria un model.",
|
||||
"PluginOAuthLocalLogout": "{provider}: sessió local eliminada. L’accés remot no ha canviat."
|
||||
"PluginOAuthLocalLogout": "{provider}: sessió local eliminada. L’accés remot no ha canviat.",
|
||||
"CmdConnectDescription": "Connecta un proveïdor de plugins amb inici de sessió al navegador",
|
||||
"CmdDisconnectDescription": "Elimina l’inici de sessió local d’un proveïdor de plugins"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "Das Haustier verwendet eine ältere Version. Starte es nach dem Codewhale-Update neu, um den Walstil und die Textsprache auszuwählen.",
|
||||
"PluginOAuthBrowser": "Mit Eingabe im Browser anmelden, mit Esc zurück. Kein API-Schlüssel erforderlich.",
|
||||
"PluginOAuthReady": "{provider}: angemeldet. Wählen Sie ein Modell.",
|
||||
"PluginOAuthLocalLogout": "{provider}: lokale Anmeldung entfernt. Der Fernzugriff bleibt unverändert."
|
||||
"PluginOAuthLocalLogout": "{provider}: lokale Anmeldung entfernt. Der Fernzugriff bleibt unverändert.",
|
||||
"CmdConnectDescription": "Plugin-Anbieter per Browser-Anmeldung verbinden",
|
||||
"CmdDisconnectDescription": "Lokale Anmeldung eines Plugin-Anbieters entfernen"
|
||||
}
|
||||
@@ -2519,7 +2519,9 @@
|
||||
"PlanHandoffKeepPlanningDetail": "Stay in Plan. Nothing is changed.",
|
||||
"PlanHandoffProceed": "Go ahead with the plan.",
|
||||
"PetAppearanceLegacy": "The pet companion is running an older version. Restart it after updating Codewhale to use whale styles and text languages.",
|
||||
"PluginOAuthBrowser": "Press Enter to sign in with your browser. Esc returns. No API key is needed.",
|
||||
"PluginOAuthReady": "{provider}: signed in. Choose a model.",
|
||||
"PluginOAuthLocalLogout": "{provider}: local sign-in removed. Remote access is unchanged."
|
||||
"PluginOAuthBrowser": "Press Enter to connect in your browser. Esc returns. No API key is needed.",
|
||||
"PluginOAuthReady": "{provider}: connected. Choose a model.",
|
||||
"PluginOAuthLocalLogout": "{provider}: disconnected on this device. Remote access is unchanged.",
|
||||
"CmdConnectDescription": "Connect a plugin provider with browser sign-in",
|
||||
"CmdDisconnectDescription": "Remove a plugin provider's local sign-in"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "La mascota usa una versión anterior. Reiníciala después de actualizar Codewhale para elegir el estilo de ballena y el idioma del texto.",
|
||||
"PluginOAuthBrowser": "Presiona Intro para iniciar sesión en el navegador o Esc para volver. No se necesita una clave API.",
|
||||
"PluginOAuthReady": "{provider}: sesión iniciada. Elige un modelo.",
|
||||
"PluginOAuthLocalLogout": "{provider}: se eliminó la sesión local. El acceso remoto no cambió."
|
||||
"PluginOAuthLocalLogout": "{provider}: se eliminó la sesión local. El acceso remoto no cambió.",
|
||||
"CmdConnectDescription": "Conecta un proveedor de plugins con inicio de sesión en el navegador",
|
||||
"CmdDisconnectDescription": "Elimina el inicio de sesión local de un proveedor de plugins"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "Le compagnon utilise une ancienne version. Redémarrez-le après la mise à jour de Codewhale pour choisir le style de baleine et la langue du texte.",
|
||||
"PluginOAuthBrowser": "Appuyez sur Entrée pour vous connecter dans le navigateur, ou Échap pour revenir. Aucune clé API nécessaire.",
|
||||
"PluginOAuthReady": "{provider} : connexion établie. Choisissez un modèle.",
|
||||
"PluginOAuthLocalLogout": "{provider} : connexion locale supprimée. L’accès distant reste inchangé."
|
||||
"PluginOAuthLocalLogout": "{provider} : connexion locale supprimée. L’accès distant reste inchangé.",
|
||||
"CmdConnectDescription": "Connecter un fournisseur de plugin par connexion dans le navigateur",
|
||||
"CmdDisconnectDescription": "Supprimer la connexion locale d’un fournisseur de plugin"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "पेट पुराने संस्करण पर चल रहा है। व्हेल का रूप और टेक्स्ट की भाषा चुनने के लिए Codewhale अपडेट करने के बाद पेट को फिर से शुरू करें।",
|
||||
"PluginOAuthBrowser": "ब्राउज़र से साइन इन करने के लिए Enter दबाएँ। वापस जाने के लिए Esc दबाएँ। API कुंजी की ज़रूरत नहीं है।",
|
||||
"PluginOAuthReady": "{provider}: साइन इन हो गया। मॉडल चुनें।",
|
||||
"PluginOAuthLocalLogout": "{provider}: स्थानीय साइन इन हटाया गया। दूरस्थ पहुँच नहीं बदली है।"
|
||||
"PluginOAuthLocalLogout": "{provider}: स्थानीय साइन इन हटाया गया। दूरस्थ पहुँच नहीं बदली है।",
|
||||
"CmdConnectDescription": "ब्राउज़र साइन-इन से किसी प्लगइन प्रदाता को जोड़ें",
|
||||
"CmdDisconnectDescription": "किसी प्लगइन प्रदाता का स्थानीय साइन-इन हटाएँ"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "Pet masih memakai versi lama. Mulai ulang pet setelah memperbarui Codewhale untuk memilih gaya paus dan bahasa teks.",
|
||||
"PluginOAuthBrowser": "Tekan Enter untuk masuk melalui peramban atau Esc untuk kembali. Kunci API tidak diperlukan.",
|
||||
"PluginOAuthReady": "{provider}: berhasil masuk. Pilih model.",
|
||||
"PluginOAuthLocalLogout": "{provider}: data masuk lokal dihapus. Akses jarak jauh tidak berubah."
|
||||
"PluginOAuthLocalLogout": "{provider}: data masuk lokal dihapus. Akses jarak jauh tidak berubah.",
|
||||
"CmdConnectDescription": "Hubungkan penyedia plugin dengan masuk lewat peramban",
|
||||
"CmdDisconnectDescription": "Hapus data masuk lokal penyedia plugin"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "ペットは以前のバージョンで動作しています。Codewhale を更新してペットを再起動すると、クジラのスタイルと文字の言語を選べます。",
|
||||
"PluginOAuthBrowser": "Enter でブラウザーからログインします。Esc で戻ります。API キーは不要です。",
|
||||
"PluginOAuthReady": "{provider}:ログインしました。モデルを選択してください。",
|
||||
"PluginOAuthLocalLogout": "{provider}:この端末の認証情報を削除しました。サーバーの認可は解除されていません。"
|
||||
"PluginOAuthLocalLogout": "{provider}:この端末の認証情報を削除しました。サーバーの認可は解除されていません。",
|
||||
"CmdConnectDescription": "ブラウザーのログインでプラグインプロバイダーに接続します",
|
||||
"CmdDisconnectDescription": "プラグインプロバイダーのこの端末のログイン情報を削除します"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "펫이 이전 버전으로 실행 중입니다. Codewhale을 업데이트한 뒤 펫을 다시 시작하면 고래 스타일과 텍스트 언어를 선택할 수 있습니다.",
|
||||
"PluginOAuthBrowser": "Enter를 눌러 브라우저에서 로그인하세요. Esc를 누르면 돌아갑니다. API 키는 필요하지 않습니다.",
|
||||
"PluginOAuthReady": "{provider}: 로그인했습니다. 모델을 선택하세요.",
|
||||
"PluginOAuthLocalLogout": "{provider}: 이 기기의 로그인 정보를 삭제했습니다. 서버 권한은 유지됩니다."
|
||||
"PluginOAuthLocalLogout": "{provider}: 이 기기의 로그인 정보를 삭제했습니다. 서버 권한은 유지됩니다.",
|
||||
"CmdConnectDescription": "브라우저 로그인으로 플러그인 제공자를 연결합니다",
|
||||
"CmdDisconnectDescription": "플러그인 제공자의 이 기기 로그인 정보를 삭제합니다"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "O pet está usando uma versão anterior. Reinicie-o após atualizar o Codewhale para escolher o estilo da baleia e o idioma do texto.",
|
||||
"PluginOAuthBrowser": "Pressione Enter para entrar pelo navegador ou Esc para voltar. Não é necessária uma chave de API.",
|
||||
"PluginOAuthReady": "{provider}: sessão iniciada. Escolha um modelo.",
|
||||
"PluginOAuthLocalLogout": "{provider}: sessão local removida. O acesso remoto não foi alterado."
|
||||
"PluginOAuthLocalLogout": "{provider}: sessão local removida. O acesso remoto não foi alterado.",
|
||||
"CmdConnectDescription": "Conecta um provedor de plugin com login pelo navegador",
|
||||
"CmdDisconnectDescription": "Remove o login local de um provedor de plugin"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "Питомец работает на старой версии. После обновления Codewhale перезапустите его, чтобы выбрать стиль кита и язык текста.",
|
||||
"PluginOAuthBrowser": "Нажмите Enter для входа через браузер или Esc для возврата. Ключ API не нужен.",
|
||||
"PluginOAuthReady": "{provider}: вход выполнен. Выберите модель.",
|
||||
"PluginOAuthLocalLogout": "{provider}: локальные данные входа удалены. Удалённый доступ не изменён."
|
||||
"PluginOAuthLocalLogout": "{provider}: локальные данные входа удалены. Удалённый доступ не изменён.",
|
||||
"CmdConnectDescription": "Подключить провайдера плагина через вход в браузере",
|
||||
"CmdDisconnectDescription": "Удалить локальные данные входа провайдера плагина"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "Улюбленець працює на старій версії. Після оновлення Codewhale перезапустіть його, щоб вибрати стиль кита й мову тексту.",
|
||||
"PluginOAuthBrowser": "Натисніть Enter для входу через браузер або Esc для повернення. Ключ API не потрібен.",
|
||||
"PluginOAuthReady": "{provider}: вхід виконано. Виберіть модель.",
|
||||
"PluginOAuthLocalLogout": "{provider}: локальні дані входу видалено. Віддалений доступ не змінено."
|
||||
"PluginOAuthLocalLogout": "{provider}: локальні дані входу видалено. Віддалений доступ не змінено.",
|
||||
"CmdConnectDescription": "Підключити провайдера плагіна через вхід у браузері",
|
||||
"CmdDisconnectDescription": "Видалити локальні дані входу провайдера плагіна"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "Thú cưng đang dùng phiên bản cũ. Khởi động lại sau khi cập nhật Codewhale để chọn kiểu cá voi và ngôn ngữ văn bản.",
|
||||
"PluginOAuthBrowser": "Nhấn Enter để đăng nhập bằng trình duyệt hoặc Esc để quay lại. Không cần khóa API.",
|
||||
"PluginOAuthReady": "{provider}: đã đăng nhập. Hãy chọn mô hình.",
|
||||
"PluginOAuthLocalLogout": "{provider}: đã xóa thông tin đăng nhập cục bộ. Quyền truy cập từ xa không đổi."
|
||||
"PluginOAuthLocalLogout": "{provider}: đã xóa thông tin đăng nhập cục bộ. Quyền truy cập từ xa không đổi.",
|
||||
"CmdConnectDescription": "Kết nối nhà cung cấp plugin bằng đăng nhập qua trình duyệt",
|
||||
"CmdDisconnectDescription": "Xóa đăng nhập cục bộ của nhà cung cấp plugin"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "宠物仍在使用旧版本。更新 Codewhale 后重启宠物,即可选择鲸鱼样式和文字语言。",
|
||||
"PluginOAuthBrowser": "按 Enter 打开浏览器登录,按 Esc 返回。不需要 API Key。",
|
||||
"PluginOAuthReady": "{provider}:已登录,请选择模型。",
|
||||
"PluginOAuthLocalLogout": "{provider}:已删除本机登录凭据,未撤销服务端授权。"
|
||||
"PluginOAuthLocalLogout": "{provider}:已删除本机登录凭据,未撤销服务端授权。",
|
||||
"CmdConnectDescription": "通过浏览器登录连接插件提供方",
|
||||
"CmdDisconnectDescription": "删除插件提供方的本机登录凭据"
|
||||
}
|
||||
@@ -2521,5 +2521,7 @@
|
||||
"PetAppearanceLegacy": "寵物仍在使用舊版本。更新 Codewhale 後重新啟動寵物,即可選擇鯨魚樣式和文字語言。",
|
||||
"PluginOAuthBrowser": "按 Enter 開啟瀏覽器登入,按 Esc 返回。不需要 API Key。",
|
||||
"PluginOAuthReady": "{provider}:已登入,請選擇模型。",
|
||||
"PluginOAuthLocalLogout": "{provider}:已刪除本機登入憑證,未撤銷伺服器授權。"
|
||||
"PluginOAuthLocalLogout": "{provider}:已刪除本機登入憑證,未撤銷伺服器授權。",
|
||||
"CmdConnectDescription": "透過瀏覽器登入連接外掛提供者",
|
||||
"CmdDisconnectDescription": "刪除外掛提供者的本機登入憑證"
|
||||
}
|
||||
@@ -548,6 +548,8 @@ pub enum MessageId {
|
||||
CmdLoadDescription,
|
||||
CmdLogoutDescription,
|
||||
CmdLoginDescription,
|
||||
CmdConnectDescription,
|
||||
CmdDisconnectDescription,
|
||||
CmdMcpDescription,
|
||||
McpRecommendedUnknownId,
|
||||
McpRecommendationsHeading,
|
||||
@@ -3189,6 +3191,8 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
|
||||
MessageId::CmdLoadDescription,
|
||||
MessageId::CmdLogoutDescription,
|
||||
MessageId::CmdLoginDescription,
|
||||
MessageId::CmdConnectDescription,
|
||||
MessageId::CmdDisconnectDescription,
|
||||
MessageId::CmdMcpDescription,
|
||||
MessageId::McpRecommendedUnknownId,
|
||||
MessageId::McpRecommendationsHeading,
|
||||
|
||||
@@ -3341,12 +3341,9 @@ pub fn lsp_command(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
|
||||
pub fn login(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
let raw = arg.map(str::trim).unwrap_or("");
|
||||
if raw.split_whitespace().count() > 1 {
|
||||
return CommandResult::error("Usage: /login [status|account|key|<provider>]");
|
||||
}
|
||||
match raw {
|
||||
"" => CommandResult::action(AppAction::OpenProviderPicker),
|
||||
"status" => CommandResult::message(login_status_text(app)),
|
||||
let token = raw.split_whitespace().next().unwrap_or("");
|
||||
match token {
|
||||
"" | "status" => CommandResult::message(login_status_text(app)),
|
||||
"key" | "provider" => CommandResult::with_message_and_action(
|
||||
"Open the provider picker to store an API key. Account sign-in is `codewhale login`.",
|
||||
AppAction::OpenProviderPicker,
|
||||
@@ -3354,26 +3351,59 @@ pub fn login(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
"account" => CommandResult::message(
|
||||
"TUI cannot start the browser device flow without freezing the session.\n\
|
||||
Run `codewhale login` (same as `codewhale account login`) in a terminal.\n\
|
||||
Then `/login status` to confirm the session landed."
|
||||
Then `/login` to confirm the session landed."
|
||||
.to_string(),
|
||||
),
|
||||
provider
|
||||
if app
|
||||
.plugin_registry
|
||||
.active_plugins()
|
||||
.into_iter()
|
||||
.any(|plugin| plugin.manifest.providers.contains_key(provider)) =>
|
||||
{
|
||||
other => CommandResult::error(format!(
|
||||
"Usage: /login [status|account|key]\nUnknown argument: {other}\n\
|
||||
To sign in to a plugin provider, use `/connect <provider>`."
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_enabled_plugin_provider(app: &App, provider: &str) -> bool {
|
||||
app.plugin_registry
|
||||
.active_plugins()
|
||||
.into_iter()
|
||||
.any(|plugin| plugin.manifest.providers.contains_key(provider))
|
||||
}
|
||||
|
||||
pub fn connect(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
let raw = arg.map(str::trim).unwrap_or("");
|
||||
if raw.split_whitespace().count() > 1 {
|
||||
return CommandResult::error("Usage: /connect [<provider>]");
|
||||
}
|
||||
match raw {
|
||||
"" => CommandResult::action(AppAction::OpenProviderPicker),
|
||||
provider if is_enabled_plugin_provider(app, provider) => {
|
||||
CommandResult::action(AppAction::StartPluginLogin {
|
||||
provider: provider.to_owned(),
|
||||
})
|
||||
}
|
||||
other => CommandResult::error(format!(
|
||||
"Usage: /login [status|account|key|<provider>]\nUnknown or inactive provider: {other}"
|
||||
"Usage: /connect [<provider>]\nUnknown or inactive plugin provider: {other}\n\
|
||||
Codewhale account sign-in is `/login`."
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
let raw = arg.map(str::trim).unwrap_or("");
|
||||
if raw.is_empty() || raw.split_whitespace().count() > 1 {
|
||||
return CommandResult::error("Usage: /disconnect <provider>");
|
||||
}
|
||||
if is_enabled_plugin_provider(app, raw) {
|
||||
CommandResult::action(AppAction::StartPluginLogout {
|
||||
provider: raw.to_owned(),
|
||||
})
|
||||
} else {
|
||||
CommandResult::error(format!(
|
||||
"Usage: /disconnect <provider>\nUnknown or inactive plugin provider: {raw}\n\
|
||||
Signing out of Codewhale is `/logout`."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn login_status_text(app: &App) -> String {
|
||||
use codewhale_secrets::account::{
|
||||
ACCOUNT_API_BASE_ENV, AccountSessionState, AccountSessionStore, DEFAULT_ACCOUNT_API_BASE,
|
||||
|
||||
@@ -22,6 +22,8 @@ impl CommandGroup for ConfigCommands {
|
||||
Box::new(FunctionCommand::new(&IMPORT_CLAUDE_INFO, run_import_claude)),
|
||||
crate::commands::config_policy_host::permissions_registration(),
|
||||
Box::new(FunctionCommand::new(&LOGIN_INFO, run_login)),
|
||||
Box::new(FunctionCommand::new(&CONNECT_INFO, run_connect)),
|
||||
Box::new(FunctionCommand::new(&DISCONNECT_INFO, run_disconnect)),
|
||||
Box::new(FunctionCommand::new(&AUTH_INFO, run_auth)),
|
||||
Box::new(FunctionCommand::new(&RAIL_INFO, run_rail)),
|
||||
Box::new(FunctionCommand::new(&PET_INFO, run_pet)),
|
||||
@@ -54,9 +56,21 @@ static IMPORT_CLAUDE_INFO: CommandInfo = CommandInfo {
|
||||
static LOGIN_INFO: CommandInfo = CommandInfo {
|
||||
name: "login",
|
||||
aliases: &[],
|
||||
usage: "/login [status|account|key|<provider>]",
|
||||
usage: "/login [status|account|key]",
|
||||
description_id: MessageId::CmdLoginDescription,
|
||||
};
|
||||
static CONNECT_INFO: CommandInfo = CommandInfo {
|
||||
name: "connect",
|
||||
aliases: &[],
|
||||
usage: "/connect [<provider>]",
|
||||
description_id: MessageId::CmdConnectDescription,
|
||||
};
|
||||
static DISCONNECT_INFO: CommandInfo = CommandInfo {
|
||||
name: "disconnect",
|
||||
aliases: &[],
|
||||
usage: "/disconnect <provider>",
|
||||
description_id: MessageId::CmdDisconnectDescription,
|
||||
};
|
||||
static AUTH_INFO: CommandInfo = CommandInfo {
|
||||
name: "auth",
|
||||
aliases: &[],
|
||||
@@ -128,7 +142,7 @@ static TRUST_INFO: CommandInfo = CommandInfo {
|
||||
static LOGOUT_INFO: CommandInfo = CommandInfo {
|
||||
name: "logout",
|
||||
aliases: &[],
|
||||
usage: "/logout [<provider>]",
|
||||
usage: "/logout",
|
||||
description_id: MessageId::CmdLogoutDescription,
|
||||
};
|
||||
fn run_registered(app: &mut App, name: &str, arg: Option<&str>) -> CommandResult {
|
||||
@@ -180,6 +194,12 @@ fn run_trust(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
fn run_logout(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
run_registered(app, "logout", arg)
|
||||
}
|
||||
fn run_connect(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
run_registered(app, "connect", arg)
|
||||
}
|
||||
fn run_disconnect(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
run_registered(app, "disconnect", arg)
|
||||
}
|
||||
pub(in crate::commands) fn dispatch(
|
||||
app: &mut App,
|
||||
command: &str,
|
||||
@@ -230,21 +250,9 @@ pub(in crate::commands) fn dispatch(
|
||||
"theme" => config::theme(app, arg),
|
||||
"verbose" => config::verbose(app, arg),
|
||||
"trust" | "xinren" => config::trust(app, arg),
|
||||
"logout" => match arg.map(str::trim).filter(|arg| !arg.is_empty()) {
|
||||
None => config::logout(app),
|
||||
Some(provider)
|
||||
if app
|
||||
.plugin_registry
|
||||
.active_plugins()
|
||||
.into_iter()
|
||||
.any(|plugin| plugin.manifest.providers.contains_key(provider)) =>
|
||||
{
|
||||
CommandResult::action(crate::tui::app::AppAction::StartPluginLogout {
|
||||
provider: provider.to_owned(),
|
||||
})
|
||||
}
|
||||
Some(_) => CommandResult::error("Usage: /logout [<enabled-plugin-provider>]"),
|
||||
},
|
||||
"logout" => config::logout(app),
|
||||
"connect" => config::connect(app, arg),
|
||||
"disconnect" => config::disconnect(app, arg),
|
||||
_ => return None,
|
||||
};
|
||||
Some(result)
|
||||
|
||||
@@ -711,11 +711,12 @@ mod tests {
|
||||
assert!(!message.contains("Daytona"), "{message}");
|
||||
assert!(!message.contains("set-slot"), "{message}");
|
||||
|
||||
assert_eq!(
|
||||
execute("/login", &mut app).action,
|
||||
Some(AppAction::OpenProviderPicker)
|
||||
assert!(
|
||||
execute("/login", &mut app)
|
||||
.message
|
||||
.expect("login status")
|
||||
.contains("Codewhale login")
|
||||
);
|
||||
assert!(execute("/login status extra", &mut app).is_error);
|
||||
let key = execute("/login key", &mut app);
|
||||
assert!(!key.is_error);
|
||||
assert_eq!(key.action, Some(AppAction::OpenProviderPicker));
|
||||
@@ -723,15 +724,25 @@ mod tests {
|
||||
let daytona = execute("/login daytona", &mut app);
|
||||
assert!(daytona.is_error);
|
||||
let err = daytona.message.expect("usage");
|
||||
assert!(
|
||||
err.contains("Usage: /login [status|account|key|<provider>]"),
|
||||
"{err}"
|
||||
);
|
||||
assert!(err.contains("Usage: /login [status|account|key]"), "{err}");
|
||||
assert!(err.contains("/connect <provider>"), "{err}");
|
||||
|
||||
let unknown = execute("/login oauth", &mut app);
|
||||
assert!(unknown.is_error);
|
||||
let err = unknown.message.expect("usage");
|
||||
assert!(err.contains("Usage: /login"), "{err}");
|
||||
|
||||
assert_eq!(
|
||||
execute("/connect", &mut app).action,
|
||||
Some(AppAction::OpenProviderPicker)
|
||||
);
|
||||
let connect = execute("/connect nothing-enabled", &mut app);
|
||||
assert!(connect.is_error);
|
||||
let err = connect.message.expect("usage");
|
||||
assert!(err.contains("Usage: /connect [<provider>]"), "{err}");
|
||||
assert!(err.contains("`/login`"), "{err}");
|
||||
assert!(execute("/disconnect", &mut app).is_error);
|
||||
assert!(execute("/disconnect nothing-enabled", &mut app).is_error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -538,7 +538,9 @@ const PROTECTED_BUILTINS: &[&str] = &[
|
||||
"auth",
|
||||
"auto",
|
||||
"config",
|
||||
"connect",
|
||||
"constitution",
|
||||
"disconnect",
|
||||
"hooks",
|
||||
"login",
|
||||
"logout",
|
||||
|
||||
@@ -4533,7 +4533,7 @@ impl PluginOAuthConfig {
|
||||
callback_path: &self.callback_path,
|
||||
loopback_ports: &[],
|
||||
relogin_hint: "codewhale auth plugin-login --provider <provider>",
|
||||
session_login_hint: "/login <provider>",
|
||||
session_login_hint: "/connect <provider>",
|
||||
callback_conflict_hint: "",
|
||||
}
|
||||
}
|
||||
@@ -4794,7 +4794,7 @@ fn plugin_oauth_access_token_with_store(
|
||||
) -> Result<String> {
|
||||
let resolve = |raw: &mut Option<String>| -> Result<String> {
|
||||
let stored = raw.as_ref().context(
|
||||
"Plugin OAuth login missing; use /login <provider> in Codewhale or codewhale auth plugin-login --provider <provider> in a terminal",
|
||||
"Plugin OAuth login missing; use /connect <provider> in Codewhale or codewhale auth plugin-login --provider <provider> in a terminal",
|
||||
)?;
|
||||
let mut token: PluginOAuthTokens = serde_json::from_str(stored)
|
||||
.map_err(|_| anyhow::anyhow!("Plugin OAuth credential store contains invalid data"))?;
|
||||
|
||||
@@ -49,18 +49,18 @@ the extension host is enabled. Receipts from the previous policy require an
|
||||
explicit review again, including bundles without provider declarations. An
|
||||
old review is never silently upgraded.
|
||||
|
||||
## Sign in inside Codewhale
|
||||
## Connect inside Codewhale
|
||||
|
||||
After installation, review and enable the bundle, then start a new session.
|
||||
Run `/login` to open the provider picker, or `/login <provider-id>` to authorize
|
||||
Run `/connect` to open the provider picker, or `/connect <provider-id>` to authorize
|
||||
an enabled plugin directly. The host runs its existing PKCE flow and then
|
||||
refreshes the provider’s standard `/models` roster. Choose the model explicitly;
|
||||
login never chooses the first model, changes billing groups, or copies another
|
||||
application’s credentials. A provider selected from `/provider` uses the same
|
||||
flow instead of asking for an API key.
|
||||
|
||||
`/login status` retains account status. `/logout <provider-id>` removes only that
|
||||
plugin’s local grant. Bare `/logout` retains Codewhale account logout.
|
||||
`/login` stays Codewhale-account sign-in. `/disconnect <provider-id>` removes only that
|
||||
plugin’s local grant. `/logout` stays Codewhale account logout.
|
||||
Plugin rosters are account-scoped and not reused from the disk cache.
|
||||
The terminal is temporarily suspended during browser authorization, like the
|
||||
built-in PKCE flows; device-code and remote revocation are not added here.
|
||||
|
||||
+2
-2
@@ -568,7 +568,7 @@ owner = "crates/tui/src/commands/groups/core/home.rs"
|
||||
[[feature]]
|
||||
id = "account-login"
|
||||
name = "Account login"
|
||||
summary = "/login selects providers; /login <id> runs reviewed plugin OAuth; status and account remain explicit."
|
||||
summary = "/login signs in to Codewhale or stores provider keys; plugin providers connect with /connect."
|
||||
status = "stable"
|
||||
surfaces = { tui = "stable" }
|
||||
owner = "crates/tui/src/commands/groups/config/config.rs"
|
||||
@@ -682,7 +682,7 @@ owner = "crates/tui/src/oauth.rs"
|
||||
[[feature]]
|
||||
id = "plugin-oauth-providers"
|
||||
name = "Reviewed plugin OAuth providers"
|
||||
summary = "Reviewed plugins appear in /login and /provider; the host owns PKCE, model discovery and per-request approvals."
|
||||
summary = "Reviewed plugins connect with /connect <id>; the host owns PKCE, model discovery and per-request approvals."
|
||||
status = "experimental"
|
||||
surfaces = { tui = "preview", api = "partial", web = "none" }
|
||||
since = "unreleased"
|
||||
|
||||
Reference in new issue
Block a user