tokmon 0.23.5 → 0.24.0

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.
@@ -6,6 +6,12 @@ import {
6
6
  sendJson,
7
7
  serveStatic
8
8
  } from "./chunk-SMPY52EV.js";
9
+ import {
10
+ buildAccounts,
11
+ colorHex,
12
+ fetchPeak,
13
+ namedHex
14
+ } from "./chunk-42H7R376.js";
9
15
  import {
10
16
  PROVIDERS,
11
17
  TOKMON_CAPABILITIES,
@@ -13,14 +19,10 @@ import {
13
19
  TOKMON_WS_METHODS,
14
20
  TOKMON_WS_PATH,
15
21
  TokmonRpcGroup,
16
- buildAccounts,
17
- colorHex,
18
22
  detectProviders,
19
- fetchPeak,
20
- namedHex,
21
23
  resolveTimezone,
22
24
  withTimeout
23
- } from "./chunk-IELQ5EY3.js";
25
+ } from "./chunk-O27I2XFN.js";
24
26
  import {
25
27
  cacheDir,
26
28
  expandHome,
@@ -9,7 +9,6 @@ import {
9
9
  import {
10
10
  appVersion
11
11
  } from "./chunk-SMPY52EV.js";
12
- import "./chunk-5CWOJMAH.js";
13
12
 
14
13
  // src/client/daemon-handle.ts
15
14
  import { spawn } from "child_process";
@@ -157,6 +156,7 @@ function degraded() {
157
156
  return { kind: "degraded", baseUrl: null, stop: () => {
158
157
  } };
159
158
  }
159
+
160
160
  export {
161
161
  attachOrSpawn
162
162
  };
@@ -0,0 +1,526 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ attachOrSpawn
4
+ } from "./chunk-W2WGBXZG.js";
5
+ import "./chunk-3TJVFKXV.js";
6
+ import "./chunk-SMPY52EV.js";
7
+ import {
8
+ createDaemonRpcClient
9
+ } from "./chunk-45ZP755H.js";
10
+ import {
11
+ PROVIDERS,
12
+ antigravityStateDb,
13
+ claudeConfigDirs,
14
+ codexHomes,
15
+ copilotStateDirs,
16
+ cursorStateDb,
17
+ dayKey,
18
+ geminiCredsPath,
19
+ geminiDir,
20
+ geminiTmpDir,
21
+ ghHostsPath,
22
+ grokAuthPaths,
23
+ grokHomes,
24
+ opencodeDbPaths,
25
+ piSessionsDir,
26
+ startOfMonth,
27
+ startOfWeek,
28
+ withTimeout
29
+ } from "./chunk-O27I2XFN.js";
30
+ import {
31
+ PROVIDER_IDS,
32
+ configLocation
33
+ } from "./chunk-5CWOJMAH.js";
34
+
35
+ // src/provider-locations.ts
36
+ import { access } from "fs/promises";
37
+ import { join } from "path";
38
+ async function location(kind, path) {
39
+ let exists = false;
40
+ try {
41
+ await access(path);
42
+ exists = true;
43
+ } catch {
44
+ }
45
+ return { kind, path, exists };
46
+ }
47
+ function unique(items) {
48
+ const seen = /* @__PURE__ */ new Set();
49
+ return items.filter(([kind, path]) => {
50
+ const key = `${kind}:${path}`;
51
+ if (seen.has(key)) return false;
52
+ seen.add(key);
53
+ return true;
54
+ });
55
+ }
56
+ async function providerLocations(providerId, homeDir) {
57
+ let candidates;
58
+ switch (providerId) {
59
+ case "claude":
60
+ candidates = claudeConfigDirs(homeDir).flatMap((dir) => [
61
+ ["config", dir],
62
+ ["auth", join(dir, ".credentials.json")],
63
+ ["usage", join(dir, "projects")]
64
+ ]);
65
+ break;
66
+ case "codex":
67
+ candidates = codexHomes(homeDir).flatMap((dir) => [
68
+ ["config", dir],
69
+ ["auth", join(dir, "auth.json")],
70
+ ["usage", join(dir, "sessions")],
71
+ ["usage", join(dir, "archived_sessions")]
72
+ ]);
73
+ break;
74
+ case "cursor": {
75
+ const state = cursorStateDb(homeDir);
76
+ candidates = [["state", state], ["usage", state]];
77
+ break;
78
+ }
79
+ case "copilot":
80
+ candidates = [
81
+ ["auth", ghHostsPath(homeDir)],
82
+ ...copilotStateDirs(homeDir).map((path) => ["state", path])
83
+ ];
84
+ break;
85
+ case "pi":
86
+ candidates = [["usage", piSessionsDir(homeDir)]];
87
+ break;
88
+ case "opencode":
89
+ candidates = opencodeDbPaths(homeDir).map((path) => ["usage", path]);
90
+ break;
91
+ case "antigravity":
92
+ candidates = [["state", await antigravityStateDb(homeDir)]];
93
+ break;
94
+ case "gemini":
95
+ candidates = [
96
+ ["config", geminiDir(homeDir)],
97
+ ["auth", geminiCredsPath(homeDir)],
98
+ ["usage", geminiTmpDir(homeDir)]
99
+ ];
100
+ break;
101
+ case "grok":
102
+ candidates = [
103
+ ...grokHomes(homeDir).map((path) => ["config", path]),
104
+ ...grokAuthPaths(homeDir).map((path) => ["auth", path]),
105
+ ...grokHomes(homeDir).map((path) => ["usage", join(path, "logs")])
106
+ ];
107
+ break;
108
+ }
109
+ return Promise.all(unique(candidates).map(([kind, path]) => location(kind, path)));
110
+ }
111
+
112
+ // src/cli-query.ts
113
+ var CLI_SCHEMA_VERSION = 1;
114
+ var USAGE_PERIODS = ["today", "week", "month", "all"];
115
+ function sourceId(account) {
116
+ return `${account.providerId}:${account.id}`;
117
+ }
118
+ function firstDayFor(period, now, tz) {
119
+ if (period === "all") return null;
120
+ if (period === "today") return dayKey(now, tz);
121
+ if (period === "week") return dayKey(startOfWeek(now, tz), tz);
122
+ return dayKey(startOfMonth(now, tz), tz);
123
+ }
124
+ function accountMatches(account, filters) {
125
+ if (filters.provider && account.providerId !== filters.provider) return false;
126
+ if (!filters.account) return true;
127
+ const needle = filters.account.toLowerCase();
128
+ return account.id.toLowerCase() === needle || account.name.toLowerCase().includes(needle) || (account.email?.toLowerCase().includes(needle) ?? false);
129
+ }
130
+ async function cliSource(account) {
131
+ return {
132
+ id: sourceId(account),
133
+ providerId: account.providerId,
134
+ provider: PROVIDERS[account.providerId].name,
135
+ accountId: account.id,
136
+ account: account.email || account.displayName || account.name,
137
+ homeDir: account.homeDir,
138
+ locations: await providerLocations(account.providerId, account.homeDir ?? void 0)
139
+ };
140
+ }
141
+ function addUsage(target, value) {
142
+ target.input += value.input;
143
+ target.output += value.output;
144
+ target.cacheCreate += value.cacheCreate;
145
+ target.cacheRead += value.cacheRead;
146
+ target.tokens += value.tokens;
147
+ target.cacheSavings += value.cacheSavings;
148
+ target.cost += value.cost;
149
+ target.calls += value.calls;
150
+ }
151
+ var ZERO_TOTALS = () => ({
152
+ input: 0,
153
+ output: 0,
154
+ cacheCreate: 0,
155
+ cacheRead: 0,
156
+ tokens: 0,
157
+ cacheSavings: 0,
158
+ cost: 0,
159
+ calls: 0
160
+ });
161
+ async function buildUsageReport(snapshot, filters, now = Date.now(), tokmonConfig = configLocation()) {
162
+ const firstDay = firstDayFor(filters.period, now, snapshot.tz);
163
+ const accounts = snapshot.accounts.filter((account) => accountMatches(account, filters));
164
+ const sources = await Promise.all(accounts.map(cliSource));
165
+ const models = /* @__PURE__ */ new Map();
166
+ const modelNeedle = filters.model?.toLowerCase();
167
+ for (const account of accounts) {
168
+ for (const row of account.table?.daily ?? []) {
169
+ if (firstDay && row.label < firstDay) continue;
170
+ for (const model of row.breakdown) {
171
+ if (modelNeedle && !model.name.toLowerCase().includes(modelNeedle)) continue;
172
+ const key = `${sourceId(account)}\0${model.name}`;
173
+ let current = models.get(key);
174
+ if (!current) {
175
+ current = {
176
+ sourceId: sourceId(account),
177
+ providerId: account.providerId,
178
+ accountId: account.id,
179
+ model: model.name,
180
+ ...ZERO_TOTALS()
181
+ };
182
+ models.set(key, current);
183
+ }
184
+ addUsage(current, {
185
+ input: model.input,
186
+ output: model.output,
187
+ cacheCreate: model.cacheCreate,
188
+ cacheRead: model.cacheRead,
189
+ tokens: model.input + model.output + model.cacheCreate + model.cacheRead,
190
+ cacheSavings: model.cacheSavings,
191
+ cost: model.cost,
192
+ calls: model.count
193
+ });
194
+ }
195
+ }
196
+ }
197
+ const sorted = [...models.values()].sort((a, b) => b.cost - a.cost || b.tokens - a.tokens || a.model.localeCompare(b.model));
198
+ const totals = ZERO_TOTALS();
199
+ for (const model of sorted) addUsage(totals, model);
200
+ return {
201
+ schemaVersion: CLI_SCHEMA_VERSION,
202
+ generatedAt: new Date(snapshot.generatedAt).toISOString(),
203
+ timezone: snapshot.tz,
204
+ tokmonConfig,
205
+ period: filters.period,
206
+ filters: {
207
+ provider: filters.provider ?? null,
208
+ account: filters.account ?? null,
209
+ model: filters.model ?? null
210
+ },
211
+ totals,
212
+ models: sorted,
213
+ sources,
214
+ errors: accounts.filter((account) => account.tableState === "error").map((account) => ({ sourceId: sourceId(account), tableState: account.tableState }))
215
+ };
216
+ }
217
+ async function buildProvidersReport(snapshot, tokmonConfig) {
218
+ const providers = await Promise.all(snapshot.accounts.map(async (account) => ({
219
+ ...await cliSource(account),
220
+ hasUsage: account.hasUsage,
221
+ hasBilling: account.hasBilling,
222
+ plan: account.plan ?? null,
223
+ summaryState: account.summaryState,
224
+ tableState: account.tableState,
225
+ billingState: account.billingState,
226
+ billingError: account.billing?.error ?? null,
227
+ metrics: account.billing?.metrics ?? []
228
+ })));
229
+ return {
230
+ schemaVersion: CLI_SCHEMA_VERSION,
231
+ generatedAt: new Date(snapshot.generatedAt).toISOString(),
232
+ timezone: snapshot.tz,
233
+ tokmonConfig,
234
+ providers
235
+ };
236
+ }
237
+ var compact = (value) => {
238
+ const abs = Math.abs(value);
239
+ if (abs >= 1e9) return `${(value / 1e9).toFixed(2)}B`;
240
+ if (abs >= 1e6) return `${(value / 1e6).toFixed(2)}M`;
241
+ if (abs >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
242
+ return Math.round(value).toString();
243
+ };
244
+ var fit = (value, width) => value.length > width ? `${value.slice(0, Math.max(0, width - 1))}\u2026` : value.padEnd(width);
245
+ function formatUsageReport(report) {
246
+ const sourceMap = new Map(report.sources.map((source) => [source.id, source]));
247
+ const lines = [
248
+ `tokmon usage \xB7 ${report.period} \xB7 ${report.generatedAt} \xB7 ${report.timezone}`,
249
+ `Tokmon config: ${report.tokmonConfig}`,
250
+ `${fit("PROVIDER", 12)} ${fit("ACCOUNT", 22)} ${fit("MODEL", 32)} ${"TOKENS".padStart(10)} ${"CALLS".padStart(7)} ${"COST".padStart(10)}`
251
+ ];
252
+ if (report.models.length === 0) lines.push("No matching model usage.");
253
+ for (const model of report.models) {
254
+ const source = sourceMap.get(model.sourceId);
255
+ lines.push([
256
+ fit(source?.provider ?? model.providerId, 12),
257
+ fit(source?.account ?? model.accountId, 22),
258
+ fit(model.model, 32),
259
+ compact(model.tokens).padStart(10),
260
+ compact(model.calls).padStart(7),
261
+ `$${model.cost.toFixed(2)}`.padStart(10)
262
+ ].join(" "));
263
+ }
264
+ lines.push("");
265
+ lines.push(`Total: ${compact(report.totals.tokens)} tokens \xB7 ${compact(report.totals.calls)} calls \xB7 $${report.totals.cost.toFixed(2)}`);
266
+ lines.push("Sources:");
267
+ for (const source of report.sources) {
268
+ const paths = source.locations.filter((item) => item.exists).map((item) => item.path);
269
+ lines.push(` ${source.id} ${paths.join(", ") || source.homeDir || "(no local path found)"}`);
270
+ }
271
+ if (report.errors.length) lines.push(`Warnings: ${report.errors.map((error) => `${error.sourceId} ${error.tableState}`).join(", ")}`);
272
+ return lines.join("\n");
273
+ }
274
+ function formatProvidersReport(report) {
275
+ const lines = [
276
+ `tokmon providers \xB7 ${report.generatedAt}`,
277
+ `Tokmon config: ${report.tokmonConfig}`
278
+ ];
279
+ for (const provider of report.providers) {
280
+ lines.push("");
281
+ lines.push(`${provider.provider} \xB7 ${provider.account} (${provider.id})`);
282
+ lines.push(` usage=${provider.hasUsage ? provider.tableState : "n/a"} billing=${provider.hasBilling ? provider.billingState : "n/a"} plan=${provider.plan ?? "unknown"}`);
283
+ for (const item of provider.locations) {
284
+ lines.push(` ${item.exists ? "\u2713" : "\xB7"} ${item.kind.padEnd(6)} ${item.path}`);
285
+ }
286
+ if (provider.billingError) lines.push(` warning ${provider.billingError}`);
287
+ }
288
+ return lines.join("\n");
289
+ }
290
+
291
+ // src/cli-command.ts
292
+ var QUERY_HELP = `tokmon usage - Query model usage without opening the TUI
293
+
294
+ Usage:
295
+ tokmon usage [options]
296
+ tokmon models [options] Alias for usage
297
+ tokmon query [options] Alias for usage
298
+
299
+ Options:
300
+ --period <value> today | week | month | all (default: month)
301
+ --provider <id> Filter by provider (${PROVIDER_IDS.join(", ")})
302
+ --account <id-or-name> Filter by account id, name, or email
303
+ --model <substring> Filter model names
304
+ --json Stable machine-readable JSON (schemaVersion: 1)
305
+ --compact Emit compact JSON instead of pretty JSON
306
+ --cached Skip the default local-history refresh
307
+ --refresh Refresh all usage and billing before reading
308
+ --timeout <seconds> Startup/refresh timeout (default: 45)
309
+ -h, --help Show this help
310
+
311
+ Examples:
312
+ tokmon usage
313
+ tokmon usage --period week --provider codex
314
+ tokmon usage --model opus --json
315
+ tokmon usage --period all --json --compact
316
+ `;
317
+ var PROVIDERS_HELP = `tokmon providers - Show detected accounts and their local data/config locations
318
+
319
+ Usage:
320
+ tokmon providers [options]
321
+
322
+ Options:
323
+ --json Stable machine-readable JSON (schemaVersion: 1)
324
+ --compact Emit compact JSON instead of pretty JSON
325
+ --refresh Refresh usage and billing before reading
326
+ --timeout <seconds> Startup/refresh timeout (default: 45)
327
+ -h, --help Show this help
328
+
329
+ Examples:
330
+ tokmon providers
331
+ tokmon providers --json
332
+ `;
333
+ var SNAPSHOT_HELP = `tokmon snapshot - Print the daemon's complete raw snapshot as JSON
334
+
335
+ Usage:
336
+ tokmon snapshot [options]
337
+
338
+ Options:
339
+ --refresh Refresh all usage and billing before reading
340
+ --compact Emit compact JSON instead of pretty JSON
341
+ --timeout <seconds> Startup/refresh timeout (default: 45)
342
+ -h, --help Show this help
343
+ `;
344
+ var CONFIG_HELP = `tokmon config - Print tokmon's configuration file location
345
+
346
+ Usage:
347
+ tokmon config [path]
348
+ tokmon config --json
349
+
350
+ Options:
351
+ --json Emit { "path": "..." }
352
+ -h, --help Show this help
353
+ `;
354
+ function valueAfter(args, index, name) {
355
+ const value = args[index + 1];
356
+ if (!value || value.startsWith("-")) throw new Error(`${name} requires a value`);
357
+ return [value, index + 1];
358
+ }
359
+ function parseQueryArgs(args) {
360
+ const parsed = {
361
+ help: false,
362
+ json: false,
363
+ compact: false,
364
+ refresh: false,
365
+ cached: false,
366
+ timeoutMs: 45e3,
367
+ period: "month",
368
+ positionals: []
369
+ };
370
+ for (let index = 0; index < args.length; index++) {
371
+ const arg = args[index];
372
+ if (arg === "--help" || arg === "-h") parsed.help = true;
373
+ else if (arg === "--json") parsed.json = true;
374
+ else if (arg === "--compact") parsed.compact = true;
375
+ else if (arg === "--refresh") parsed.refresh = true;
376
+ else if (arg === "--cached" || arg === "--no-refresh") parsed.cached = true;
377
+ else if (arg === "--period") {
378
+ const [value, next] = valueAfter(args, index, "--period");
379
+ index = next;
380
+ if (!USAGE_PERIODS.includes(value)) {
381
+ throw new Error(`--period must be one of: ${USAGE_PERIODS.join(", ")}`);
382
+ }
383
+ parsed.period = value;
384
+ } else if (arg.startsWith("--period=")) {
385
+ const value = arg.slice("--period=".length);
386
+ if (!USAGE_PERIODS.includes(value)) {
387
+ throw new Error(`--period must be one of: ${USAGE_PERIODS.join(", ")}`);
388
+ }
389
+ parsed.period = value;
390
+ } else if (arg === "--provider") {
391
+ const [value, next] = valueAfter(args, index, "--provider");
392
+ index = next;
393
+ if (!PROVIDER_IDS.includes(value)) throw new Error(`unknown provider: ${value}`);
394
+ parsed.provider = value;
395
+ } else if (arg.startsWith("--provider=")) {
396
+ const value = arg.slice("--provider=".length);
397
+ if (!PROVIDER_IDS.includes(value)) throw new Error(`unknown provider: ${value}`);
398
+ parsed.provider = value;
399
+ } else if (arg === "--account") {
400
+ [parsed.account, index] = valueAfter(args, index, "--account");
401
+ } else if (arg.startsWith("--account=")) {
402
+ parsed.account = arg.slice("--account=".length);
403
+ if (!parsed.account) throw new Error("--account requires a value");
404
+ } else if (arg === "--model") {
405
+ [parsed.model, index] = valueAfter(args, index, "--model");
406
+ } else if (arg.startsWith("--model=")) {
407
+ parsed.model = arg.slice("--model=".length);
408
+ if (!parsed.model) throw new Error("--model requires a value");
409
+ } else if (arg === "--timeout") {
410
+ const [value, next] = valueAfter(args, index, "--timeout");
411
+ index = next;
412
+ const seconds = Number(value);
413
+ if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 300) throw new Error("--timeout must be greater than 0 and at most 300 seconds");
414
+ parsed.timeoutMs = seconds * 1e3;
415
+ } else if (arg.startsWith("--timeout=")) {
416
+ const seconds = Number(arg.slice("--timeout=".length));
417
+ if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 300) throw new Error("--timeout must be greater than 0 and at most 300 seconds");
418
+ parsed.timeoutMs = seconds * 1e3;
419
+ } else if (arg.startsWith("-")) throw new Error(`unknown option: ${arg}`);
420
+ else parsed.positionals.push(arg);
421
+ }
422
+ if (parsed.compact) parsed.json = true;
423
+ if (parsed.refresh && parsed.cached) throw new Error("--refresh and --cached cannot be used together");
424
+ return parsed;
425
+ }
426
+ function isSnapshot(value) {
427
+ const snapshot = value;
428
+ return !!snapshot && typeof snapshot.generatedAt === "number" && typeof snapshot.tz === "string" && Array.isArray(snapshot.accounts) && Array.isArray(snapshot.providers);
429
+ }
430
+ var delay = (ms) => new Promise((resolve) => {
431
+ setTimeout(resolve, ms);
432
+ });
433
+ async function fetchDaemonSnapshot(timeoutMs, refresh) {
434
+ const handle = await attachOrSpawn({ timeoutMs });
435
+ if (handle.kind !== "spawned" || !handle.baseUrl) throw new Error("tokmon daemon is unavailable");
436
+ const deadline = Date.now() + timeoutMs;
437
+ if (refresh) {
438
+ const client = createDaemonRpcClient(handle.baseUrl, {
439
+ transport: "node",
440
+ reconnectAttempts: 2,
441
+ reconnectBaseDelayMs: 100
442
+ });
443
+ try {
444
+ await withTimeout(client.refresh(refresh), timeoutMs).catch(() => {
445
+ });
446
+ } finally {
447
+ await client.close().catch(() => {
448
+ });
449
+ }
450
+ }
451
+ while (Date.now() < deadline) {
452
+ try {
453
+ const remaining = Math.max(1, deadline - Date.now());
454
+ const response = await fetch(`${handle.baseUrl}/api/data`, {
455
+ cache: "no-store",
456
+ signal: AbortSignal.timeout(Math.min(2e3, remaining))
457
+ });
458
+ if (response.ok) {
459
+ const body = await response.json();
460
+ if (isSnapshot(body)) return body;
461
+ }
462
+ } catch {
463
+ }
464
+ await delay(100);
465
+ }
466
+ throw new Error(`snapshot unavailable after ${timeoutMs / 1e3}s`);
467
+ }
468
+ function json(value, compact2) {
469
+ return JSON.stringify(value, null, compact2 ? void 0 : 2) + "\n";
470
+ }
471
+ function rejectsOption(args, names) {
472
+ for (const arg of args) {
473
+ const name = names.find((candidate) => arg === candidate || arg.startsWith(`${candidate}=`));
474
+ if (name) return name;
475
+ }
476
+ return null;
477
+ }
478
+ function queryHelp(command) {
479
+ if (command === "providers") return PROVIDERS_HELP;
480
+ if (command === "snapshot") return SNAPSHOT_HELP;
481
+ if (command === "config") return CONFIG_HELP;
482
+ return QUERY_HELP;
483
+ }
484
+ async function runQueryCommand(command, args, dependencies = {}) {
485
+ const parsed = parseQueryArgs(args);
486
+ if (parsed.help) return queryHelp(command);
487
+ const getConfigPath = dependencies.configPath ?? configLocation;
488
+ if (command === "config") {
489
+ const invalid = rejectsOption(args, ["--period", "--provider", "--account", "--model", "--refresh", "--cached", "--no-refresh", "--timeout"]);
490
+ if (invalid) throw new Error(`${invalid} is not valid for tokmon config`);
491
+ if (parsed.positionals.length > 1 || parsed.positionals[0] && parsed.positionals[0] !== "path") {
492
+ throw new Error("usage: tokmon config [path] [--json]");
493
+ }
494
+ return parsed.json ? json({ path: getConfigPath() }, parsed.compact) : `${getConfigPath()}
495
+ `;
496
+ }
497
+ if (command === "providers" || command === "snapshot") {
498
+ const invalid = rejectsOption(args, ["--period", "--provider", "--account", "--model", "--cached", "--no-refresh"]);
499
+ if (invalid) throw new Error(`${invalid} is only valid for tokmon usage`);
500
+ }
501
+ if (parsed.positionals.length) throw new Error(`unexpected argument: ${parsed.positionals[0]}`);
502
+ const usageCommand = command === "usage" || command === "models" || command === "query";
503
+ const refresh = parsed.refresh ? "all" : usageCommand && !parsed.cached ? "table" : null;
504
+ const snapshot = await (dependencies.fetchSnapshot ?? fetchDaemonSnapshot)(parsed.timeoutMs, refresh);
505
+ if (command === "snapshot") return json(snapshot, parsed.compact);
506
+ if (command === "providers") {
507
+ const report2 = await buildProvidersReport(snapshot, getConfigPath());
508
+ return parsed.json ? json(report2, parsed.compact) : `${formatProvidersReport(report2)}
509
+ `;
510
+ }
511
+ const filters = {
512
+ period: parsed.period,
513
+ provider: parsed.provider,
514
+ account: parsed.account,
515
+ model: parsed.model
516
+ };
517
+ const report = await buildUsageReport(snapshot, filters, Date.now(), getConfigPath());
518
+ return parsed.json ? json(report, parsed.compact) : `${formatUsageReport(report)}
519
+ `;
520
+ }
521
+ export {
522
+ fetchDaemonSnapshot,
523
+ parseQueryArgs,
524
+ queryHelp,
525
+ runQueryCommand
526
+ };
package/dist/cli.js CHANGED
@@ -40,15 +40,34 @@ function validateServeArgs(serveArgs) {
40
40
  }
41
41
  }
42
42
  async function main() {
43
+ if (subcommand && ["usage", "models", "query", "providers", "snapshot", "config"].includes(subcommand)) {
44
+ const { runQueryCommand } = await import("./cli-command-TLGIGF63.js");
45
+ try {
46
+ const output = await runQueryCommand(
47
+ subcommand,
48
+ args.slice(1)
49
+ );
50
+ process.stdout.write(output);
51
+ process.exitCode ??= 0;
52
+ } catch (error) {
53
+ const message = describeError(error);
54
+ if (args.includes("--json")) process.stderr.write(JSON.stringify({ error: message }) + "\n");
55
+ else process.stderr.write(`tokmon ${subcommand}: ${message}
56
+ Run tokmon ${subcommand} --help for usage.
57
+ `);
58
+ process.exitCode = 1;
59
+ }
60
+ return;
61
+ }
43
62
  if (subcommand === "__daemon") {
44
- const { runDaemon } = await import("./daemon-PVYTXJMS.js");
63
+ const { runDaemon } = await import("./daemon-F4EGY77J.js");
45
64
  await runDaemon(args.slice(1), { foreground: false });
46
65
  process.exitCode ??= 0;
47
66
  return;
48
67
  }
49
68
  if (subcommand === "serve" || subcommand === "web") {
50
69
  validateServeArgs(args.slice(1));
51
- const { runDaemon } = await import("./daemon-PVYTXJMS.js");
70
+ const { runDaemon } = await import("./daemon-F4EGY77J.js");
52
71
  await runDaemon(args.slice(1), { foreground: true });
53
72
  process.exitCode ??= 0;
54
73
  return;
@@ -71,6 +90,12 @@ async function main() {
71
90
  console.log(" Claude \xB7 Codex \xB7 Cursor \xB7 Copilot \xB7 opencode \xB7 pi \xB7 Antigravity \xB7 Gemini \xB7 Grok\n");
72
91
  console.log("Usage: tokmon [options]");
73
92
  console.log(" tokmon serve [--port <n>] [--no-open] Launch the web dashboard\n");
93
+ console.log("Data commands:");
94
+ console.log(" tokmon usage [--period month] [--json] Query usage by model");
95
+ console.log(" tokmon providers [--json] Show providers and local paths");
96
+ console.log(" tokmon snapshot [--refresh] Print the raw daemon snapshot");
97
+ console.log(" tokmon config [path] Print the config file location");
98
+ console.log(" Run tokmon <command> --help for all filters and examples.\n");
74
99
  console.log("Options:");
75
100
  console.log(" -i, --interval <seconds> Refresh interval (default: from config or 2)");
76
101
  console.log(" --ascii Force ASCII glyphs (also: TOKMON_ASCII=1)");
@@ -92,7 +117,7 @@ async function main() {
92
117
  }
93
118
  const { loadConfig } = await import("./config-QC5QSP3G.js");
94
119
  const { resolveGlyphs, setGlyphs } = await import("./glyphs-NKCSZLGO.js");
95
- const { attachOrSpawn } = await import("./daemon-handle-Y2N2NA6D.js");
120
+ const { attachOrSpawn } = await import("./daemon-handle-NLCLYWFU.js");
96
121
  const config = await loadConfig();
97
122
  setGlyphs(resolveGlyphs({
98
123
  flag: asciiFlag,
@@ -103,7 +128,7 @@ async function main() {
103
128
  }));
104
129
  const daemon = await attachOrSpawn();
105
130
  const mode = daemon.kind === "spawned" ? "connected" : "degraded";
106
- const { bootstrapInk } = await import("./bootstrap-ink-WA3ENW2M.js");
131
+ const { bootstrapInk } = await import("./bootstrap-ink-HL2BYNW3.js");
107
132
  await bootstrapInk({ interval, config, daemon, mode });
108
133
  }
109
134
  void main().catch((error) => {
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- startWebServer
4
- } from "./chunk-MJWFCDBB.js";
5
2
  import {
6
3
  acquireLock,
7
4
  isAlive,
@@ -12,12 +9,16 @@ import {
12
9
  verifyLock,
13
10
  writeLock
14
11
  } from "./chunk-3TJVFKXV.js";
12
+ import {
13
+ startWebServer
14
+ } from "./chunk-REO4O5VE.js";
15
15
  import {
16
16
  appVersion
17
17
  } from "./chunk-SMPY52EV.js";
18
+ import "./chunk-42H7R376.js";
18
19
  import {
19
20
  flushDisk
20
- } from "./chunk-IELQ5EY3.js";
21
+ } from "./chunk-O27I2XFN.js";
21
22
  import {
22
23
  loadConfig
23
24
  } from "./chunk-5CWOJMAH.js";
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ attachOrSpawn
4
+ } from "./chunk-W2WGBXZG.js";
5
+ import "./chunk-3TJVFKXV.js";
6
+ import "./chunk-SMPY52EV.js";
7
+ import "./chunk-5CWOJMAH.js";
8
+ export {
9
+ attachOrSpawn
10
+ };
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startWebServer
4
- } from "./chunk-MJWFCDBB.js";
4
+ } from "./chunk-REO4O5VE.js";
5
5
  import "./chunk-SMPY52EV.js";
6
- import "./chunk-IELQ5EY3.js";
6
+ import "./chunk-42H7R376.js";
7
+ import "./chunk-O27I2XFN.js";
7
8
  import "./chunk-5CWOJMAH.js";
8
9
  export {
9
10
  startWebServer