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.
- package/README.md +20 -0
- package/dist/{bootstrap-ink-WA3ENW2M.js → bootstrap-ink-HL2BYNW3.js} +14 -279
- package/dist/chunk-42H7R376.js +298 -0
- package/dist/chunk-45ZP755H.js +275 -0
- package/dist/{chunk-IELQ5EY3.js → chunk-O27I2XFN.js} +277 -539
- package/dist/{chunk-MJWFCDBB.js → chunk-REO4O5VE.js} +7 -5
- package/dist/{daemon-handle-Y2N2NA6D.js → chunk-W2WGBXZG.js} +1 -1
- package/dist/cli-command-TLGIGF63.js +526 -0
- package/dist/cli.js +29 -4
- package/dist/{daemon-PVYTXJMS.js → daemon-F4EGY77J.js} +5 -4
- package/dist/daemon-handle-NLCLYWFU.js +10 -0
- package/dist/{server-XUCQWX6N.js → server-X63FGHIZ.js} +3 -2
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
21
|
+
} from "./chunk-O27I2XFN.js";
|
|
21
22
|
import {
|
|
22
23
|
loadConfig
|
|
23
24
|
} from "./chunk-5CWOJMAH.js";
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startWebServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-REO4O5VE.js";
|
|
5
5
|
import "./chunk-SMPY52EV.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-42H7R376.js";
|
|
7
|
+
import "./chunk-O27I2XFN.js";
|
|
7
8
|
import "./chunk-5CWOJMAH.js";
|
|
8
9
|
export {
|
|
9
10
|
startWebServer
|