tokentracker-cli 0.10.2 → 0.11.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.
- package/dashboard/dist/assets/main-CeKzyCR5.css +1 -0
- package/dashboard/dist/assets/main-Dof-uaUN.js +5224 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/lib/categorizer-utils.js +232 -0
- package/src/lib/claude-categorizer.js +541 -84
- package/src/lib/codex-context-breakdown.js +400 -0
- package/src/lib/codex-rollout-parser.js +623 -0
- package/src/lib/local-api.js +137 -14
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/dashboard/dist/assets/main-Bst6S3yM.css +0 -1
- package/dashboard/dist/assets/main-Cw4csGy9.js +0 -5182
|
@@ -210,8 +210,8 @@
|
|
|
210
210
|
]
|
|
211
211
|
}
|
|
212
212
|
</script>
|
|
213
|
-
<script type="module" crossorigin src="/assets/main-
|
|
214
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
213
|
+
<script type="module" crossorigin src="/assets/main-Dof-uaUN.js"></script>
|
|
214
|
+
<link rel="stylesheet" crossorigin href="/assets/main-CeKzyCR5.css">
|
|
215
215
|
</head>
|
|
216
216
|
<body>
|
|
217
217
|
<main class="aeo-seed-content" aria-label="Token Tracker AI-readable summary">
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
52
52
|
}
|
|
53
53
|
</script>
|
|
54
|
-
<script type="module" crossorigin src="/assets/main-
|
|
55
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
54
|
+
<script type="module" crossorigin src="/assets/main-Dof-uaUN.js"></script>
|
|
55
|
+
<link rel="stylesheet" crossorigin href="/assets/main-CeKzyCR5.css">
|
|
56
56
|
</head>
|
|
57
57
|
<body>
|
|
58
58
|
<main class="aeo-seed-content" aria-label="Token Tracker share page summary">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Every Code, Hermes, GitHub Copilot, Kimi Code, CodeBuddy, oh-my-pi, pi, Craft Agents)",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Shared helpers for claude-categorizer.js and codex-context-breakdown.js.
|
|
2
|
+
// Extracted to eliminate copy-paste between the two files.
|
|
3
|
+
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Tool categorizer
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
function categorizeTool(name) {
|
|
13
|
+
if (name === "text_response") return "Text Response";
|
|
14
|
+
if (name === "Malformed") return "Malformed";
|
|
15
|
+
|
|
16
|
+
if (name.startsWith("mcp__")) {
|
|
17
|
+
const parts = name.split("__");
|
|
18
|
+
if (parts.length >= 3) {
|
|
19
|
+
const serverRaw = parts[1];
|
|
20
|
+
let server;
|
|
21
|
+
const pluginMatch = serverRaw.match(/^plugin_(.+)$/);
|
|
22
|
+
if (pluginMatch) {
|
|
23
|
+
const inner = pluginMatch[1];
|
|
24
|
+
const segments = inner.split("_");
|
|
25
|
+
const half = Math.floor(segments.length / 2);
|
|
26
|
+
const firstHalf = segments.slice(0, half).join("_");
|
|
27
|
+
const secondHalf = segments.slice(half).join("_");
|
|
28
|
+
if (firstHalf && firstHalf === secondHalf) {
|
|
29
|
+
server = firstHalf;
|
|
30
|
+
} else {
|
|
31
|
+
server = inner;
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
server = serverRaw;
|
|
35
|
+
}
|
|
36
|
+
server = server.replace(/_/g, "-");
|
|
37
|
+
return `MCP: ${server}`;
|
|
38
|
+
}
|
|
39
|
+
return "MCP: Unknown";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (/^Task(Create|Update|Get|List|Output|Stop)$/.test(name)) return "Task Mgmt";
|
|
43
|
+
if (/^Todo/.test(name)) return "Task Mgmt";
|
|
44
|
+
if (/Plan/.test(name)) return "Planning";
|
|
45
|
+
if (name === "Agent") return "Agent";
|
|
46
|
+
if (/^Web(Fetch|Search)$/.test(name)) return "Web";
|
|
47
|
+
if (name === "Skill") return "Skill";
|
|
48
|
+
if (name === "LSP") return "IDE";
|
|
49
|
+
if (name === "AskUserQuestion") return "Interaction";
|
|
50
|
+
|
|
51
|
+
if (name === "exec_command" || name === "write_stdin") return "Execution";
|
|
52
|
+
if (name === "update_plan") return "Planning";
|
|
53
|
+
if (/_agent$/.test(name)) return "Agent";
|
|
54
|
+
if (/^list_mcp/.test(name)) return "MCP Mgmt";
|
|
55
|
+
if (
|
|
56
|
+
/^(navigate_page|click|select_page|new_page|take_snapshot|take_screenshot|evaluate_script|list_pages|list_console_messages|view_image|emulate|resize_page|wait_for|close_page|get_console_message|get_network_request|list_network_requests|performance_)/.test(
|
|
57
|
+
name,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
return "Browser";
|
|
61
|
+
|
|
62
|
+
// Claude Code built-in tools (also harmless to match from Codex side).
|
|
63
|
+
if (/^(Read|Write|Edit|Glob)$/.test(name)) return "File Ops";
|
|
64
|
+
if (name === "Grep") return "Search";
|
|
65
|
+
if (name === "Bash") return "Execution";
|
|
66
|
+
|
|
67
|
+
if (name.includes("<tool_call>") || name.includes("<arg_")) return "Malformed";
|
|
68
|
+
|
|
69
|
+
return "Other";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Shell command helpers
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
function inferExecCommandKind(command) {
|
|
77
|
+
const cmd = String(command || "").trim();
|
|
78
|
+
if (/^(npm|yarn|pnpm)\s+(run\s+)?(build|build:|.*:build\b)/.test(cmd)) return "build";
|
|
79
|
+
if (/^(npm|yarn|pnpm)\s+(test|run\s+test\b|run\s+.*test\b)/.test(cmd)) return "test";
|
|
80
|
+
if (/^(npm|yarn|pnpm)\s+run\s+typecheck\b/.test(cmd)) return "typecheck";
|
|
81
|
+
if (/^(npm|yarn|pnpm)\s+(install|add|ci)\b/.test(cmd)) return "dependency";
|
|
82
|
+
if (/^(npm|yarn|pnpm)\s+(pack|publish|version)\b/.test(cmd)) return "package";
|
|
83
|
+
if (/^(npm|yarn|pnpm)\s+run\s+(dev|serve|start|.*dev.*)\b/.test(cmd)) return "dev_server";
|
|
84
|
+
if (/^node\s+--check\b/.test(cmd) || /\bnode\s+--check\b/.test(cmd)) return "syntax_check";
|
|
85
|
+
if (/^node\s+--input-type=module\s+-e\b/.test(cmd) || /^node\s+-e\b/.test(cmd)) return "node_eval";
|
|
86
|
+
if (/^node\s+.*\b(query|analyze|report)\b/.test(cmd)) return "node_cli";
|
|
87
|
+
if (/^git\s+status\b/.test(cmd)) return "git_status";
|
|
88
|
+
if (/^git\s+(push|pull|fetch|clone)\b/.test(cmd) || /\bgit\s+(push|pull|fetch|clone)\b/.test(cmd)) return "git_remote";
|
|
89
|
+
if (/^git\s+(add|commit|branch|config|remote|restore)\b/.test(cmd) || /\bgit\s+(add|commit|branch|config|remote|restore)\b/.test(cmd)) return "git_local";
|
|
90
|
+
if (/^(curl|wget)\b/.test(cmd) || /\b(curl|wget)\b/.test(cmd)) return "http";
|
|
91
|
+
if (/^(ps|pgrep|pkill|kill|lsof)\b/.test(cmd)) return "process";
|
|
92
|
+
if (/^tmux\b/.test(cmd)) return "terminal";
|
|
93
|
+
if (/^(open|osascript)\b/.test(cmd)) return "browser_control";
|
|
94
|
+
if (/^(rm|mkdir|touch|chmod|cp|mv)\b/.test(cmd)) return "file_mutation";
|
|
95
|
+
if (/^(pwd|ls|test)\b/.test(cmd) || /^(pwd|ls)\s*[;&|]/.test(cmd)) return "shell_inspect";
|
|
96
|
+
if (/[;&|]{1,2}/.test(cmd)) return "compound";
|
|
97
|
+
return "unknown";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function shellWords(command) {
|
|
101
|
+
const out = [];
|
|
102
|
+
const re = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^']*)'|(\S+)/g;
|
|
103
|
+
let match;
|
|
104
|
+
while ((match = re.exec(String(command || ""))) !== null) {
|
|
105
|
+
out.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
106
|
+
}
|
|
107
|
+
return out.filter(Boolean);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function unwrapShellCommand(words) {
|
|
111
|
+
if (words.length >= 3 && /^(bash|sh|zsh|fish)$/.test(words[0]) && words[1] === "-lc") {
|
|
112
|
+
return shellWords(words.slice(2).join(" "));
|
|
113
|
+
}
|
|
114
|
+
if (words.length >= 3 && /^(rtk|env|command|xcrun)$/.test(words[0])) {
|
|
115
|
+
return unwrapShellCommand(words.slice(1));
|
|
116
|
+
}
|
|
117
|
+
return words;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function sanitizeCommandSignature(command) {
|
|
121
|
+
const words = unwrapShellCommand(shellWords(command));
|
|
122
|
+
if (words.length === 0) return "unknown";
|
|
123
|
+
const executable = path.basename(words[0] || "unknown");
|
|
124
|
+
const subcommand = words.find((word, idx) => {
|
|
125
|
+
if (idx === 0) return false;
|
|
126
|
+
if (!word || word.startsWith("-")) return false;
|
|
127
|
+
if (/^[A-Z_][A-Z0-9_]*=/.test(word)) return false;
|
|
128
|
+
return true;
|
|
129
|
+
});
|
|
130
|
+
return subcommand ? `${executable} ${subcommand}` : executable;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getExecutableName(command) {
|
|
134
|
+
const words = unwrapShellCommand(shellWords(command));
|
|
135
|
+
if (words.length === 0) return "unknown";
|
|
136
|
+
return path.basename(words[0] || "unknown") || "unknown";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Token totals helpers
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
function emptyTotals() {
|
|
144
|
+
return {
|
|
145
|
+
input_tokens: 0,
|
|
146
|
+
cached_input_tokens: 0,
|
|
147
|
+
cache_creation_input_tokens: 0,
|
|
148
|
+
output_tokens: 0,
|
|
149
|
+
reasoning_output_tokens: 0,
|
|
150
|
+
total_tokens: 0,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function addInto(target, source) {
|
|
155
|
+
target.input_tokens += source.input_tokens || 0;
|
|
156
|
+
target.cached_input_tokens += source.cached_input_tokens || 0;
|
|
157
|
+
target.cache_creation_input_tokens += source.cache_creation_input_tokens || 0;
|
|
158
|
+
target.output_tokens += source.output_tokens || 0;
|
|
159
|
+
target.reasoning_output_tokens += source.reasoning_output_tokens || 0;
|
|
160
|
+
target.total_tokens += source.total_tokens || 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function roundTotals(totals) {
|
|
164
|
+
return {
|
|
165
|
+
input_tokens: Math.round(totals?.input_tokens || 0),
|
|
166
|
+
cached_input_tokens: Math.round(totals?.cached_input_tokens || 0),
|
|
167
|
+
cache_creation_input_tokens: Math.round(totals?.cache_creation_input_tokens || 0),
|
|
168
|
+
output_tokens: Math.round(totals?.output_tokens || 0),
|
|
169
|
+
reasoning_output_tokens: Math.round(totals?.reasoning_output_tokens || 0),
|
|
170
|
+
total_tokens: Math.round(totals?.total_tokens || 0),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function buildExecStatsEntry() {
|
|
175
|
+
return {
|
|
176
|
+
calls: 0,
|
|
177
|
+
failures: 0,
|
|
178
|
+
duration_ms: 0,
|
|
179
|
+
max_duration_ms: 0,
|
|
180
|
+
output_chars: 0,
|
|
181
|
+
output_lines: 0,
|
|
182
|
+
totals: emptyTotals(),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Integer allocation
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
function allocateByLargestRemainder(total, weights, order) {
|
|
191
|
+
const out = {};
|
|
192
|
+
if (!Number.isFinite(total) || total <= 0) {
|
|
193
|
+
for (const key of order) out[key] = 0;
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let totalWeight = 0;
|
|
198
|
+
for (const key of order) {
|
|
199
|
+
const w = Number(weights[key] || 0);
|
|
200
|
+
if (Number.isFinite(w) && w > 0) totalWeight += w;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (totalWeight <= 0) {
|
|
204
|
+
for (const key of order) out[key] = 0;
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const exact = order.map((key) => (Number(weights[key] || 0) / totalWeight) * total);
|
|
209
|
+
const floored = exact.map((x) => Math.floor(x));
|
|
210
|
+
const remainder = total - floored.reduce((a, b) => a + b, 0);
|
|
211
|
+
const remainders = exact
|
|
212
|
+
.map((x, i) => ({ i, frac: x - Math.floor(x) }))
|
|
213
|
+
.sort((a, b) => b.frac - a.frac);
|
|
214
|
+
for (let k = 0; k < remainder; k++) floored[remainders[k % order.length].i] += 1;
|
|
215
|
+
|
|
216
|
+
for (let i = 0; i < order.length; i++) out[order[i]] = floored[i];
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
categorizeTool,
|
|
222
|
+
inferExecCommandKind,
|
|
223
|
+
shellWords,
|
|
224
|
+
unwrapShellCommand,
|
|
225
|
+
sanitizeCommandSignature,
|
|
226
|
+
getExecutableName,
|
|
227
|
+
emptyTotals,
|
|
228
|
+
addInto,
|
|
229
|
+
roundTotals,
|
|
230
|
+
buildExecStatsEntry,
|
|
231
|
+
allocateByLargestRemainder,
|
|
232
|
+
};
|