* feat(terminal): replace xterm with ghostty-web Replace xterm.js terminal with ghostty-web implementation Add terminal serialization support for state restoration Apply custom patches to ghostty-web for enhancements * feat(terminal): add bun-pty backend support Switch terminal to ghostty-web with bun-pty backend for better performance Auto-detect and prefer Bun runtime when available for terminal sessions Update terminal viewport write queue handling for improved reliability * fix(terminal): prevent unnecessary resize events Only report terminal resize when dimensions actually change Simplify chunk processing state tracking Disable terminal transparency for consistent rendering * feat(terminal): increase scrollback and buffer limits Increase terminal scrollback buffer from 10k to 50k lines Increase terminal buffer limit from 256k to 1M bytes Add rate limiting and improve output handling for terminal streams
97 lines
2.5 KiB
Rust
97 lines
2.5 KiB
Rust
use anyhow::{anyhow, Result};
|
|
use log::info;
|
|
use serde_json::Value;
|
|
use std::path::PathBuf;
|
|
use tokio::fs;
|
|
|
|
/// Get OpenCode data directory path (~/.local/share/opencode)
|
|
fn get_data_dir() -> PathBuf {
|
|
dirs::home_dir()
|
|
.expect("Cannot determine home directory")
|
|
.join(".local")
|
|
.join("share")
|
|
.join("opencode")
|
|
}
|
|
|
|
/// Get auth file path
|
|
fn get_auth_file() -> PathBuf {
|
|
get_data_dir().join("auth.json")
|
|
}
|
|
|
|
/// Ensure data directory exists
|
|
async fn ensure_data_dir() -> Result<()> {
|
|
let data_dir = get_data_dir();
|
|
fs::create_dir_all(&data_dir).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Read auth.json file
|
|
pub async fn read_auth() -> Result<Value> {
|
|
let auth_file = get_auth_file();
|
|
|
|
if !auth_file.exists() {
|
|
return Ok(Value::Object(serde_json::Map::new()));
|
|
}
|
|
|
|
let content = fs::read_to_string(&auth_file).await?;
|
|
let trimmed = content.trim();
|
|
|
|
if trimmed.is_empty() {
|
|
return Ok(Value::Object(serde_json::Map::new()));
|
|
}
|
|
|
|
serde_json::from_str(trimmed).map_err(|e| anyhow!("Failed to parse auth file: {}", e))
|
|
}
|
|
|
|
/// Write auth.json file with backup
|
|
pub async fn write_auth(auth: &Value) -> Result<()> {
|
|
ensure_data_dir().await?;
|
|
|
|
let auth_file = get_auth_file();
|
|
|
|
// Create backup before writing
|
|
if auth_file.exists() {
|
|
let file_name = auth_file
|
|
.file_name()
|
|
.and_then(|name| name.to_str())
|
|
.ok_or_else(|| anyhow!("Invalid auth file name"))?;
|
|
|
|
let backup_path = auth_file.with_file_name(format!("{file_name}.openchamber.backup"));
|
|
fs::copy(&auth_file, &backup_path).await?;
|
|
info!("Created auth backup: {}", backup_path.display());
|
|
}
|
|
|
|
let json_string = serde_json::to_string_pretty(auth)?;
|
|
fs::write(&auth_file, json_string).await?;
|
|
info!("Successfully wrote auth file");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove provider auth entry from auth.json
|
|
pub async fn remove_provider_auth(provider_id: &str) -> Result<bool> {
|
|
if provider_id.is_empty() {
|
|
return Err(anyhow!("Provider ID is required"));
|
|
}
|
|
|
|
let mut auth = read_auth().await?;
|
|
|
|
let auth_obj = auth
|
|
.as_object_mut()
|
|
.ok_or_else(|| anyhow!("Auth file is not a valid JSON object"))?;
|
|
|
|
if !auth_obj.contains_key(provider_id) {
|
|
info!(
|
|
"Provider {} not found in auth file, nothing to remove",
|
|
provider_id
|
|
);
|
|
return Ok(false);
|
|
}
|
|
|
|
auth_obj.remove(provider_id);
|
|
write_auth(&auth).await?;
|
|
info!("Removed provider auth: {}", provider_id);
|
|
|
|
Ok(true)
|
|
}
|