use serde_json::Value;
use std::fs;
use std::path::PathBuf;
const STATUSLINE_SCRIPT: &str = r#"#!/bin/bash
# abtop StatusLine hook — writes rate limit data for abtop to read.
# Installed by: abtop --setup
INPUT=$(cat)
python3 -c "
import sys, json, time, os
data = json.loads(sys.argv[1])
rl = data.get('rate_limits')
if not rl:
sys.exit(0)
out = {'source': 'claude', 'updated_at': int(time.time())}
fh = rl.get('five_hour')
if fh:
out['five_hour'] = {'used_percentage': fh.get('used_percentage', 0), 'resets_at': fh.get('resets_at', 0)}
sd = rl.get('seven_day')
if sd:
out['seven_day'] = {'used_percentage': sd.get('used_percentage', 0), 'resets_at': sd.get('resets_at', 0)}
home = os.path.expanduser('~')
with open(os.path.join(home, '.claude', 'abtop-rate-limits.json'), 'w') as f:
json.dump(out, f)
" "$INPUT" 2>/dev/null
"#;
fn claude_dir() -> PathBuf {
dirs::home_dir().unwrap_or_default().join(".claude")
}
fn script_path() -> PathBuf {
claude_dir().join("abtop-statusline.sh")
}
fn settings_path() -> PathBuf {
claude_dir().join("settings.json")
}
pub fn run_setup() {
println!("abtop --setup: configuring Claude Code StatusLine hook\n");
let dir = claude_dir();
if let Err(e) = fs::create_dir_all(&dir) {
eprintln!(" ✗ failed to create {}: {}", dir.display(), e);
std::process::exit(1);
}
let script = script_path();
match fs::write(&script, STATUSLINE_SCRIPT) {
Ok(_) => {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(&script, fs::Permissions::from_mode(0o755));
}
println!(" ✓ wrote {}", script.display());
}
Err(e) => {
eprintln!(" ✗ failed to write {}: {}", script.display(), e);
std::process::exit(1);
}
}
let settings_file = settings_path();
let mut settings: Value = if settings_file.exists() {
let content = match fs::read_to_string(&settings_file) {
Ok(c) => c,
Err(e) => {
eprintln!(" ✗ cannot read {}: {}", settings_file.display(), e);
std::process::exit(1);
}
};
match serde_json::from_str(&content) {
Ok(v) => v,
Err(e) => {
eprintln!(" ✗ {} contains invalid JSON: {}", settings_file.display(), e);
eprintln!(" fix the file manually before running --setup");
std::process::exit(1);
}
}
} else {
Value::Object(Default::default())
};
let obj = settings.as_object_mut().unwrap();
if let Some(existing) = obj.get("statusLine") {
if let Some(existing_obj) = existing.as_object() {
if let Some(cmd) = existing_obj.get("command") {
let cmd_str = cmd.as_str().unwrap_or("");
if !cmd_str.contains("abtop") {
eprintln!(" ⚠ statusLine already configured: {}", cmd_str);
eprintln!(" to override, remove the existing statusLine key from:");
eprintln!(" {}", settings_file.display());
std::process::exit(1);
}
}
}
}
obj.insert(
"statusLine".to_string(),
serde_json::json!({
"type": "command",
"command": script.display().to_string()
}),
);
match fs::write(&settings_file, serde_json::to_string_pretty(&settings).unwrap_or_default()) {
Ok(_) => println!(" ✓ updated {}", settings_file.display()),
Err(e) => {
eprintln!(" ✗ failed to update {}: {}", settings_file.display(), e);
std::process::exit(1);
}
}
println!("\n done! rate limit data will appear in abtop after the next Claude response.");
println!(" restart any running Claude Code sessions to activate.");
}