tokmon 0.8.2 → 0.9.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +43 -15
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -96,6 +96,7 @@ async function parseFile(path, since) {
96
96
  const u = obj.message.usage;
97
97
  entries.push({
98
98
  ts,
99
+ msgId: obj.message.id ?? "",
99
100
  model: obj.message.model ?? "unknown",
100
101
  cost: costOf(obj.message.model ?? "", u),
101
102
  input: u.input_tokens ?? 0,
@@ -138,7 +139,14 @@ async function loadEntries(since) {
138
139
  }
139
140
  }));
140
141
  }
141
- return chunks.flat();
142
+ const all = chunks.flat();
143
+ const seenIds = /* @__PURE__ */ new Set();
144
+ return all.filter((e) => {
145
+ if (!e.msgId) return true;
146
+ if (seenIds.has(e.msgId)) return false;
147
+ seenIds.add(e.msgId);
148
+ return true;
149
+ });
142
150
  }
143
151
  function sum(entries) {
144
152
  let cost = 0, tokens2 = 0;
@@ -250,29 +258,49 @@ async function fetchTable() {
250
258
 
251
259
  // src/billing.ts
252
260
  import { execFile as execFileCb } from "child_process";
261
+ import { readFile as readFile2 } from "fs/promises";
262
+ import { join as join3 } from "path";
263
+ import { homedir as homedir3 } from "os";
253
264
  import { promisify } from "util";
254
265
  var execFile = promisify(execFileCb);
266
+ function credentialsFilePath() {
267
+ const base = process.env.CLAUDE_CONFIG_DIR ?? join3(homedir3(), ".claude");
268
+ return join3(base, ".credentials.json");
269
+ }
270
+ async function readCredentialsFile() {
271
+ try {
272
+ const raw = await readFile2(credentialsFilePath(), "utf-8");
273
+ const creds = JSON.parse(raw);
274
+ return creds?.claudeAiOauth?.accessToken ?? creds?.accessToken ?? null;
275
+ } catch {
276
+ return null;
277
+ }
278
+ }
279
+ async function readMacKeychain() {
280
+ try {
281
+ const { stdout } = await execFile("security", [
282
+ "find-generic-password",
283
+ "-s",
284
+ "Claude Code-credentials",
285
+ "-w"
286
+ ], { timeout: 5e3 });
287
+ const creds = JSON.parse(stdout.trim());
288
+ return creds?.claudeAiOauth?.accessToken ?? creds?.accessToken ?? null;
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
255
293
  async function getAccessToken() {
256
294
  if (process.platform === "darwin") {
257
- try {
258
- const { stdout } = await execFile("security", [
259
- "find-generic-password",
260
- "-s",
261
- "Claude Code-credentials",
262
- "-w"
263
- ], { timeout: 5e3 });
264
- const creds = JSON.parse(stdout.trim());
265
- return creds?.claudeAiOauth?.accessToken ?? null;
266
- } catch {
267
- return null;
268
- }
295
+ const token = await readMacKeychain();
296
+ if (token) return token;
269
297
  }
270
- return null;
298
+ return readCredentialsFile();
271
299
  }
272
300
  var EMPTY = { session: null, weekly: null, sonnet: null, extraUsage: null, error: null };
273
301
  async function fetchBilling() {
274
302
  const token = await getAccessToken();
275
- if (!token) return { ...EMPTY, error: "No OAuth token found (macOS Keychain)" };
303
+ if (!token) return { ...EMPTY, error: "No OAuth token \u2014 run claude and log in" };
276
304
  try {
277
305
  const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
278
306
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "description": "Terminal dashboard for Claude Code usage and costs",
5
5
  "type": "module",
6
6
  "bin": {