generated from nhcarrigan/template
feat: stuffy feature bundle (#159)
## Summary This PR bundles a collection of new features and quality-of-life improvements identified during a Claude CLI 2.1.50 audit. - **Tab status indicator** — Tab stays yellow until the greeting is responded to, then turns green. Fixed disconnect not resetting to grey. Closes #157 - **Auth status display** — New "Account" section in settings sidebar showing login status, email, org, API key source, and Hikari override indicator. Includes login/logout buttons. Closes #153 - **CLI version badge** — New "Supported" badge showing the highest audited CLI version, colour-coded green/amber/red based on installed vs supported version. Closes #154 (bump to 2.1.50) - **Rate limit events** — `rate_limit_event` messages from the stream are now parsed and shown as amber `[rate-limit]` lines in the terminal instead of being silently dropped. Closes #155 - **"Prompt is too long" handling** — Detects this error in assistant messages and shows a ⚡ Compact Conversation button to send `/compact` directly. Closes #158 - **`last_assistant_message` in Agent Monitor** — Extracts the agent's final output from the `ToolResult` content block in the JSON stream and displays it as a snippet on completed agent cards. Closes #156 - **`--worktree` flag** — New "Worktree isolation" toggle in session settings passes `--worktree` to Claude Code. Hook events (`WorktreeCreate`/`WorktreeRemove`) are displayed as green `[worktree]` lines. Closes #152, Closes #150 - **ConfigChange hook events** — `[ConfigChange Hook]` stderr events are now displayed as cyan `[config]` lines instead of errors. Closes #151 - **`CLAUDE_CODE_DISABLE_1M_CONTEXT` toggle** — New "Disable 1M context" setting in session configuration injects this env var into the Claude process. Closes #154 ## Test plan - [ ] Tab status indicator: start a new session and verify the tab stays yellow until Claude responds to the greeting, then turns green - [ ] Auth status: open settings and verify the Account section shows correct login info - [ ] CLI version badge: verify the "Supported 2.1.50" badge shows green when CLI matches - [ ] Rate limit events: unit tests cover parsing; amber `[rate-limit]` lines display correctly - [ ] Compact button: unit tests cover detection; button renders correctly in terminal - [ ] Agent Monitor: use the Task tool and verify completed agent cards show a message snippet - [ ] Worktree: enable toggle, start session, verify `--worktree` flag appears in process args - [ ] ConfigChange: hook events display as `[config]` lines rather than errors - [ ] Disable 1M context: enable toggle, start session, verify `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` in `/proc/<pid>/environ` ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #159 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #159.
This commit is contained in:
@@ -1360,6 +1360,136 @@ pub async fn get_claude_version() -> Result<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Auth Commands ====================
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClaudeAuthStatus {
|
||||
pub is_logged_in: bool,
|
||||
pub email: Option<String>,
|
||||
pub org_name: Option<String>,
|
||||
pub api_key_source: Option<String>,
|
||||
pub api_provider: Option<String>,
|
||||
pub subscription_type: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_auth_status() -> Result<ClaudeAuthStatus, String> {
|
||||
tracing::debug!("Getting Claude auth status");
|
||||
|
||||
let output = create_claude_command()
|
||||
.args(["auth", "status"])
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run claude auth status: {}", e))?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
let raw = if stdout.is_empty() { &stderr } else { &stdout };
|
||||
|
||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(raw) {
|
||||
let is_logged_in = json
|
||||
.get("loggedIn")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let email = json
|
||||
.get("email")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from);
|
||||
|
||||
let org_name = json
|
||||
.get("orgName")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from);
|
||||
|
||||
let api_key_source = json
|
||||
.get("apiKeySource")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from);
|
||||
|
||||
let api_provider = json
|
||||
.get("apiProvider")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from);
|
||||
|
||||
let subscription_type = json
|
||||
.get("subscriptionType")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from);
|
||||
|
||||
tracing::info!("Claude auth status: logged_in={}", is_logged_in);
|
||||
Ok(ClaudeAuthStatus {
|
||||
is_logged_in,
|
||||
email,
|
||||
org_name,
|
||||
api_key_source,
|
||||
api_provider,
|
||||
subscription_type,
|
||||
})
|
||||
} else {
|
||||
// Non-JSON output: fall back to heuristic
|
||||
let lower = raw.to_lowercase();
|
||||
let is_logged_in = output.status.success()
|
||||
&& !lower.contains("not logged in")
|
||||
&& !lower.contains("not authenticated")
|
||||
&& !lower.contains("no account");
|
||||
tracing::info!("Claude auth status (non-JSON): logged_in={}", is_logged_in);
|
||||
Ok(ClaudeAuthStatus {
|
||||
is_logged_in,
|
||||
email: None,
|
||||
org_name: None,
|
||||
api_key_source: None,
|
||||
api_provider: None,
|
||||
subscription_type: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn auth_login() -> Result<String, String> {
|
||||
tracing::info!("Running claude auth login");
|
||||
|
||||
let output = create_claude_command()
|
||||
.args(["auth", "login"])
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run claude auth login: {}", e))?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
|
||||
if output.status.success() {
|
||||
let message = if stdout.is_empty() { "Login successful".to_string() } else { stdout };
|
||||
tracing::info!("Claude auth login succeeded");
|
||||
Ok(message)
|
||||
} else {
|
||||
let error = if stderr.is_empty() { stdout } else { stderr };
|
||||
tracing::error!("Claude auth login failed: {}", error);
|
||||
Err(format!("Login failed: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn auth_logout() -> Result<String, String> {
|
||||
tracing::info!("Running claude auth logout");
|
||||
|
||||
let output = create_claude_command()
|
||||
.args(["auth", "logout"])
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run claude auth logout: {}", e))?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
|
||||
if output.status.success() {
|
||||
let message = if stdout.is_empty() { "Logged out successfully".to_string() } else { stdout };
|
||||
tracing::info!("Claude auth logout succeeded");
|
||||
Ok(message)
|
||||
} else {
|
||||
let error = if stderr.is_empty() { stdout } else { stderr };
|
||||
tracing::error!("Claude auth logout failed: {}", error);
|
||||
Err(format!("Logout failed: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Plugin Management Commands ====================
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user