Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/tokscale-cli/src/commands/wrapped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,7 @@ fn client_display_name(client: &str) -> Option<&'static str> {
"kilo" => Some("Kilo CLI"),
"mux" => Some("Mux"),
"crush" => Some("Crush"),
"goose" => Some("Goose"),
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
"synthetic" => Some("Synthetic"),
_ => None,
}
Expand Down Expand Up @@ -1398,6 +1399,7 @@ fn client_logo_url(client_name: &str) -> Option<&'static str> {
"Crush" => Some(
"https://raw.githubusercontent.com/junhoyeo/tokscale/6b483d0f2de3717266dec8faed13acd067f90ff3/.github/assets/client-crush.png",
),
"Goose" => Some("https://tokscale.ai/assets/logos/goose.png"),
"Synthetic" => Some("https://tokscale.ai/assets/logos/synthetic.png"),
_ => None,
}
Expand Down
10 changes: 10 additions & 0 deletions crates/tokscale-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,8 @@ pub struct ClientFlags {
pub mux: bool,
#[arg(long, help = "Show only Crush usage")]
pub crush: bool,
#[arg(long, help = "Show only Goose usage")]
pub goose: bool,
#[arg(long, help = "Show only Synthetic usage")]
pub synthetic: bool,
}
Expand Down Expand Up @@ -648,6 +650,7 @@ fn build_client_filter(flags: ClientFlags) -> Option<Vec<String>> {
(ClientId::Kilo, flags.kilo),
(ClientId::Mux, flags.mux),
(ClientId::Crush, flags.crush),
(ClientId::Goose, flags.goose),
]
.into_iter()
.filter(|(_, enabled)| *enabled)
Expand Down Expand Up @@ -2324,6 +2327,7 @@ fn capitalize_client(client: &str) -> String {
"crush" => "Crush".to_string(),
"openclaw" => "openclaw".to_string(),
"hermes" => "Hermes Agent".to_string(),
"goose" => "Goose".to_string(),
"pi" => "Pi".to_string(),
other => other.to_string(),
}
Expand Down Expand Up @@ -3883,6 +3887,7 @@ mod tests {
kilo: false,
mux: false,
crush: false,
goose: false,
synthetic: false,
};
assert_eq!(build_client_filter(flags), None);
Expand All @@ -3909,6 +3914,7 @@ mod tests {
kilo: false,
mux: false,
crush: false,
goose: false,
synthetic: false,
};
assert_eq!(
Expand Down Expand Up @@ -3938,6 +3944,7 @@ mod tests {
kilo: false,
mux: false,
crush: false,
goose: false,
synthetic: false,
};
assert_eq!(
Expand Down Expand Up @@ -3971,6 +3978,7 @@ mod tests {
kilo: false,
mux: false,
crush: false,
goose: false,
synthetic: true,
};
assert_eq!(
Expand Down Expand Up @@ -4000,6 +4008,7 @@ mod tests {
kilo: true,
mux: true,
crush: true,
goose: true,
synthetic: true,
};
let result = build_client_filter(flags);
Expand All @@ -4025,6 +4034,7 @@ mod tests {
assert!(sources.contains(&"kilo".to_string()));
assert!(sources.contains(&"mux".to_string()));
assert!(sources.contains(&"crush".to_string()));
assert!(sources.contains(&"goose".to_string()));
assert!(sources.contains(&"synthetic".to_string()));
}

Expand Down
4 changes: 4 additions & 0 deletions crates/tokscale-cli/src/tui/client_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ pub const CLIENT_UI: [ClientUi; ClientId::COUNT] = [
display_name: "Copilot",
hotkey: 'c',
},
ClientUi {
display_name: "Goose",
hotkey: 'o',
},
];

pub fn display_name(client: ClientId) -> &'static str {
Expand Down
2 changes: 1 addition & 1 deletion crates/tokscale-cli/src/tui/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,7 @@ mod tests {
#[test]
fn test_client_all() {
let clients = ClientId::ALL;
assert_eq!(clients.len(), 18);
assert_eq!(clients.len(), 19);
assert_eq!(clients[0], ClientId::OpenCode);
assert_eq!(clients[1], ClientId::Claude);
assert_eq!(clients[2], ClientId::Codex);
Expand Down
1 change: 1 addition & 0 deletions crates/tokscale-cli/src/tui/ui/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ pub fn get_client_color(client: &str) -> Color {
"droid" => Color::Rgb(16, 185, 129), // #10b981
"openclaw" => Color::Rgb(239, 68, 68), // #ef4444
"hermes" => Color::Rgb(255, 215, 0), // #ffd700
"goose" => Color::Rgb(100, 180, 220), // #64b4dc
_ => Color::Rgb(136, 136, 136), // #888888
}
}
Expand Down
11 changes: 10 additions & 1 deletion crates/tokscale-core/src/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,15 @@ define_clients!(
headless: false,
parse_local: true,
submit_default: true
},
Goose = 18 => {
id: "goose",
root: PathRoot::XdgData,
relative: "goose/sessions/sessions.db",
pattern: "sessions.db",
headless: false,
parse_local: true,
submit_default: true
}
);

Expand Down Expand Up @@ -353,7 +362,7 @@ mod tests {

#[test]
fn test_client_id_count() {
assert_eq!(ClientId::COUNT, 18);
assert_eq!(ClientId::COUNT, 19);
}

#[test]
Expand Down
21 changes: 21 additions & 0 deletions crates/tokscale-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,17 @@ fn parse_all_messages_with_pricing_with_env_strategy(
all_messages.extend(hermes_messages);
}

if let Some(db_path) = &scan_result.goose_db {
let goose_messages: Vec<UnifiedMessage> = sessions::goose::parse_goose_sqlite(db_path)
.into_iter()
.map(|mut msg| {
apply_pricing_if_available(&mut msg, pricing);
msg
})
.collect();
all_messages.extend(goose_messages);
}

for source in &scan_result.crush_dbs {
let crush_messages: Vec<UnifiedMessage> =
sessions::crush::parse_crush_sqlite(&source.db_path)
Expand Down Expand Up @@ -1844,6 +1855,16 @@ pub fn parse_local_clients(options: LocalParseOptions) -> Result<ParsedMessages,
messages.extend(hermes_msgs);
}

if let Some(db_path) = &scan_result.goose_db {
let goose_msgs: Vec<ParsedMessage> = sessions::goose::parse_goose_sqlite(db_path)
.into_iter()
.map(|msg| unified_to_parsed(&msg))
.collect();
let count = summed_parsed_message_count(&goose_msgs);
counts.set(ClientId::Goose, count);
messages.extend(goose_msgs);
}

let crush_msgs: Vec<ParsedMessage> = scan_result
.crush_dbs
.par_iter()
Expand Down
55 changes: 54 additions & 1 deletion crates/tokscale-core/src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub struct ScanResult {
pub synthetic_db: Option<PathBuf>,
pub kilo_db: Option<PathBuf>,
pub hermes_db: Option<PathBuf>,
pub goose_db: Option<PathBuf>,
pub crush_dbs: Vec<CrushDbSource>,
/// Path to the OpenCode legacy JSON directory (for migration cache stat checks)
pub opencode_json_dir: Option<PathBuf>,
Expand All @@ -82,6 +83,7 @@ impl Default for ScanResult {
synthetic_db: None,
kilo_db: None,
hermes_db: None,
goose_db: None,
crush_dbs: Vec::new(),
opencode_json_dir: None,
}
Expand Down Expand Up @@ -434,7 +436,7 @@ fn supports_extra_dir_scanning(client_id: ClientId) -> bool {
// registry rather than scanned file paths.
!matches!(
client_id,
ClientId::Kilo | ClientId::Crush | ClientId::Hermes
ClientId::Kilo | ClientId::Crush | ClientId::Hermes | ClientId::Goose
)
}

Expand Down Expand Up @@ -571,6 +573,7 @@ fn scan_all_clients_with_env_strategy_inner(
| ClientId::KiloCode
| ClientId::Kilo
| ClientId::Hermes
| ClientId::Goose
| ClientId::Crush
) {
continue;
Expand Down Expand Up @@ -796,6 +799,56 @@ fn scan_all_clients_with_env_strategy_inner(
}
}

if enabled.contains(&ClientId::Goose) {
if use_env_roots {
if let Ok(custom_root) = std::env::var("GOOSE_PATH_ROOT") {
let trimmed = custom_root.trim();
if !trimmed.is_empty() {
let custom_path = PathBuf::from(trimmed).join("data/sessions/sessions.db");
if custom_path.is_file() {
result.goose_db = Some(custom_path);
}
}
}
}
if result.goose_db.is_none() {
let xdg_path = ClientId::Goose
.data()
.resolve_path_with_env_strategy(home_dir, use_env_roots);
let xdg = PathBuf::from(xdg_path);
if xdg.is_file() {
result.goose_db = Some(xdg);
}
}
if result.goose_db.is_none() {
let macos_path = PathBuf::from(format!(
"{}/Library/Application Support/goose/sessions/sessions.db",
home_dir
));
if macos_path.is_file() {
result.goose_db = Some(macos_path);
}
}
if result.goose_db.is_none() {
let legacy_macos_path = PathBuf::from(format!(
"{}/Library/Application Support/Block/goose/sessions/sessions.db",
home_dir
));
if legacy_macos_path.is_file() {
result.goose_db = Some(legacy_macos_path);
}
}
if result.goose_db.is_none() {
let legacy_xdg_path = PathBuf::from(format!(
"{}/.local/share/Block/goose/sessions/sessions.db",
home_dir
));
if legacy_xdg_path.is_file() {
result.goose_db = Some(legacy_xdg_path);
}
}
}

if enabled.contains(&ClientId::Crush) {
result.crush_dbs = discover_crush_dbs(home_dir, use_env_roots);
}
Expand Down
Loading