From d6b22ea528cc4e1ae4df668cdbb8fdad79792fb6 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Mon, 24 Aug 2026 13:36:59 +0200 Subject: [PATCH] feat(usage): pin the TokenTracker CLI invocations --- .../Usage/TokenTracker/TokenTrackerArgs.cs | 31 ++++++++++++++ .../TokenTracker/TokenTrackerArgsTests.cs | 42 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/ClaudeDo.Worker/Usage/TokenTracker/TokenTrackerArgs.cs create mode 100644 tests/ClaudeDo.Worker.Tests/Usage/TokenTracker/TokenTrackerArgsTests.cs diff --git a/src/ClaudeDo.Worker/Usage/TokenTracker/TokenTrackerArgs.cs b/src/ClaudeDo.Worker/Usage/TokenTracker/TokenTrackerArgs.cs new file mode 100644 index 00000000..612b730a --- /dev/null +++ b/src/ClaudeDo.Worker/Usage/TokenTracker/TokenTrackerArgs.cs @@ -0,0 +1,31 @@ +using System.Globalization; + +namespace ClaudeDo.Worker.Usage.TokenTracker; + +/// +/// The exact CLI invocations we rely on. --no-git matters: without it TokenTracker runs +/// git log inside every session's working directory, which is both slower and touches +/// repositories we have no business touching. We never invoke init — that would write +/// hooks into the user's global ~/.claude/settings.json and switch on cloud sync. +/// The date range is passed for correctness, not economy: v0.88.4 accepts it but returns the +/// full history regardless, so does the real filtering. +/// +public static class TokenTrackerArgs +{ + public const string Command = "tokentracker"; + public const string NpmCommand = "npm"; + public const string PackageName = "tokentracker-cli"; + + public static string[] Export(DateOnly from, DateOnly to) => + [ + "sessions", + "--from", from.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), + "--to", to.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), + "--no-git", + "--format", "json", + ]; + + public static string[] Version() => ["-v"]; + + public static string[] Install() => ["i", "-g", PackageName]; +} diff --git a/tests/ClaudeDo.Worker.Tests/Usage/TokenTracker/TokenTrackerArgsTests.cs b/tests/ClaudeDo.Worker.Tests/Usage/TokenTracker/TokenTrackerArgsTests.cs new file mode 100644 index 00000000..613ee6d7 --- /dev/null +++ b/tests/ClaudeDo.Worker.Tests/Usage/TokenTracker/TokenTrackerArgsTests.cs @@ -0,0 +1,42 @@ +using ClaudeDo.Worker.Usage.TokenTracker; + +namespace ClaudeDo.Worker.Tests.Usage.TokenTracker; + +public sealed class TokenTrackerArgsTests +{ + [Fact] + public void Export_UsesIsoDatesAndDisablesGitAttribution() + { + var args = TokenTrackerArgs.Export(new DateOnly(2026, 5, 26), new DateOnly(2026, 8, 24)); + + Assert.Equal( + new[] { "sessions", "--from", "2026-05-26", "--to", "2026-08-24", "--no-git", "--format", "json" }, + args); + } + + [Fact] + public void Export_IsCultureInvariant() + { + var previous = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE"); + try + { + var args = TokenTrackerArgs.Export(new DateOnly(2026, 1, 2), new DateOnly(2026, 1, 3)); + Assert.Equal("2026-01-02", args[2]); + Assert.Equal("2026-01-03", args[4]); + } + finally { Thread.CurrentThread.CurrentCulture = previous; } + } + + [Fact] + public void Version_AsksTheCliForItsVersion() + { + Assert.Equal(new[] { "-v" }, TokenTrackerArgs.Version()); + } + + [Fact] + public void Install_InstallsTheCliPackageGlobally() + { + Assert.Equal(new[] { "i", "-g", "tokentracker-cli" }, TokenTrackerArgs.Install()); + } +}