letmecode 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Devforth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,32 +1,51 @@
1
- # letmecode - Discover your detailed agent ussage Codex | Claude
1
+ # LetMeCode
2
2
 
3
- Ussage:
3
+ Terminal AI real-money value usage dashboard for Codex, Claude, Copilot, and Antigravity.
4
+
5
+ See your real $ usage data, inspect limits, see daily activity, and model-level token totals in a terminal UI.
6
+
7
+ ## Quick start
4
8
 
5
9
  ```bash
6
10
  npx -y letmecode@latest
7
11
  ```
8
12
 
13
+ ## Privacy and anonymous reporting
9
14
 
10
- Preview:
11
-
12
- <img width="2301" height="1397" alt="image" src="https://github.com/user-attachments/assets/c37d6847-9926-4977-8592-5cab8346a86f" />
15
+ By default, `letmecode` sends an anonymous usage summary which powers [aggregated real-world plans comparison](https://devforth.io/agents-for-code/).
13
16
 
17
+ The report includes aggregated limit-window percentages, token counts, plan/window metadata, the `letmecode` version, and a hashed user identifier when a provider exposes one. It does not send prompt content, usernames, email addresses, company names, or other directly identifiable personal information.
14
18
 
19
+ To disable anonymous usage reporting:
15
20
 
21
+ ```bash
22
+ npx -y letmecode@latest -- --no-usage
23
+ ```
16
24
 
17
- ## Advanced ussage
25
+ ## Options
18
26
 
27
+ ```bash
28
+ npx -y letmecode@latest -- --help
29
+ npx -y letmecode@latest -- --log-to ./letmecode.log
19
30
  ```
20
- npx -y letmecode -- -h
21
- npx -y letmecode -- --log-to log.txt
22
- ```
23
31
 
24
- `--log-to` now records Claude binary discovery, session-root selection, parsed transcript file summaries, entrypoint matching, raw `/usage` output, and live-window event matching so zero-token windows are diagnosable.
32
+ `--log-to` records provider discovery details and raw usage parsing diagnostics so empty or unexpected windows are easier to debug.
33
+
34
+ ## Providers
35
+
36
+ - Codex
37
+ - Claude
38
+ - Copilot
39
+ - Antigravity
40
+
41
+ ## Preview
25
42
 
43
+ <img width="2301" height="1397" alt="letmecode preview" src="https://github.com/user-attachments/assets/c37d6847-9926-4977-8592-5cab8346a86f" />
26
44
 
27
45
  ## Local development
28
46
 
29
47
  ```bash
30
48
  pnpm install
49
+ pnpm test
31
50
  pnpm start
32
51
  ```
@@ -4,6 +4,7 @@ export function parseCliOptions(argv) {
4
4
  let showHelp = false;
5
5
  let verbose = false;
6
6
  let logToPath;
7
+ let enableAnonymousUsageReporting = true;
7
8
  for (let index = 0; index < argv.length; index += 1) {
8
9
  const argument = argv[index] ?? "";
9
10
  if (argument === "-h" || argument === "--help") {
@@ -29,9 +30,13 @@ export function parseCliOptions(argv) {
29
30
  throw new Error("Expected a file path after --log-to=.");
30
31
  }
31
32
  logToPath = value;
33
+ continue;
34
+ }
35
+ if (argument === "--no-usage") {
36
+ enableAnonymousUsageReporting = false;
32
37
  }
33
38
  }
34
- return { showHelp, verbose, logToPath };
39
+ return { showHelp, verbose, logToPath, enableAnonymousUsageReporting };
35
40
  }
36
41
  export function buildProviderStatsOptions(options) {
37
42
  return {
@@ -41,7 +46,7 @@ export function buildProviderStatsOptions(options) {
41
46
  }
42
47
  export function buildHelpText() {
43
48
  return [
44
- "letmecode - provider-based terminal usage dashboard",
49
+ "letmecode - terminal AI usage dashboard",
45
50
  "",
46
51
  "Usage:",
47
52
  " letmecode [options]",
@@ -50,6 +55,7 @@ export function buildHelpText() {
50
55
  " -h, --help Show this help and exit",
51
56
  " -v, --verbose Show extra provider warnings",
52
57
  " --log-to PATH Write trace logs to PATH",
58
+ " --no-usage Disable anonymous usage reporting",
53
59
  "",
54
60
  "Controls:",
55
61
  " [ ] / Tab Switch providers",
@@ -64,7 +70,11 @@ export function buildHelpText() {
64
70
  " --log-to PATH writes Claude detection details,",
65
71
  " session root selection, parsed session file summaries, aggregated usage selection,",
66
72
  " every candidate binary path check, the final found/not-found result,",
67
- " and the raw /usage command output plus live window matching details."
73
+ " and the raw /usage command output plus live window matching details.",
74
+ "",
75
+ "Anonymous reporting:",
76
+ " Enabled by default. Use --no-usage to disable the best-effort",
77
+ " anonymous usage summary upload."
68
78
  ].join("\n");
69
79
  }
70
80
  export function createFileTraceLogger(logPath) {
@@ -96,7 +96,9 @@ function App(props) {
96
96
  setSelectedProviderId(topProvider.provider.id);
97
97
  }, [hasUserSelectedProvider, providerStates, sortedProviderStates]);
98
98
  useEffect(() => {
99
- if (hasReportedAnonymousUsageRef.current || providerStates.some((state) => state.status === "loading")) {
99
+ if (!props.usageReportingEnabled ||
100
+ hasReportedAnonymousUsageRef.current ||
101
+ providerStates.some((state) => state.status === "loading")) {
100
102
  return;
101
103
  }
102
104
  hasReportedAnonymousUsageRef.current = true;
@@ -106,7 +108,7 @@ function App(props) {
106
108
  void reportAnonymousUsage(readyStats).catch(() => {
107
109
  // Anonymous usage reporting is best-effort and must never disturb the TUI.
108
110
  });
109
- }, [providerStates]);
111
+ }, [props.usageReportingEnabled, providerStates]);
110
112
  useMouseClick((click) => {
111
113
  const regionId = resolveClick(click);
112
114
  if (!regionId) {
@@ -1002,7 +1004,7 @@ export function main(argv = process.argv.slice(2)) {
1002
1004
  restoreFullscreen();
1003
1005
  };
1004
1006
  process.once("exit", exitHandler);
1005
- const instance = render(_jsx(App, { statsOptions: statsOptions }), {
1007
+ const instance = render(_jsx(App, { statsOptions: statsOptions, usageReportingEnabled: cliOptions.enableAnonymousUsageReporting }), {
1006
1008
  stdout: process.stdout,
1007
1009
  stdin: process.stdin,
1008
1010
  stderr: process.stderr
@@ -10,12 +10,26 @@ import { applyRateLimits, asRecord, buildWindowLists, createLimitWindowAggregate
10
10
  import { addDailyUsage, buildDailyUsageRows, createDailyUsageAggregates } from "./daily.js";
11
11
  import { resolveUsageRate } from "./pricing.js";
12
12
  const RATE_CARD = {
13
+ "claude-fable-5": { input: 10, cacheRead: 1, cacheWrite: 12.5, cacheWrite5m: 12.5, cacheWrite1h: 20, output: 50 },
13
14
  "claude-opus-4-8": { input: 5, cacheRead: 0.5, cacheWrite: 6.25, cacheWrite5m: 6.25, cacheWrite1h: 10, output: 25 },
14
15
  "claude-opus-4-7": { input: 5, cacheRead: 0.5, cacheWrite: 6.25, cacheWrite5m: 6.25, cacheWrite1h: 10, output: 25 },
15
16
  "claude-opus-4-6": { input: 5, cacheRead: 0.5, cacheWrite: 6.25, cacheWrite5m: 6.25, cacheWrite1h: 10, output: 25 },
16
17
  "claude-opus-4-5": { input: 5, cacheRead: 0.5, cacheWrite: 6.25, cacheWrite5m: 6.25, cacheWrite1h: 10, output: 25 },
17
18
  "claude-opus-4-1": { input: 15, cacheRead: 1.5, cacheWrite: 18.75, cacheWrite5m: 18.75, cacheWrite1h: 30, output: 75 },
18
19
  "claude-opus-4": { input: 15, cacheRead: 1.5, cacheWrite: 18.75, cacheWrite5m: 18.75, cacheWrite1h: 30, output: 75 },
20
+ "claude-sonnet-5": {
21
+ input: 3,
22
+ cacheRead: 0.3,
23
+ cacheWrite: 3.75,
24
+ cacheWrite5m: 3.75,
25
+ cacheWrite1h: 6,
26
+ output: 15,
27
+ // Intro pricing (~0.66x) through 2026-08-31; reverts to the standard rate above on 2026-09-01.
28
+ introOffer: {
29
+ effectiveUntilMs: Date.UTC(2026, 8, 1),
30
+ rate: { input: 2, cacheRead: 0.2, cacheWrite: 2.5, cacheWrite5m: 2.5, cacheWrite1h: 4, output: 10 }
31
+ }
32
+ },
19
33
  "claude-sonnet-4-6": { input: 3, cacheRead: 0.3, cacheWrite: 3.75, cacheWrite5m: 3.75, cacheWrite1h: 6, output: 15 },
20
34
  "claude-sonnet-4-5": { input: 3, cacheRead: 0.3, cacheWrite: 3.75, cacheWrite5m: 3.75, cacheWrite1h: 6, output: 15 },
21
35
  "claude-sonnet-4": { input: 3, cacheRead: 0.3, cacheWrite: 3.75, cacheWrite5m: 3.75, cacheWrite1h: 6, output: 15 },
@@ -46,12 +60,14 @@ const parsedClaudeSessionFilesCache = new Map();
46
60
  const claudeBinaryPathCache = new Map();
47
61
  const claudeUsageOutputCache = new Map();
48
62
  const claudeAuthStatusOutputCache = new Map();
63
+ const claudeOauthCredentialsOutputCache = new Map();
49
64
  export class ClaudeUsageProvider extends UsageProviderBase {
50
65
  constructor(options = {}) {
51
66
  super(options.id ?? "claude", options.label ?? "Claude");
52
67
  this.root = path.resolve(options.root ?? os.homedir());
53
68
  this.readUsageCommandOutput = options.readUsageCommandOutput;
54
69
  this.readAuthStatusOutput = options.readAuthStatusOutput;
70
+ this.readOauthCredentials = options.readOauthCredentials;
55
71
  this.now = options.now ?? (() => new Date());
56
72
  }
57
73
  async getStats(options = {}) {
@@ -147,7 +163,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
147
163
  const liveLimitWindows = await buildLiveLimitWindows({
148
164
  root: this.root,
149
165
  readUsageCommandOutput: this.readUsageCommandOutput,
150
- readAuthStatusOutput: this.readAuthStatusOutput,
166
+ readOauthCredentials: this.readOauthCredentials,
151
167
  traceLogger: options.traceLogger,
152
168
  now: this.now(),
153
169
  selectedEvents
@@ -212,14 +228,14 @@ function normalizeUsage(value) {
212
228
  inferenceGeo: String(usage.inference_geo ?? "")
213
229
  };
214
230
  }
215
- function resolveRate(modelId) {
216
- return resolveUsageRate(RATE_CARD, modelId, 0, { prefixMatch: true });
231
+ function resolveRate(modelId, timestampMs) {
232
+ return resolveUsageRate(RATE_CARD, modelId, 0, { prefixMatch: true, timestampMs });
217
233
  }
218
234
  function isInternalClaudeModel(modelId) {
219
235
  return modelId === "<synthetic>";
220
236
  }
221
- function creditsFor(modelId, usage) {
222
- const rate = resolveRate(modelId);
237
+ function creditsFor(modelId, usage, timestampMs) {
238
+ const rate = resolveRate(modelId, timestampMs);
223
239
  if (!rate) {
224
240
  return 0;
225
241
  }
@@ -233,7 +249,7 @@ function creditsFor(modelId, usage) {
233
249
  inferenceMultiplier *
234
250
  USD_TO_CREDITS);
235
251
  }
236
- function usageToTotals(modelId, usage) {
252
+ function usageToTotals(modelId, usage, timestampMs) {
237
253
  const cacheWriteBreakdown = resolveClaudeCacheWriteBreakdown(usage);
238
254
  const cacheWriteInputTokens = cacheWriteBreakdown.cacheWrite5mInputTokens +
239
255
  cacheWriteBreakdown.cacheWrite1hInputTokens;
@@ -249,7 +265,7 @@ function usageToTotals(modelId, usage) {
249
265
  usage.cacheReadInputTokens +
250
266
  cacheWriteInputTokens +
251
267
  usage.outputTokens,
252
- estimatedCredits: creditsFor(modelId, usage),
268
+ estimatedCredits: creditsFor(modelId, usage, timestampMs),
253
269
  eventCount: 1
254
270
  };
255
271
  }
@@ -437,7 +453,7 @@ async function parseSessionFile(filePath) {
437
453
  timestampMs: eventTimeMs,
438
454
  modelId,
439
455
  usage: normalizedUsage,
440
- totals: usageToTotals(modelId, normalizedUsage),
456
+ totals: usageToTotals(modelId, normalizedUsage, eventTimeMs),
441
457
  rateLimits
442
458
  });
443
459
  }
@@ -546,7 +562,7 @@ function mergeParsedUsageEvents(previous, next) {
546
562
  timestampMs: Math.max(normalizeTimestamp(previous.timestampMs), normalizeTimestamp(next.timestampMs)),
547
563
  modelId,
548
564
  usage,
549
- totals: usageToTotals(modelId, usage),
565
+ totals: usageToTotals(modelId, usage, primaryEvent.timestampMs),
550
566
  rateLimits: latestEvent.rateLimits ?? previous.rateLimits ?? next.rateLimits
551
567
  };
552
568
  }
@@ -648,16 +664,16 @@ function buildClaudeCommandEnvironment() {
648
664
  };
649
665
  }
650
666
  async function buildLiveLimitWindows(options) {
651
- const [usageOutput, subscriptionType] = await Promise.all([
667
+ const [usageOutput, credentials] = await Promise.all([
652
668
  readClaudeUsageCommandOutput(options.root, options.readUsageCommandOutput, options.traceLogger),
653
- readClaudeSubscriptionType(options.root, options.readAuthStatusOutput, options.traceLogger)
669
+ readClaudeOauthCredentials(options.root, options.readOauthCredentials, options.traceLogger)
654
670
  ]);
655
671
  const snapshots = parseLiveUsageWindowSnapshots(usageOutput, options.now);
656
672
  traceClaude(options.traceLogger, `Parsed ${snapshots.length} live usage snapshot(s) from /usage output.`);
657
673
  if (snapshots.length === 0) {
658
674
  traceClaude(options.traceLogger, "No live usage snapshots matched the expected /usage format.");
659
675
  }
660
- const resolvedPlanType = resolveClaudeLivePlanType(subscriptionType, snapshots);
676
+ const resolvedPlanType = resolveClaudeLivePlanType(credentials);
661
677
  traceClaude(options.traceLogger, `Resolved live plan type ${resolvedPlanType}.`);
662
678
  const primaryLimitWindows = snapshots
663
679
  .filter((snapshot) => snapshot.scope === "primary")
@@ -690,20 +706,82 @@ async function buildLiveLimitWindows(options) {
690
706
  secondaryLimitWindows
691
707
  };
692
708
  }
693
- function resolveClaudeLivePlanType(subscriptionType, snapshots) {
694
- if (snapshots.some((snapshot) => snapshot.modelScope === "sonnet-only")) {
695
- return "team_premium";
709
+ // Reported as-is: the claude.ai OAuth `rateLimitTier` is not mapped to a friendly tier name
710
+ // anymore, it is combined with `subscriptionType` verbatim (e.g. "team|default_claude_max_5x").
711
+ function resolveClaudeLivePlanType(credentials) {
712
+ const subscriptionType = credentials?.subscriptionType;
713
+ if (!subscriptionType) {
714
+ return "live";
715
+ }
716
+ return credentials?.rateLimitTier ? `${subscriptionType}|${credentials.rateLimitTier}` : subscriptionType;
717
+ }
718
+ async function readClaudeOauthCredentials(root, override, traceLogger) {
719
+ const output = await readClaudeOauthCredentialsOutput(root, override, traceLogger);
720
+ const credentials = parseClaudeOauthCredentials(output);
721
+ if (output && !credentials) {
722
+ traceClaude(traceLogger, "Could not parse claude.ai OAuth credentials from credentials file.");
723
+ }
724
+ traceClaude(traceLogger, `OAuth credentials result: subscriptionType=${credentials?.subscriptionType ?? "<none>"} rateLimitTier=${credentials?.rateLimitTier ?? "<none>"}.`);
725
+ return credentials;
726
+ }
727
+ async function readClaudeOauthCredentialsOutput(root, override, traceLogger) {
728
+ if (override) {
729
+ try {
730
+ const output = await override();
731
+ traceClaude(traceLogger, "Using injected OAuth credentials override.");
732
+ return output;
733
+ }
734
+ catch {
735
+ traceClaude(traceLogger, "Injected OAuth credentials override failed.");
736
+ return null;
737
+ }
738
+ }
739
+ const credentialsPath = path.join(path.resolve(root), ".claude", ".credentials.json");
740
+ const cacheKey = credentialsPath;
741
+ const cached = claudeOauthCredentialsOutputCache.get(cacheKey);
742
+ if (cached) {
743
+ traceClaude(traceLogger, "OAuth credentials cache hit.");
744
+ return cached;
696
745
  }
697
- return subscriptionType || "live";
746
+ const pending = (async () => {
747
+ try {
748
+ const output = await fs.promises.readFile(credentialsPath, { encoding: "utf8" });
749
+ traceClaude(traceLogger, `Read OAuth credentials from ${credentialsPath}.`);
750
+ return output;
751
+ }
752
+ catch {
753
+ traceClaude(traceLogger, `No readable OAuth credentials at ${credentialsPath}.`);
754
+ return null;
755
+ }
756
+ })();
757
+ claudeOauthCredentialsOutputCache.set(cacheKey, pending);
758
+ return pending;
698
759
  }
699
- async function readClaudeSubscriptionType(root, override, traceLogger) {
700
- const output = await readClaudeAuthStatusOutput(root, override, traceLogger);
701
- const subscriptionType = parseClaudeSubscriptionType(output);
702
- if (output && !subscriptionType) {
703
- traceClaude(traceLogger, "Could not parse subscription type from auth status output.");
704
- }
705
- traceClaude(traceLogger, `Subscription type result: ${subscriptionType ?? "<none>"}.`);
706
- return subscriptionType;
760
+ function parseClaudeOauthCredentials(output) {
761
+ if (!output) {
762
+ return null;
763
+ }
764
+ let payload;
765
+ try {
766
+ payload = JSON.parse(output);
767
+ }
768
+ catch {
769
+ return null;
770
+ }
771
+ const oauth = asRecord(asRecord(payload)?.claudeAiOauth);
772
+ if (!oauth) {
773
+ return null;
774
+ }
775
+ const subscriptionType = typeof oauth.subscriptionType === "string" && oauth.subscriptionType.trim()
776
+ ? oauth.subscriptionType.trim()
777
+ : null;
778
+ const rateLimitTier = typeof oauth.rateLimitTier === "string" && oauth.rateLimitTier.trim()
779
+ ? oauth.rateLimitTier.trim()
780
+ : null;
781
+ if (!subscriptionType && !rateLimitTier) {
782
+ return null;
783
+ }
784
+ return { subscriptionType, rateLimitTier };
707
785
  }
708
786
  async function readClaudeAuthStatusOutput(root, override, traceLogger) {
709
787
  if (override) {
@@ -751,10 +829,6 @@ async function readClaudeAuthStatusOutput(root, override, traceLogger) {
751
829
  claudeAuthStatusOutputCache.set(cacheKey, pending);
752
830
  return pending;
753
831
  }
754
- function parseClaudeSubscriptionType(output) {
755
- const snapshot = parseClaudeAuthStatusSnapshot(output);
756
- return snapshot?.subscriptionType ?? null;
757
- }
758
832
  function parseClaudeAuthStatusSnapshot(output) {
759
833
  if (!output) {
760
834
  return null;
@@ -977,9 +1051,6 @@ function parseLiveUsageWindowSnapshots(usageOutput, now) {
977
1051
  resetString: match[4]?.trim() || null
978
1052
  });
979
1053
  }
980
- // Per-scope reset times printed by Claude. Lines like "Current week (Sonnet
981
- // only)" omit the reset because it is identical to the "all models" week, so
982
- // a missing reset inherits the resolved reset of the same scope.
983
1054
  const resetMsByLabel = new Map();
984
1055
  for (const parsed of parsedLines) {
985
1056
  if (!parsed.resetString || resetMsByLabel.has(parsed.label)) {
@@ -996,14 +1067,17 @@ function parseLiveUsageWindowSnapshots(usageOutput, now) {
996
1067
  if (!resetsAtMs) {
997
1068
  continue;
998
1069
  }
999
- const isSonnetOnlyWeek = parsed.label === "week" && parsed.windowQualifier === "sonnet only";
1000
- const limitId = isSonnetOnlyWeek ? "current-week-sonnet-only" : `current-${parsed.label}`;
1070
+ const isModelScopedWeek = parsed.label === "week" && parsed.windowQualifier !== "" && parsed.windowQualifier !== "all models";
1071
+ const modelFamily = isModelScopedWeek ? parsed.windowQualifier.replace(/\s+only$/, "") : null;
1072
+ const limitId = modelFamily
1073
+ ? `current-week-${modelFamily.replace(/\s+/g, "-")}-only`
1074
+ : `current-${parsed.label}`;
1001
1075
  snapshots.set(limitId, {
1002
1076
  scope: parsed.label === "session" ? "primary" : "secondary",
1003
1077
  label: parsed.label,
1004
1078
  limitId,
1005
- modelScope: isSonnetOnlyWeek ? "sonnet-only" : "all-models",
1006
- modelType: isSonnetOnlyWeek ? "sonnet only" : undefined,
1079
+ modelFamily,
1080
+ modelType: modelFamily ? parsed.windowQualifier : undefined,
1007
1081
  usedPercent: parsed.usedPercent,
1008
1082
  resetsAtMs,
1009
1083
  windowMinutes: parsed.windowMinutes
@@ -1087,10 +1161,10 @@ function buildLiveLimitWindowRow(snapshot, planType, selectedEvents, now) {
1087
1161
  };
1088
1162
  }
1089
1163
  function matchesClaudeLiveSnapshotModelScope(snapshot, modelId) {
1090
- if (snapshot.modelScope !== "sonnet-only") {
1164
+ if (!snapshot.modelFamily) {
1091
1165
  return true;
1092
1166
  }
1093
- return modelId.toLowerCase().includes("sonnet");
1167
+ return modelId.toLowerCase().includes(snapshot.modelFamily);
1094
1168
  }
1095
1169
  function buildModelUsageRowsForEvents(events) {
1096
1170
  const byModel = new Map();
@@ -14,5 +14,10 @@ export function resolveUsageRate(rateCard, modelId, inputTokens = 0, options = {
14
14
  if (rate.longContext && inputTokens > rate.longContext.thresholdTokens) {
15
15
  return rate.longContext.rate;
16
16
  }
17
+ if (rate.introOffer &&
18
+ Number.isFinite(options.timestampMs) &&
19
+ options.timestampMs < rate.introOffer.effectiveUntilMs) {
20
+ return rate.introOffer.rate;
21
+ }
17
22
  return rate;
18
23
  }
@@ -64,7 +64,11 @@ function resolveReportModelType(stats, window) {
64
64
  return truncateSchemaString(stats.providerId, 128);
65
65
  }
66
66
  function resolveClaudeReportModelType(window) {
67
- return window.modelType?.toLowerCase().includes("sonnet") ? "sonnet-only" : "all";
67
+ const modelFamily = window.modelType?.toLowerCase().replace(/\s+only$/, "").trim();
68
+ if (!modelFamily) {
69
+ return "all";
70
+ }
71
+ return truncateSchemaString(`${modelFamily.replace(/\s+/g, "-")}-only`, 128);
68
72
  }
69
73
  function resolveAntigravityReportModelType(stats, window) {
70
74
  const limitId = window.limitId.toLowerCase();
package/package.json CHANGED
@@ -1,14 +1,23 @@
1
1
  {
2
2
  "name": "letmecode",
3
- "version": "0.1.21",
4
- "description": "Provider-based terminal usage dashboard for LetMeCode.",
5
- "author": "devforth.io",
3
+ "version": "0.1.23",
4
+ "description": "Terminal AI usage dashboard for Codex, Claude, Copilot, and Antigravity.",
5
+ "author": "Devforth (https://devforth.io)",
6
6
  "license": "MIT",
7
7
  "packageManager": "pnpm@10.28.2",
8
8
  "type": "commonjs",
9
+ "main": "./dist/index.js",
9
10
  "bin": {
10
11
  "letmecode": "./bin/letmecode.js"
11
12
  },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/devforth/letmecode.git"
16
+ },
17
+ "homepage": "https://github.com/devforth/letmecode#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/devforth/letmecode/issues"
20
+ },
12
21
  "files": [
13
22
  "bin",
14
23
  "dist",
@@ -32,9 +41,18 @@
32
41
  "test": "node --test ink-app/test/*.test.mjs"
33
42
  },
34
43
  "keywords": [
44
+ "ai",
45
+ "agents",
35
46
  "cli",
36
- "ink",
47
+ "dashboard",
48
+ "usage",
49
+ "terminal",
50
+ "codex",
51
+ "claude",
52
+ "copilot",
53
+ "antigravity",
37
54
  "npx",
55
+ "ink",
38
56
  "typescript"
39
57
  ],
40
58
  "dependencies": {