Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e1a3612
docs: sync architecture diagram and component guides with recent code…
mm65x Mar 12, 2026
f5c7bb7
chore: bump version to v0.2.4
mm65x Mar 12, 2026
119d76e
chore: remove dangling empty doc comments
mm65x Mar 12, 2026
ecfa55e
fix(tui): enable full group-by column resolution in history mode
mm65x Mar 13, 2026
72fb877
chore: add CODEOWNERS file
mm65x Mar 16, 2026
3ec1b3e
fix: add missing must_use attrs and fix doc comment on card()
mm65x Mar 22, 2026
9fa1ee5
fix: re-price zero-cost records and improve error resilience (#14)
mm65x Mar 22, 2026
d8974b5
feat: toggle summary cards in top view
mm65x Jul 25, 2026
9314335
feat(tui): add mouse navigation support
mm65x Jul 25, 2026
06c816b
fix(cost): refresh routed model pricing
mm65x Jul 25, 2026
8d024c1
feat: support local pricing overrides
mm65x Jul 25, 2026
b45eef7
feat: add local usage source
mm65x Jul 25, 2026
b4d737d
feat(tui): add usage visualizations
mm65x Jul 25, 2026
0220b7a
fix(tui): harden empty and error states (#16)
mm65x Aug 7, 2026
e7a06fb
Merge develop into summary-card visibility feature
mm65x Aug 7, 2026
6327c81
Merge develop into routed pricing fix
mm65x Aug 8, 2026
a81c85a
Merge develop into pricing override feature
mm65x Aug 8, 2026
99a0c59
Merge develop into local usage source feature
mm65x Aug 8, 2026
d08d20b
Merge develop into mouse navigation feature
mm65x Aug 8, 2026
3bfb871
Merge develop into usage visualizations feature
mm65x Aug 8, 2026
5fe8cec
feat(tui): add persistent metric visibility
mm65x Aug 8, 2026
a6ef1d5
Merge pull request #29 from mm65x/codex/issue-28-metric-visibility
mm65x Aug 8, 2026
27eb6eb
Merge pull request #19 from mm65x/codex/fix-provider-pricing
mm65x Aug 8, 2026
01e3a9e
chore(release): prepare v0.2.6
mm65x Aug 8, 2026
5259878
Merge pull request #30 from mm65x/release/0.2.6
mm65x Aug 8, 2026
a0bb953
Merge remote-tracking branch 'origin/develop' into codex/feat-pricing…
mm65x Aug 8, 2026
75a9a49
Merge pull request #17 from mm65x/codex/feat-mouse-support
mm65x Aug 8, 2026
fdefb9f
Merge pull request #20 from mm65x/codex/feat-pricing-overrides
mm65x Aug 8, 2026
e7e4af1
Merge pull request #21 from mm65x/codex/feat-additional-usage-sources
mm65x Aug 8, 2026
f87cdf4
feat(tui): add hover feedback
mm65x Aug 8, 2026
03c65bf
Merge pull request #32 from mm65x/codex/issue-27-hover-feedback
mm65x Aug 8, 2026
8926acb
Merge develop into summary card visibility
mm65x Aug 8, 2026
e2b2140
style: format merged tui changes
mm65x Aug 8, 2026
262bbd9
fix: complete summary card merge
mm65x Aug 8, 2026
1ae9697
Merge pull request #18 from mm65x/codex/feat-toggle-summary-cards
mm65x Aug 8, 2026
0fd4d66
Merge develop into usage visuals
mm65x Aug 8, 2026
bb1a199
Fix import formatting
mm65x Aug 8, 2026
772b2df
Merge pull request #22 from mm65x/codex/feat-usage-visuals
mm65x Aug 8, 2026
e777a0b
feat: add interactive configuration editor
mm65x Aug 8, 2026
d6c3d66
feat: add declarative custom JSONL sources
mm65x Aug 8, 2026
2bde428
Merge pull request #33 from mm65x/codex/issue-24-interactive-config
mm65x Aug 8, 2026
1ee943a
chore: resolve custom source branch with develop
mm65x Aug 8, 2026
20cd35e
Merge pull request #34 from mm65x/codex/issue-25-custom-sources
mm65x Aug 9, 2026
a96ca6a
feat: add versioned JSON status output
mm65x Aug 9, 2026
fe04f67
Merge pull request #35 from mm65x/codex/issue-23-status
mm65x Aug 9, 2026
464cc5b
feat: parse structured telemetry usage records
mm65x Aug 9, 2026
86ba5d6
Merge pull request #36 from mm65x/codex/issue-11-telemetry
mm65x Aug 9, 2026
9721c3e
docs: align supported source table
mm65x Aug 9, 2026
bfe2257
feat: add settings editor mouse input
mm65x Aug 9, 2026
398fd9e
refactor: share settings layout geometry
mm65x Aug 9, 2026
1b859d0
fix: preserve settings edits during mouse input
mm65x Aug 9, 2026
e55d3cd
Merge pull request #38 from mm65x/codex/issue-37-docs
mm65x Aug 9, 2026
c459926
Merge pull request #40 from mm65x/codex/issue-39-settings-mouse
mm65x Aug 9, 2026
7165c94
chore(release): prepare v0.2.7
mm65x Aug 9, 2026
60a9740
merge: resolve master divergence for v0.2.7
mm65x Aug 9, 2026
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
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tokemon"
version = "0.2.6"
version = "0.2.7"
edition = "2021"
description = "Unified LLM token usage tracking across all providers"
license = "MIT"
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Unified token usage tracking across all your AI coding tools. `tokemon top` prov
- **SQLite cache** — parsed data is cached for instant repeated runs and survives log rotation
- **Budget pacemaker** — set daily/weekly/monthly spending limits with progress tracking
- **Statusline mode** — compact one-line output for shell prompts and status bars (`tokemon statusline`)
- **Versioned status JSON** — stable machine-readable summaries for lightweight clients (`tokemon status --json`)
- **Session breakdown** — per-session cost analysis across all providers (`tokemon sessions`)
- **MCP server** — expose usage data to AI tools via Model Context Protocol (`tokemon mcp`)
- **Two display modes** — compact one-row-per-day (default) or detailed per-model breakdown with responsive API Provider and Client columns
Expand Down Expand Up @@ -111,6 +112,7 @@ tokemon [OPTIONS] <COMMAND>
Commands:
top Live monitoring dashboard
report Generate a static usage report (table, json, or csv)
status Emit a versioned machine-readable status document
statusline Compact one-line output for shell prompts and status bars
budget Show spending vs configured limits
sessions Show per-session cost breakdown
Expand Down Expand Up @@ -169,6 +171,23 @@ cost = true

CLI flags always override config values.

### Local pricing overrides

Create `~/.config/tokemon/pricing_override.json` to replace downloaded prices or add custom models:

```json
{
"custom-model": {
"input_cost_per_token": 0.000001,
"output_cost_per_token": 0.000004,
"cache_read_cost": 0.0000001,
"cache_creation_cost": 0.00000125
}
}
```

Prices are in USD per token. Entries are merged by exact model key after the base pricing data is loaded. Supplied fields replace base values, while omitted fields keep their base values. The file is optional and changes take effect the next time `tokemon` starts.

## Supported Providers

| Provider | Log Location | Format |
Expand All @@ -181,7 +200,7 @@ CLI flags always override config values.
| Cline | VSCode globalStorage | JSON |
| Roo Code | VSCode globalStorage | JSON |
| Kilo Code | VSCode globalStorage | JSON |
| Copilot | VSCode workspaceStorage | JSON (stub) |
| Copilot | VSCode globalStorage telemetry directories | JSONL |
| Cursor | `~/.config/tokscale/cursor-cache/usage*.csv` | CSV |
| Qwen Code | `~/.qwen/tmp/{project}/session.json` | JSON |
| Pi Agent | `~/.pi/agent/sessions/{project}/*.jsonl` | JSONL |
Expand Down
19 changes: 19 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use chrono::NaiveDate;
use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum DisplayMode {
Expand Down Expand Up @@ -112,6 +113,8 @@ pub enum Frequency {
pub enum Commands {
/// Generate a static usage report (table, json, or csv)
Report,
/// Emit a versioned machine-readable status document
Status,
/// Compact one-line output for shell prompts and status bars
Statusline,
/// Show budget progress against configured limits
Expand All @@ -120,6 +123,13 @@ pub enum Commands {
Discover,
/// Generate default config file at ~/.config/tokemon/config.toml
Init,
/// Manage declarative local usage sources
Source {
#[command(subcommand)]
command: SourceCommands,
},
/// Open the interactive configuration editor
Config,
/// Show per-session cost breakdown
Sessions {
/// Show top N sessions by cost
Expand All @@ -145,3 +155,12 @@ pub enum Commands {
interval: u64,
},
}

#[derive(Subcommand)]
pub enum SourceCommands {
/// Validate a definition and preview discovered records
Validate {
/// TOML definition file to validate
definition: PathBuf,
},
}
79 changes: 72 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fs;
use std::io::ErrorKind;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -303,22 +304,59 @@ impl Config {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let content = toml::to_string_pretty(self)?;
let mut document = match fs::read_to_string(&path) {
Ok(existing) => toml::from_str::<toml::Value>(&existing)
.unwrap_or_else(|_| toml::Value::Table(toml::map::Map::new())),
Err(error) if error.kind() == ErrorKind::NotFound => {
toml::Value::Table(toml::map::Map::new())
}
Err(error) => return Err(error.into()),
};
let updated = toml::Value::try_from(self)?;
merge_toml_tables(&mut document, updated);
let content = toml::to_string_pretty(&document)?;
let header = "# Tokemon configuration\n\
# Location: ~/.config/tokemon/config.toml\n\
#\n\
# Changes here affect default behavior.\n\
# CLI flags always override config values.\n\n";
fs::write(&path, format!("{header}{content}"))?;
let temporary_path = path.with_extension(format!("toml.{}.tmp", std::process::id()));
fs::write(&temporary_path, format!("{header}{content}"))?;
match fs::rename(&temporary_path, &path) {
Ok(()) => {}
Err(error) if error.kind() == ErrorKind::AlreadyExists => {
fs::remove_file(&path)?;
fs::rename(&temporary_path, &path)?;
}
Err(error) => {
let _ = fs::remove_file(&temporary_path);
return Err(error.into());
}
}
Ok(())
}

pub fn config_path() -> PathBuf {
let config_dir = directories::ProjectDirs::from("", "", "tokemon").map_or_else(
|| paths::home_dir().join(".config/tokemon"),
|d| d.config_dir().to_path_buf(),
);
config_dir.join(CONFIG_FILENAME)
paths::config_dir().join(CONFIG_FILENAME)
}
}

fn merge_toml_tables(existing: &mut toml::Value, updated: toml::Value) {
match updated {
toml::Value::Table(updated_table) => {
if let toml::Value::Table(existing_table) = existing {
for (key, value) in updated_table {
if let Some(existing_value) = existing_table.get_mut(&key) {
merge_toml_tables(existing_value, value);
} else {
existing_table.insert(key, value);
}
}
} else {
*existing = toml::Value::Table(updated_table);
}
}
updated => *existing = updated,
}
}

Expand Down Expand Up @@ -422,4 +460,31 @@ mod tests {
assert_eq!(validated.month_bucket_days, 1);
assert_eq!(validated.tick_interval, 300); // Clamped to 300
}

#[test]
fn merging_saved_values_preserves_unknown_keys() {
let mut existing: toml::Value = toml::from_str(
r#"
custom_option = "keep"
[columns]
custom_column = true
cost = false
"#,
)
.expect("valid TOML");
let updated: toml::Value = toml::from_str(
r#"
[columns]
cost = true
"#,
)
.expect("valid TOML");

merge_toml_tables(&mut existing, updated);

let table = existing.as_table().expect("root table");
assert_eq!(table["custom_option"].as_str(), Some("keep"));
assert_eq!(table["columns"]["custom_column"].as_bool(), Some(true));
assert_eq!(table["columns"]["cost"].as_bool(), Some(true));
}
}
Loading
Loading