From 6a50fa946dc0f5f1252a709f8f900eacd85ea1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=9D=A4?= <1240816911@qq.com> Date: Mon, 27 Oct 2025 14:51:08 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20--patch=20=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E6=B3=A8=E5=85=A5=E4=BD=8D=E7=BD=AEbug?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=20wrapper=20=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要改动 ### 1. 修复 Claude Code patcher 注入位置bug (src/utils/claude_code_patcher.rs) - **问题**: statusline auto-refresh 代码被错误注入到 if-else 语句中间,导致语法错误 - **原因**: 代码在 xBB() 调用后寻找分号,但实际是在 try-catch 块中用逗号分隔 - **修复**: 改为查找整个 try-catch 块的结束位置 (}});),在块结束后注入 - **效果**: patch 后的 cli.js 语法正确,Claude Code 可以正常运行 ### 2. 优化 wrapper 模式无翻译时的 I/O 处理 (src/wrapper/injector.rs) - **问题**: 无翻译模式下未使用 Stdio::inherit(),导致终端交互异常 - **修复**: 添加 stdin/stdout/stderr 的 inherit 设置 - **效果**: wrapper 模式在不启用翻译时能正常交互 ### 3. 添加配置目录自动迁移 (src/main.rs) - 添加从旧目录 ~/.claude/88code 到新目录 ~/.claude/byebyecode 的自动迁移 - 更新配置路径引用 ## 测试结果 - ✅ byebyecode --patch 可以成功修补 cli.js 且不破坏语法 - ✅ byebyecode --wrap 可以正常启动并交互 - ✅ Claude Code 在 patch 后可以正常运行 ## 版本 v1.1.11 --- src/main.rs | 18 +++++++++++++- src/utils/claude_code_patcher.rs | 41 ++++++++++++++++++++++++-------- src/wrapper/injector.rs | 5 ++++ 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index c8b2cb2..1d14b0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,9 @@ use byebyecode::wrapper::{find_claude_code, injector::ClaudeCodeInjector}; use std::io::{self, IsTerminal}; fn main() -> Result<(), Box> { + // Migrate legacy config directory if needed + migrate_legacy_config()?; + let cli = Cli::parse_args(); // Handle wrapper mode - inject into Claude Code @@ -197,7 +200,7 @@ fn run_wrapper_mode(cli: &Cli) -> Result<(), Box> { // Load API keys from config let home = dirs::home_dir().ok_or("Could not find home directory")?; - let keys_path = home.join(".claude").join("88code").join("api_keys.toml"); + let keys_path = home.join(".claude").join("byebyecode").join("api_keys.toml"); let (_api_key, glm_key) = if keys_path.exists() { use serde::Deserialize; @@ -260,3 +263,16 @@ fn run_wrapper_mode(cli: &Cli) -> Result<(), Box> { injector.run_with_interception(claude_args) } + +fn migrate_legacy_config() -> Result<(), Box> { + if let Some(home) = dirs::home_dir() { + let old_dir = home.join(".claude").join("88code"); + let new_dir = home.join(".claude").join("byebyecode"); + + if old_dir.exists() && !new_dir.exists() { + std::fs::rename(&old_dir, &new_dir)?; + println!("✓ 已自动迁移配置目录: ~/.claude/88code → ~/.claude/byebyecode"); + } + } + Ok(()) +} diff --git a/src/utils/claude_code_patcher.rs b/src/utils/claude_code_patcher.rs index c14a1f0..06dd2f0 100644 --- a/src/utils/claude_code_patcher.rs +++ b/src/utils/claude_code_patcher.rs @@ -544,17 +544,38 @@ impl ClaudeCodePatcher { if let Some(call_pos) = self.file_content.find(&call_pattern) { println!("✅ Found {} call at position: {}", call_pattern, call_pos); - // Find the semicolon after the call - let search_start = call_pos + call_pattern.len(); - let remaining = &self.file_content[search_start..]; - - if let Some(semicolon_offset) = remaining.find(';') { - let pos = search_start + semicolon_offset + 1; - println!("✅ Injecting after {}; at position: {}", call_pattern, pos); - pos + // Find the try-catch block that contains this call + // Look backward to find 'try{' + let search_back_start = call_pos.saturating_sub(500); + let before_text = &self.file_content[search_back_start..call_pos]; + + if let Some(try_offset) = before_text.rfind("try{") { + let try_pos = search_back_start + try_offset; + println!("✅ Found try block at position: {}", try_pos); + + // Now find the end of the try-catch block + // Look for pattern: }catch(...){...} + let search_forward_start = call_pos; + let remaining = &self.file_content[search_forward_start..]; + + // Find the matching closing brace for the try-catch + // Look for "}});" pattern which typically ends the function + if let Ok(end_pattern) = Regex::new(r"\}\}\);") { + if let Some(end_match) = end_pattern.find(remaining) { + let pos = search_forward_start + end_match.end(); + println!("✅ Injecting after try-catch block at position: {}", pos); + pos + } else { + println!("⚠️ Could not find try-catch end, using fallback"); + return Err("Could not find try-catch block end".into()); + } + } else { + println!("⚠️ Regex error, using fallback"); + return Err("Regex compilation failed".into()); + } } else { - println!("⚠️ No semicolon found, injecting right after {}()", init_func_name); - search_start + println!("⚠️ Could not find try block, using fallback"); + return Err("Could not find try block".into()); } } else { println!("⚠️ Could not find {}() call, using fallback", init_func_name); diff --git a/src/wrapper/injector.rs b/src/wrapper/injector.rs index 6a69a20..bff6970 100644 --- a/src/wrapper/injector.rs +++ b/src/wrapper/injector.rs @@ -114,6 +114,11 @@ impl ClaudeCodeInjector { cmd.env("BYEBYECODE_WRAPPER", "1"); cmd.env("BYEBYECODE_VERSION", env!("CARGO_PKG_VERSION")); + // Inherit stdin/stdout/stderr for interactive use + cmd.stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + let status = cmd.status()?; if !status.success() { From e0e8811a0a663b2bdfdbf2952fce4eb0638a140c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=9D=A4?= <1240816911@qq.com> Date: Mon, 27 Oct 2025 16:46:11 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0packy=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- npm/main/scripts/postinstall.js | 2 +- src/api/cache.rs | 2 +- src/api/client.rs | 37 ++++-- src/api/mod.rs | 145 ++++++++++++++++++++--- src/auto_config/mod.rs | 2 +- src/config/loader.rs | 14 +-- src/config/models.rs | 6 +- src/core/segments/byebyecode_status.rs | 157 +------------------------ src/core/segments/byebyecode_usage.rs | 49 ++++++-- src/core/segments/usage.rs | 2 +- src/ui/components/preview.rs | 12 +- src/ui/themes/presets.rs | 6 +- src/updater.rs | 4 +- 14 files changed, 224 insertions(+), 216 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13d4dcd..aab0f13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,7 +185,7 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byebyecode" -version = "1.1.7" +version = "1.1.11" dependencies = [ "ansi-to-tui", "ansi_term", diff --git a/npm/main/scripts/postinstall.js b/npm/main/scripts/postinstall.js index 02da34a..3f0daa2 100644 --- a/npm/main/scripts/postinstall.js +++ b/npm/main/scripts/postinstall.js @@ -14,7 +14,7 @@ try { const platform = process.platform; const arch = process.arch; const homeDir = os.homedir(); - const claudeDir = path.join(homeDir, '.claude', '88code'); + const claudeDir = path.join(homeDir, '.claude', 'byebyecode'); // Create directory fs.mkdirSync(claudeDir, { recursive: true }); diff --git a/src/api/cache.rs b/src/api/cache.rs index 317d0f2..d0dd3f6 100644 --- a/src/api/cache.rs +++ b/src/api/cache.rs @@ -9,7 +9,7 @@ const CACHE_FRESH_SECONDS: u64 = 300; /// 获取缓存文件路径 fn get_cache_file(cache_type: &str) -> Option { let home = dirs::home_dir()?; - let cache_dir = home.join(".claude").join("88code").join("cache"); + let cache_dir = home.join(".claude").join("byebyecode").join("cache"); // 确保缓存目录存在 fs::create_dir_all(&cache_dir).ok()?; diff --git a/src/api/client.rs b/src/api/client.rs index 6b1fdea..9c8b6ea 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -18,19 +18,38 @@ impl ApiClient { } pub fn get_usage(&self) -> Result> { - let response = self - .client - .post(&self.config.usage_url) - .header("Authorization", format!("Bearer {}", self.config.api_key)) - .header("Content-Type", "application/json") - .send()?; + let is_packyapi = self.config.is_packyapi(); + + let response = if is_packyapi { + self.client + .get(&self.config.usage_url) + .header("Authorization", format!("Bearer {}", self.config.api_key)) + .send()? + } else { + self.client + .post(&self.config.usage_url) + .header("Authorization", format!("Bearer {}", self.config.api_key)) + .header("Content-Type", "application/json") + .send()? + }; if !response.status().is_success() { return Err(format!("Usage API request failed: {}", response.status()).into()); } - let mut usage: UsageData = response.json()?; - usage.calculate(); // 计算使用情况 + let response_text = response.text()?; + + let mut usage: UsageData = if is_packyapi { + let resp: super::PackyUsageResponse = serde_json::from_str(&response_text) + .map_err(|e| format!("Packyapi JSON parse error: {} | Response: {}", e, response_text))?; + UsageData::Packy(resp.data) + } else { + let data: super::Code88UsageData = serde_json::from_str(&response_text) + .map_err(|e| format!("API JSON parse error: {} | Response: {}", e, response_text))?; + UsageData::Code88(data) + }; + + usage.calculate(); Ok(usage) } @@ -59,6 +78,6 @@ impl ApiClient { pub fn check_token_limit(&self) -> Result> { let usage = self.get_usage()?; - Ok(usage.remaining_tokens == 0) + Ok(usage.get_remaining_tokens() == 0) } } diff --git a/src/api/mod.rs b/src/api/mod.rs index a418b85..7a3c751 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -23,8 +23,21 @@ impl Default for ApiConfig { } } +impl ApiConfig { + pub fn is_packyapi(&self) -> bool { + self.usage_url.contains("packyapi.com") + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum UsageData { + Code88(Code88UsageData), + Packy(PackyUsageData), +} + #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct UsageData { +pub struct Code88UsageData { #[serde(rename = "totalTokens")] pub total_tokens: u64, #[serde(rename = "creditLimit")] @@ -32,7 +45,6 @@ pub struct UsageData { #[serde(rename = "currentCredits")] pub current_credits: f64, - // 计算字段(序列化时保存,从缓存读取时也能用) #[serde(default)] pub used_tokens: u64, #[serde(default)] @@ -41,14 +53,74 @@ pub struct UsageData { pub percentage_used: f64, } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PackyUsageResponse { + pub code: bool, + pub data: PackyUsageData, + pub message: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PackyUsageData { + pub expires_at: i64, + pub name: String, + pub object: String, + pub total_available: u64, + pub total_granted: u64, + pub total_used: u64, + pub unlimited_quota: bool, + + #[serde(default)] + pub used_tokens: u64, + #[serde(default)] + pub remaining_tokens: u64, + #[serde(default)] + pub percentage_used: f64, + #[serde(default)] + pub credit_limit: f64, + #[serde(default)] + pub current_credits: f64, +} + impl UsageData { - /// 计算已使用和剩余的积分 pub fn calculate(&mut self) { - // 使用积分而不是token - // creditLimit = 20.0刀, currentCredits = 15.35刀 - // 已使用积分 = 20 - 15.35 = 4.65刀 - // 使用百分比 = (20 - 15.35) / 20 * 100 = 23.25% + match self { + UsageData::Code88(data) => data.calculate(), + UsageData::Packy(data) => data.calculate(), + } + } + + pub fn is_exhausted(&self) -> bool { + match self { + UsageData::Code88(data) => data.is_exhausted(), + UsageData::Packy(data) => data.is_exhausted(), + } + } + + pub fn get_used_tokens(&self) -> u64 { + match self { + UsageData::Code88(data) => data.used_tokens, + UsageData::Packy(data) => data.used_tokens, + } + } + pub fn get_remaining_tokens(&self) -> u64 { + match self { + UsageData::Code88(data) => data.remaining_tokens, + UsageData::Packy(data) => data.remaining_tokens, + } + } + + pub fn get_credit_limit(&self) -> f64 { + match self { + UsageData::Code88(data) => data.credit_limit, + UsageData::Packy(data) => data.credit_limit, + } + } +} + +impl Code88UsageData { + pub fn calculate(&mut self) { let used_credits = self.credit_limit - self.current_credits; self.percentage_used = if self.credit_limit > 0.0 { (used_credits / self.credit_limit * 100.0).clamp(0.0, 100.0) @@ -56,13 +128,8 @@ impl UsageData { 0.0 }; - // 直接使用积分(美元)进行显示 - // used_tokens 和 remaining_tokens 现在表示积分(以美分为单位,便于整数显示) - // 注意:currentCredits可能是负数(超额使用),需要正确处理 - self.used_tokens = (used_credits * 100.0).max(0.0) as u64; // 转换为美分 + self.used_tokens = (used_credits * 100.0).max(0.0) as u64; - // remaining_tokens 需要处理负数情况 - // 如果 current_credits < 0,说明超额,remaining 应该是 0 if self.current_credits < 0.0 { self.remaining_tokens = 0; } else { @@ -70,12 +137,31 @@ impl UsageData { } } - /// 检查额度是否已用完(包括超额使用) pub fn is_exhausted(&self) -> bool { self.current_credits <= 0.0 } } +impl PackyUsageData { + pub fn calculate(&mut self) { + self.used_tokens = self.total_used; + self.remaining_tokens = self.total_available.saturating_sub(self.total_used); + + self.percentage_used = if self.total_granted > 0 { + (self.total_used as f64 / self.total_granted as f64 * 100.0).clamp(0.0, 100.0) + } else { + 0.0 + }; + + self.credit_limit = (self.total_granted as f64) / 100.0; + self.current_credits = (self.remaining_tokens as f64) / 100.0; + } + + pub fn is_exhausted(&self) -> bool { + !self.unlimited_quota && self.remaining_tokens == 0 + } +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct SubscriptionData { #[serde(rename = "subscriptionPlanName")] @@ -125,7 +211,7 @@ fn get_claude_settings_path() -> Option { dirs::home_dir().map(|home| home.join(".claude").join("settings.json")) } -/// Read API key from Claude settings.json if base URL is 88code.org +/// Read API key from Claude settings.json if base URL is 88code.org or packyapi.com pub fn get_api_key_from_claude_settings() -> Option { let settings_path = get_claude_settings_path()?; @@ -138,12 +224,37 @@ pub fn get_api_key_from_claude_settings() -> Option { let env = settings.env?; - // Only use the API key if base URL is 88code.org + // Support both 88code.org and packyapi.com if let Some(base_url) = env.base_url { - if base_url.contains("88code.org") { + if base_url.contains("88code.org") || base_url.contains("packyapi.com") { return env.auth_token; } } None } + +/// Get usage_url from Claude settings.json based on ANTHROPIC_BASE_URL +pub fn get_usage_url_from_claude_settings() -> Option { + let settings_path = get_claude_settings_path()?; + + if !settings_path.exists() { + return None; + } + + let content = std::fs::read_to_string(settings_path).ok()?; + let settings = serde_json::from_str::(&content).ok()?; + + let base_url = settings + .get("env")? + .get("ANTHROPIC_BASE_URL")? + .as_str()?; + + if base_url.contains("packyapi.com") { + Some("https://www.packyapi.com/api/usage/token/".to_string()) + } else if base_url.contains("88code.org") { + Some("https://www.88code.org/api/usage".to_string()) + } else { + None + } +} diff --git a/src/auto_config/mod.rs b/src/auto_config/mod.rs index f60efc7..83f83a1 100644 --- a/src/auto_config/mod.rs +++ b/src/auto_config/mod.rs @@ -13,7 +13,7 @@ impl AutoConfigurator { pub fn new() -> Result> { let home = dirs::home_dir().ok_or("Could not find home directory")?; - let config_dir = home.join(".claude/88code"); + let config_dir = home.join(".claude/byebyecode"); Ok(Self { config_dir }) } diff --git a/src/config/loader.rs b/src/config/loader.rs index 1f2935b..e6d1e47 100644 --- a/src/config/loader.rs +++ b/src/config/loader.rs @@ -54,12 +54,12 @@ impl ConfigLoader { Ok(()) } - /// Get the themes directory path (~/.claude/88code/themes/) + /// Get the themes directory path (~/.claude/byebyecode/themes/) pub fn get_themes_path() -> PathBuf { if let Some(home) = dirs::home_dir() { - home.join(".claude").join("88code").join("themes") + home.join(".claude").join("byebyecode").join("themes") } else { - PathBuf::from(".claude/88code/themes") + PathBuf::from(".claude/byebyecode/themes") } } @@ -133,12 +133,12 @@ impl Config { Ok(()) } - /// Get the default config file path (~/.claude/88code/config.toml) + /// Get the default config file path (~/.claude/byebyecode/config.toml) fn get_config_path() -> PathBuf { if let Some(home) = dirs::home_dir() { - home.join(".claude").join("88code").join("config.toml") + home.join(".claude").join("byebyecode").join("config.toml") } else { - PathBuf::from(".claude/88code/config.toml") + PathBuf::from(".claude/byebyecode/config.toml") } } @@ -146,7 +146,7 @@ impl Config { pub fn init() -> Result<(), Box> { let config_path = Self::get_config_path(); - // Delete existing 88code directory if it exists + // Delete existing byebyecode directory if it exists if let Some(parent) = config_path.parent() { if parent.exists() { println!("Removing existing directory: {}", parent.display()); diff --git a/src/config/models.rs b/src/config/models.rs index 5eaae5c..cbfe38a 100644 --- a/src/config/models.rs +++ b/src/config/models.rs @@ -29,7 +29,7 @@ impl ModelConfig { // First, try to create default models.toml if it doesn't exist if let Some(home_dir) = dirs::home_dir() { - let user_models_path = home_dir.join(".claude/88code").join("models.toml"); + let user_models_path = home_dir.join(".claude/byebyecode").join("models.toml"); if !user_models_path.exists() { let _ = Self::create_default_file(&user_models_path); } @@ -37,7 +37,7 @@ impl ModelConfig { // Try loading from user config directory first, then local let config_paths = [ - dirs::home_dir().map(|d| d.join(".claude/88code").join("models.toml")), + dirs::home_dir().map(|d| d.join(".claude/byebyecode").join("models.toml")), Some(Path::new("models.toml").to_path_buf()), ]; @@ -101,7 +101,7 @@ impl ModelConfig { let template_content = format!( "# CCometixLine Model Configuration\n\ # This file defines model display names and context limits for different LLM models\n\ - # File location: ~/.claude/88code/models.toml\n\ + # File location: ~/.claude/byebyecode/models.toml\n\ \n\ {}\n\ \n\ diff --git a/src/core/segments/byebyecode_status.rs b/src/core/segments/byebyecode_status.rs index 09c4eb2..67c765f 100644 --- a/src/core/segments/byebyecode_status.rs +++ b/src/core/segments/byebyecode_status.rs @@ -1,161 +1,8 @@ use crate::config::Config; use crate::config::InputData; use crate::core::segments::SegmentData; -use std::collections::HashMap; -use std::time::{SystemTime, UNIX_EPOCH}; - -const HEALTH_CHECK_URL: &str = "https://www.88code.org"; -const CACHE_TTL_SECONDS: u64 = 30; // 30秒检查一次健康状态 - -/// 获取缓存路径 -fn get_health_cache_path() -> Option { - dirs::home_dir().map(|home| { - home.join(".claude/88code") - .join(".cache") - .join("88code_health.json") - }) -} - -/// 健康状态缓存 -#[derive(serde::Deserialize, serde::Serialize)] -struct HealthCache { - is_healthy: bool, - timestamp: u64, -} - -fn current_timestamp() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs() -} - -/// 获取进程ID作为随机种子 -fn get_process_id() -> u32 { - std::process::id() -} - -/// 根据进程ID生成一个固定的颜色索引 -/// 每次启动进程会得到不同的颜色,但同一次运行中颜色保持不变 -fn get_random_color() -> u8 { - let colors = get_wave_colors(); - let pid = get_process_id(); - // 使用进程ID对颜色数量取模,得到一个固定的颜色索引 - colors[(pid as usize) % colors.len()] -} - -/// 读取健康状态缓存 -fn read_health_cache() -> Option { - let path = get_health_cache_path()?; - if !path.exists() { - return None; - } - - let content = std::fs::read_to_string(path).ok()?; - let cache: HealthCache = serde_json::from_str(&content).ok()?; - - let now = current_timestamp(); - if now - cache.timestamp < CACHE_TTL_SECONDS { - Some(cache.is_healthy) - } else { - None - } -} - -/// 写入健康状态缓存 -fn write_health_cache(is_healthy: bool) { - if let Some(path) = get_health_cache_path() { - if let Some(parent) = path.parent() { - let _ = std::fs::create_dir_all(parent); - } - - let cache = HealthCache { - is_healthy, - timestamp: current_timestamp(), - }; - - if let Ok(content) = serde_json::to_string(&cache) { - let _ = std::fs::write(path, content); - } - } -} - -/// 检查88code服务健康状态 -fn check_health() -> bool { - use reqwest::blocking::Client; - use std::time::Duration; - - let client = Client::builder() - .timeout(Duration::from_secs(5)) - .user_agent("byebyecode/1.0.0") - .danger_accept_invalid_certs(false) // 接受有效证书 - .build(); - - if let Ok(client) = client { - match client.get(HEALTH_CHECK_URL).send() { - Ok(response) => { - let status = response.status().as_u16(); - // 200-299 都认为是健康的 - return (200..300).contains(&status); - } - Err(_e) => { - // 网络错误时,默认认为服务是健康的(避免误报) - // 只有明确收到错误响应时才认为不健康 - return true; - } - } - } - - // 客户端构建失败,默认健康 - true -} - -/// 获取可用的颜色列表 -fn get_wave_colors() -> Vec { - // 彩虹色相环: 红-橙-黄-绿-青-蓝-紫 - // 使用256色模式的颜色码 - vec![ - 196, // 红 - 202, // 橙 - 226, // 黄 - 46, // 绿 - 51, // 青 - 21, // 蓝 - 129, // 紫 - ] -} pub fn collect(_config: &Config, _input: &InputData) -> Option { - // 先检查缓存 - let is_healthy = if let Some(cached) = read_health_cache() { - cached - } else { - // 缓存过期,重新检查 - let health = check_health(); - write_health_cache(health); - health - }; - - let mut metadata = HashMap::new(); - metadata.insert("healthy".to_string(), is_healthy.to_string()); - - if is_healthy { - // 服务正常,显示静态彩色文本 - // 每次进程启动时颜色不同,但同一次运行中保持固定 - let message = "88code正持续为您服务"; - let color = get_random_color(); - - Some(SegmentData { - primary: format!("\x1b[38;5;{}m{}\x1b[0m", color, message), - secondary: String::new(), - metadata, - }) - } else { - // 服务断开,显示红色警告 - Some(SegmentData { - primary: "\x1b[38;5;196m88code服务断开\x1b[0m".to_string(), - secondary: String::new(), - metadata, - }) - } + // 不再显示任何内容 + None } diff --git a/src/core/segments/byebyecode_usage.rs b/src/core/segments/byebyecode_usage.rs index 61fb019..4e0467f 100644 --- a/src/core/segments/byebyecode_usage.rs +++ b/src/core/segments/byebyecode_usage.rs @@ -35,14 +35,30 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { } }; - // 实时获取数据,不使用缓存 - let usage = fetch_usage_sync(&api_key)?; + let usage_url = segment + .options + .get("usage_url") + .and_then(|v| v.as_str()) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .or_else(crate::api::get_usage_url_from_claude_settings) + .unwrap_or_else(|| "https://www.88code.org/api/usage".to_string()); + + let subscription_url = segment + .options + .get("subscription_url") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| "https://www.88code.org/api/subscription".to_string()); - fn fetch_usage_sync(api_key: &str) -> Option { + let usage = fetch_usage_sync(&api_key, &usage_url)?; + + fn fetch_usage_sync(api_key: &str, usage_url: &str) -> Option { let api_config = ApiConfig { enabled: true, api_key: api_key.to_string(), - ..Default::default() + usage_url: usage_url.to_string(), + subscription_url: String::new(), }; let client = ApiClient::new(api_config).ok()?; @@ -51,19 +67,28 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { } // 处理使用数据 - let used_dollars = usage.used_tokens as f64 / 100.0; - let remaining_dollars = (usage.remaining_tokens as f64 / 100.0).max(0.0); // 确保不显示负数 - let total_dollars = usage.credit_limit; + let used_dollars = usage.get_used_tokens() as f64 / 100.0; + let remaining_dollars = (usage.get_remaining_tokens() as f64 / 100.0).max(0.0); + let total_dollars = usage.get_credit_limit(); let mut metadata = HashMap::new(); metadata.insert("used".to_string(), format!("{:.2}", used_dollars)); metadata.insert("total".to_string(), format!("{:.2}", total_dollars)); metadata.insert("remaining".to_string(), format!("{:.2}", remaining_dollars)); + + // 根据 usage_url 判断是哪个服务,并设置动态图标 + let service_name = if usage_url.contains("packyapi.com") { + "packy" + } else { + "88code" + }; + metadata.insert("service".to_string(), service_name.to_string()); + metadata.insert("dynamic_icon".to_string(), service_name.to_string()); // 检查额度是否用完(包括超额使用) if usage.is_exhausted() { // 实时获取订阅信息 - let subscriptions = fetch_subscriptions_sync(&api_key); + let subscriptions = fetch_subscriptions_sync(&api_key, &subscription_url); if let Some(subs) = subscriptions { let active_subs: Vec<_> = subs.iter().filter(|s| s.is_active).collect(); @@ -110,11 +135,15 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { }) } -fn fetch_subscriptions_sync(api_key: &str) -> Option> { +fn fetch_subscriptions_sync( + api_key: &str, + subscription_url: &str, +) -> Option> { let api_config = ApiConfig { enabled: true, api_key: api_key.to_string(), - ..Default::default() + usage_url: String::new(), + subscription_url: subscription_url.to_string(), }; let client = ApiClient::new(api_config).ok()?; diff --git a/src/core/segments/usage.rs b/src/core/segments/usage.rs index f6ca14d..05fec65 100644 --- a/src/core/segments/usage.rs +++ b/src/core/segments/usage.rs @@ -69,7 +69,7 @@ impl UsageSegment { let home = dirs::home_dir()?; Some( home.join(".claude") - .join("88code") + .join("byebyecode") .join(".api_usage_cache.json"), ) } diff --git a/src/ui/components/preview.rs b/src/ui/components/preview.rs index 41361ef..641d6a3 100644 --- a/src/ui/components/preview.rs +++ b/src/ui/components/preview.rs @@ -195,18 +195,20 @@ impl PreviewComponent { }, }, SegmentId::ByeByeCodeSubscription => SegmentData { - primary: "\x1b[38;5;46m88code正持续为您服务\x1b[0m".to_string(), + primary: "Pro ¥99/月 (可重置3次, 剩余30天)".to_string(), secondary: "".to_string(), metadata: { let mut map = HashMap::new(); - map.insert("plan".to_string(), "Pro".to_string()); - map.insert("price".to_string(), "¥99/月".to_string()); - map.insert("active".to_string(), "true".to_string()); + map.insert("plan_0".to_string(), "Pro".to_string()); + map.insert("price_0".to_string(), "¥99/月".to_string()); + map.insert("status_0".to_string(), "active".to_string()); + map.insert("reset_times_0".to_string(), "3".to_string()); + map.insert("remaining_days_0".to_string(), "30".to_string()); map }, }, SegmentId::ByeByeCodeStatus => SegmentData { - primary: "88code正持续为您服务".to_string(), + primary: "".to_string(), secondary: "".to_string(), metadata: HashMap::new(), }, diff --git a/src/ui/themes/presets.rs b/src/ui/themes/presets.rs index 4639a16..e9764bf 100644 --- a/src/ui/themes/presets.rs +++ b/src/ui/themes/presets.rs @@ -50,12 +50,12 @@ impl ThemePresets { Ok(config) } - /// Get the themes directory path (~/.claude/88code/themes/) + /// Get the themes directory path (~/.claude/byebyecode/themes/) fn get_themes_path() -> std::path::PathBuf { if let Some(home) = dirs::home_dir() { - home.join(".claude/88code").join("themes") + home.join(".claude/byebyecode").join("themes") } else { - std::path::PathBuf::from(".claude/88code/themes") + std::path::PathBuf::from(".claude/byebyecode/themes") } } diff --git a/src/updater.rs b/src/updater.rs index 80f2e0b..eb9e701 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -80,7 +80,7 @@ impl UpdateState { let config_dir = dirs::home_dir() .unwrap_or_default() .join(".claude") - .join("88code"); + .join("byebyecode"); let state_file = config_dir.join(".update_state.json"); @@ -195,7 +195,7 @@ impl UpdateState { let config_dir = dirs::home_dir() .unwrap_or_default() .join(".claude") - .join("88code"); + .join("byebyecode"); std::fs::create_dir_all(&config_dir)?; let state_file = config_dir.join(".update_state.json");