From 7de7f0806f0c42418e0504390d2d1b6e77b98ee5 Mon Sep 17 00:00:00 2001 From: FabioRocha231 Date: Sun, 5 Jul 2026 13:20:46 -0300 Subject: [PATCH 1/4] feat: scaffold Rust crate (edition 2024, serde + jiff) Slice 0 of the bottom-up Rust rewrite (#27): the crate skeleton so every later slice lands its module + test against a green build. - rust/ single binary crate 'tokenline', flat src/, edition 2024, MSRV 1.85. - deps: serde + serde_json + jiff (jiff::Timestamp for ISO-8601, lands slice 3). - panic = "unwind" in the release profile (the final slice's catch_unwind needs it). - main.rs is a stub; lib.rs is the module root. cargo build/run/test green (0 tests). --- rust/.gitignore | 1 + rust/Cargo.lock | 234 +++++++++++++++++++++++++++++++++++++++++++++++ rust/Cargo.toml | 27 ++++++ rust/src/lib.rs | 4 + rust/src/main.rs | 6 ++ 5 files changed, 272 insertions(+) create mode 100644 rust/.gitignore create mode 100644 rust/Cargo.lock create mode 100644 rust/Cargo.toml create mode 100644 rust/src/lib.rs create mode 100644 rust/src/main.rs diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..a3dfa35 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,234 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +dependencies = [ + "defmt", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-static" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6142247df1a93c2b3587402a19710be3e6e942f1581a1702e76408f2c21d6590" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokenline" +version = "0.1.0" +dependencies = [ + "jiff", + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..08945d4 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tokenline" +version = "0.1.0" +edition = "2024" +rust-version = "1.85" +description = "A cache-aware statusline for AI coding CLIs" +license = "MIT" +repository = "https://github.com/inbrace-tech/tokenline" + +[[bin]] +name = "tokenline" +path = "src/main.rs" + +[lib] +name = "tokenline" +path = "src/lib.rs" + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +jiff = "0.2" + +[profile.release] +opt-level = "z" +lto = true +strip = true +panic = "unwind" # catch_unwind MUST work; abort would be a non-zero exit diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..819b919 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,4 @@ +//! tokenline — a cache-aware statusline for AI coding CLIs. +//! +//! Pure domain (deterministic, golden-testable) and a thin stateful edge. +//! Modules land bottom-up, one slice per PR (see inbrace-tech/tokenline#27). diff --git a/rust/src/main.rs b/rust/src/main.rs new file mode 100644 index 0000000..19193fd --- /dev/null +++ b/rust/src/main.rs @@ -0,0 +1,6 @@ +// tokenline — a cache-aware statusline for AI coding CLIs. +// +// Slice 0: scaffold only. The real edge (stdin → now → cache_dir → render → +// print → exit(0), with catch_unwind so a panic never crashes the host) lands +// in the final slice; the pure domain modules land bottom-up before it. +fn main() {} From faeeb3b3b24126b3d6ddeb23e64202bde1c067ff Mon Sep 17 00:00:00 2001 From: FabioRocha231 Date: Sun, 5 Jul 2026 13:22:27 -0300 Subject: [PATCH 2/4] feat: fmt_k / fmt_eta helpers (parity with tokenline.sh) Slice 1 (#27). Pure formatting helpers matching the bash fmt_k (awk %.1f token compaction) and fmt_eta (human ETA from seconds). Covered by #[cfg(test)] with the tier boundaries and the golden's 3d4h reset ETA. --- rust/src/fmt.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ rust/src/lib.rs | 2 ++ 2 files changed, 75 insertions(+) create mode 100644 rust/src/fmt.rs diff --git a/rust/src/fmt.rs b/rust/src/fmt.rs new file mode 100644 index 0000000..4a5f778 --- /dev/null +++ b/rust/src/fmt.rs @@ -0,0 +1,73 @@ +//! Formatting helpers, byte-for-byte parity with `tokenline.sh`. +//! +//! This slice carries `fmt_k` / `fmt_eta`; the 256-color palette and the +//! ISO-8601 → epoch parser land in the next two slices. + +/// Compact token count, mirroring the bash `fmt_k` (awk `%.1f`): +/// `1_500_000 -> "1.5M"`, `25_600 -> "25.6k"`, `900 -> "900"`. +pub fn fmt_k(v: u64) -> String { + if v >= 1_000_000 { + format!("{:.1}M", v as f64 / 1_000_000.0) + } else if v >= 1_000 { + format!("{:.1}k", v as f64 / 1_000.0) + } else { + v.to_string() + } +} + +/// Human ETA from raw seconds, mirroring the bash `fmt_eta`: +/// `<=0 -> "now"`, `<1h -> "%dm"`, `<1d -> "%dh%dm"/"%dh"`, else `"%dd%dh"/"%dd"`. +pub fn fmt_eta(secs: i64) -> String { + if secs <= 0 { + return "now".to_string(); + } + if secs < 3600 { + return format!("{}m", secs / 60); + } + if secs < 86_400 { + let (h, m) = (secs / 3600, (secs % 3600) / 60); + return if m > 0 { + format!("{}h{}m", h, m) + } else { + format!("{}h", h) + }; + } + let (d, h) = (secs / 86_400, (secs % 86_400) / 3600); + if h > 0 { + format!("{}d{}h", d, h) + } else { + format!("{}d", d) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fmt_k_tiers() { + assert_eq!(fmt_k(0), "0"); + assert_eq!(fmt_k(900), "900"); + assert_eq!(fmt_k(999), "999"); + assert_eq!(fmt_k(1_000), "1.0k"); + assert_eq!(fmt_k(2_000), "2.0k"); + assert_eq!(fmt_k(2_200), "2.2k"); + assert_eq!(fmt_k(25_600), "25.6k"); + assert_eq!(fmt_k(86_600), "86.6k"); + assert_eq!(fmt_k(1_000_000), "1.0M"); + assert_eq!(fmt_k(1_500_000), "1.5M"); + } + + #[test] + fn fmt_eta_tiers() { + assert_eq!(fmt_eta(-5), "now"); + assert_eq!(fmt_eta(0), "now"); + assert_eq!(fmt_eta(30), "0m"); // %dm with integer division, like bash + assert_eq!(fmt_eta(90), "1m"); + assert_eq!(fmt_eta(3_600), "1h"); + assert_eq!(fmt_eta(3_660), "1h1m"); + assert_eq!(fmt_eta(86_400), "1d"); + assert_eq!(fmt_eta(90_000), "1d1h"); + assert_eq!(fmt_eta(273_600), "3d4h"); // 5h-window reset ETA in the golden + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 819b919..7d13ae2 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2,3 +2,5 @@ //! //! Pure domain (deterministic, golden-testable) and a thin stateful edge. //! Modules land bottom-up, one slice per PR (see inbrace-tech/tokenline#27). + +pub mod fmt; From 50cf5afd55d054d50aae442b8616d87d6cf36e24 Mon Sep 17 00:00:00 2001 From: FabioRocha231 Date: Sun, 5 Jul 2026 13:27:21 -0300 Subject: [PATCH 3/4] feat: 256-color palette (byte parity with tokenline.sh) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 2 (#27). The COLOR_* / STYLE_* constants from tokenline.sh:21-30 as exact SGR sequences (RESET is [00m, BLINK [1;5m — byte-identical so the golden oracle diffs clean). Test asserts each against the bytes captured from bash output. --- rust/src/fmt.rs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/rust/src/fmt.rs b/rust/src/fmt.rs index 4a5f778..1343ad6 100644 --- a/rust/src/fmt.rs +++ b/rust/src/fmt.rs @@ -1,7 +1,24 @@ //! Formatting helpers, byte-for-byte parity with `tokenline.sh`. //! -//! This slice carries `fmt_k` / `fmt_eta`; the 256-color palette and the -//! ISO-8601 → epoch parser land in the next two slices. +//! Carries `fmt_k` / `fmt_eta` and the 256-color palette; the ISO-8601 → epoch +//! parser lands in the next slice. + +/// 256-color SGR sequences, byte-identical to `tokenline.sh`'s `COLOR_*` / +/// `STYLE_*` constants (`tokenline.sh:21-30`). Byte parity matters: the golden +/// oracle diffs the raw escape sequences, so `RESET` is `[00m` (two digits), +/// not `[0m`, exactly as the bash emits. +pub mod color { + pub const GRAY: &str = "\x1b[38;5;244m"; + pub const DARK_GRAY: &str = "\x1b[38;5;240m"; + pub const CYAN: &str = "\x1b[38;5;51m"; + pub const YELLOW: &str = "\x1b[38;5;226m"; + pub const MAGENTA: &str = "\x1b[38;5;201m"; + pub const ORANGE: &str = "\x1b[38;5;208m"; + pub const RED: &str = "\x1b[38;5;196m"; + pub const GREEN: &str = "\x1b[38;5;46m"; + pub const RESET: &str = "\x1b[00m"; + pub const BLINK: &str = "\x1b[1;5m"; +} /// Compact token count, mirroring the bash `fmt_k` (awk `%.1f`): /// `1_500_000 -> "1.5M"`, `25_600 -> "25.6k"`, `900 -> "900"`. @@ -58,6 +75,22 @@ mod tests { assert_eq!(fmt_k(1_500_000), "1.5M"); } + #[test] + fn palette_matches_bash_bytes() { + // Exact bytes captured from `tokenline.sh` output (cat -v): ESC[38;5;Nm. + assert_eq!(color::GRAY, "\x1b[38;5;244m"); + assert_eq!(color::CYAN, "\x1b[38;5;51m"); + assert_eq!(color::YELLOW, "\x1b[38;5;226m"); + assert_eq!(color::MAGENTA, "\x1b[38;5;201m"); + assert_eq!(color::ORANGE, "\x1b[38;5;208m"); + assert_eq!(color::GREEN, "\x1b[38;5;46m"); + assert_eq!(color::RED, "\x1b[38;5;196m"); + assert_eq!(color::DARK_GRAY, "\x1b[38;5;240m"); + // bash COLOR_RESET is [00m (two digits), STYLE_BLINK is [1;5m. + assert_eq!(color::RESET, "\x1b[00m"); + assert_eq!(color::BLINK, "\x1b[1;5m"); + } + #[test] fn fmt_eta_tiers() { assert_eq!(fmt_eta(-5), "now"); From cfa0caf2450f5ba07f857de6ff392f8690d8564e Mon Sep 17 00:00:00 2001 From: FabioRocha231 Date: Sun, 5 Jul 2026 13:29:05 -0300 Subject: [PATCH 4/4] feat: ISO-8601 -> epoch via jiff Slice 3 (#27). Replaces the bash epoch_from_iso (date -d) with jiff::Timestamp parsing -> epoch seconds; None on malformed input. Transcripts and rate-limit resets are fixed-format UTC, so no tz database. Tests cover exact UTC epochs, sub-second truncation, and invalid input. --- rust/src/fmt.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/rust/src/fmt.rs b/rust/src/fmt.rs index 1343ad6..16814c1 100644 --- a/rust/src/fmt.rs +++ b/rust/src/fmt.rs @@ -1,7 +1,17 @@ //! Formatting helpers, byte-for-byte parity with `tokenline.sh`. //! -//! Carries `fmt_k` / `fmt_eta` and the 256-color palette; the ISO-8601 → epoch -//! parser lands in the next slice. +//! Carries `fmt_k` / `fmt_eta`, the 256-color palette, and the ISO-8601 → epoch +//! parser. + +use jiff::Timestamp; + +/// Parse an RFC 3339 / ISO-8601 UTC timestamp to epoch seconds, or `None` on a +/// malformed value. Replaces the bash `epoch_from_iso` (`date -d`): transcripts +/// and rate-limit resets are fixed-format UTC, so `jiff::Timestamp` covers them +/// without pulling a timezone database. +pub fn iso8601_to_epoch(iso: &str) -> Option { + iso.parse::().ok().map(|t| t.as_second()) +} /// 256-color SGR sequences, byte-identical to `tokenline.sh`'s `COLOR_*` / /// `STYLE_*` constants (`tokenline.sh:21-30`). Byte parity matters: the golden @@ -75,6 +85,34 @@ mod tests { assert_eq!(fmt_k(1_500_000), "1.5M"); } + #[test] + fn iso8601_to_epoch_parses_utc() { + // Exact epochs (verified independently). now in the golden = 1_783_000_000. + assert_eq!( + iso8601_to_epoch("2026-07-05T18:00:00Z"), + Some(1_783_274_400) + ); + assert_eq!( + iso8601_to_epoch("2026-07-06T12:00:00Z"), + Some(1_783_339_200) + ); + assert_eq!( + iso8601_to_epoch("2026-07-05T00:00:00Z"), + Some(1_783_209_600) + ); + } + + #[test] + fn iso8601_to_epoch_fractional_and_invalid() { + // Sub-second precision truncates to the whole second. + assert_eq!( + iso8601_to_epoch("2026-07-05T18:00:00.500Z"), + Some(1_783_274_400) + ); + assert_eq!(iso8601_to_epoch("not a timestamp"), None); + assert_eq!(iso8601_to_epoch(""), None); + } + #[test] fn palette_matches_bash_bytes() { // Exact bytes captured from `tokenline.sh` output (cat -v): ESC[38;5;Nm.