tokely 0.5.2 → 0.5.3
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 +4 -2
- package/dist/cli.js +181 -11
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,13 +24,13 @@ tokely
|
|
|
24
24
|
## Usage
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
tokely [--all] [--today] [--claude] [--codex] [--cursor] [--gemini] [--opencode] [--pi] [--dark] [--format png|svg|json] [--output ./heatmap-last-year.png]
|
|
27
|
+
tokely [--all] [--today] [--claude] [--codex] [--cursor] [--gemini] [--opencode] [--openclaw] [--pi] [--dark] [--format png|svg|json] [--output ./heatmap-last-year.png]
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
By default, the CLI:
|
|
31
31
|
|
|
32
32
|
- scans all supported providers
|
|
33
|
-
- renders the top
|
|
33
|
+
- renders the top 5 providers with available data (priority: Claude Code → Codex → Cursor → Open Code → OpenClaw)
|
|
34
34
|
- writes `./heatmap-last-year.png`
|
|
35
35
|
- infers the date window as the rolling last year ending today
|
|
36
36
|
|
|
@@ -42,6 +42,7 @@ By default, the CLI:
|
|
|
42
42
|
- `--cursor`: include only Cursor data
|
|
43
43
|
- `--gemini`: include only Gemini CLI data
|
|
44
44
|
- `--opencode`: include only Open Code data
|
|
45
|
+
- `--openclaw`: include only OpenClaw data
|
|
45
46
|
- `--pi`: include only Pi Coding Agent data
|
|
46
47
|
- `--all`: merge all providers into one combined graph
|
|
47
48
|
- `--dark`: render the image with the dark theme
|
|
@@ -123,6 +124,7 @@ npx tokely --dark --format svg --output ./out/heatmap-dark.svg
|
|
|
123
124
|
- Cursor: reads `cursorAuth/accessToken` and `cursorAuth/refreshToken` from `$CURSOR_STATE_DB_PATH`, `$CURSOR_CONFIG_DIR/User/globalStorage/state.vscdb`, `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb` (macOS), `%APPDATA%/Cursor/User/globalStorage/state.vscdb` (Windows), or `~/.config/Cursor/User/globalStorage/state.vscdb` (Linux), then loads usage from Cursor's CSV export endpoint
|
|
124
125
|
- Gemini CLI: `$GEMINI_CONFIG_DIR/tmp/**/chats/session-*.json` or `~/.gemini/tmp/**/chats/session-*.json`
|
|
125
126
|
- Open Code: prefers `$OPENCODE_DATA_DIR/opencode.db` or `~/.local/share/opencode/opencode.db`, and falls back to `$OPENCODE_DATA_DIR/storage/message` or `~/.local/share/opencode/storage/message`
|
|
127
|
+
- OpenClaw: `$HOME/.openclaw/agents`, `$HOME/.clawdbot/agents`, `$HOME/.moltbot/agents`, or `$HOME/.moldbot/agents` (reads `*.jsonl` session transcripts)
|
|
126
128
|
- Pi Coding Agent: `$PI_CODING_AGENT_DIR/sessions` or `~/.pi/agent/sessions`
|
|
127
129
|
|
|
128
130
|
When Claude Code falls back to `stats-cache.json`, the daily input/output/cache split is reconstructed from Claude's cached model totals because the older layout does not keep per-request usage logs.
|
package/dist/cli.js
CHANGED
|
@@ -5833,6 +5833,35 @@ var heatmapThemes = {
|
|
|
5833
5833
|
]
|
|
5834
5834
|
}
|
|
5835
5835
|
},
|
|
5836
|
+
openclaw: {
|
|
5837
|
+
title: "OpenClaw",
|
|
5838
|
+
colors: {
|
|
5839
|
+
light: [
|
|
5840
|
+
"#fef2f2",
|
|
5841
|
+
// red-50
|
|
5842
|
+
"#fecaca",
|
|
5843
|
+
// red-200
|
|
5844
|
+
"#fca5a5",
|
|
5845
|
+
// red-300
|
|
5846
|
+
"#ef4444",
|
|
5847
|
+
// red-500
|
|
5848
|
+
"#991b1b"
|
|
5849
|
+
// red-800
|
|
5850
|
+
],
|
|
5851
|
+
dark: [
|
|
5852
|
+
"#450a0a",
|
|
5853
|
+
// red-950
|
|
5854
|
+
"#991b1b",
|
|
5855
|
+
// red-800
|
|
5856
|
+
"#dc2626",
|
|
5857
|
+
// red-600
|
|
5858
|
+
"#fca5a5",
|
|
5859
|
+
// red-300
|
|
5860
|
+
"#fecaca"
|
|
5861
|
+
// red-200
|
|
5862
|
+
]
|
|
5863
|
+
}
|
|
5864
|
+
},
|
|
5836
5865
|
pi: {
|
|
5837
5866
|
title: "Pi Coding Agent",
|
|
5838
5867
|
colors: {
|
|
@@ -5863,7 +5892,7 @@ var heatmapThemes = {
|
|
|
5863
5892
|
}
|
|
5864
5893
|
},
|
|
5865
5894
|
all: {
|
|
5866
|
-
title: "Amp / Claude Code / Codex / Cursor / Gemini CLI / Open Code / Pi Coding Agent",
|
|
5895
|
+
title: "Amp / Claude Code / Codex / Cursor / Gemini CLI / Open Code / OpenClaw / Pi Coding Agent",
|
|
5867
5896
|
titleCaption: "Total usage from",
|
|
5868
5897
|
colors: {
|
|
5869
5898
|
light: [
|
|
@@ -7874,6 +7903,7 @@ var providerIds = [
|
|
|
7874
7903
|
"codex",
|
|
7875
7904
|
"cursor",
|
|
7876
7905
|
"opencode",
|
|
7906
|
+
"openclaw",
|
|
7877
7907
|
"amp",
|
|
7878
7908
|
"gemini",
|
|
7879
7909
|
"pi"
|
|
@@ -7882,7 +7912,8 @@ var defaultProviderIds = [
|
|
|
7882
7912
|
"claude",
|
|
7883
7913
|
"codex",
|
|
7884
7914
|
"cursor",
|
|
7885
|
-
"opencode"
|
|
7915
|
+
"opencode",
|
|
7916
|
+
"openclaw"
|
|
7886
7917
|
];
|
|
7887
7918
|
var providerStatusLabel = {
|
|
7888
7919
|
amp: "Amp",
|
|
@@ -7891,6 +7922,7 @@ var providerStatusLabel = {
|
|
|
7891
7922
|
cursor: "Cursor",
|
|
7892
7923
|
gemini: "Gemini CLI",
|
|
7893
7924
|
opencode: "Open Code",
|
|
7925
|
+
openclaw: "OpenClaw",
|
|
7894
7926
|
pi: "Pi Coding Agent"
|
|
7895
7927
|
};
|
|
7896
7928
|
|
|
@@ -8088,21 +8120,149 @@ async function loadOpenCodeRows(start, end) {
|
|
|
8088
8120
|
);
|
|
8089
8121
|
}
|
|
8090
8122
|
|
|
8091
|
-
// src/lib/
|
|
8092
|
-
import { existsSync as existsSync7 } from "fs";
|
|
8123
|
+
// src/lib/openclaw.ts
|
|
8124
|
+
import { existsSync as existsSync7, readFileSync, statSync } from "fs";
|
|
8125
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
8126
|
+
import { join as join8 } from "path";
|
|
8093
8127
|
import { homedir as homedir7 } from "os";
|
|
8094
|
-
|
|
8128
|
+
function scanOpenClawJsonlFiles() {
|
|
8129
|
+
const dirs = [
|
|
8130
|
+
join8(homedir7(), ".openclaw", "agents"),
|
|
8131
|
+
join8(homedir7(), ".clawdbot", "agents"),
|
|
8132
|
+
join8(homedir7(), ".moltbot", "agents"),
|
|
8133
|
+
join8(homedir7(), ".moldbot", "agents")
|
|
8134
|
+
];
|
|
8135
|
+
const files = [];
|
|
8136
|
+
for (const dir of dirs) {
|
|
8137
|
+
if (!existsSync7(dir)) continue;
|
|
8138
|
+
scanDirRecursive(dir, files);
|
|
8139
|
+
}
|
|
8140
|
+
return files;
|
|
8141
|
+
}
|
|
8142
|
+
function scanDirRecursive(dir, files) {
|
|
8143
|
+
try {
|
|
8144
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
8145
|
+
for (const entry of entries) {
|
|
8146
|
+
const fullPath = join8(dir, entry.name);
|
|
8147
|
+
if (entry.isDirectory()) {
|
|
8148
|
+
scanDirRecursive(fullPath, files);
|
|
8149
|
+
} else if (entry.name.endsWith(".jsonl")) {
|
|
8150
|
+
files.push(fullPath);
|
|
8151
|
+
}
|
|
8152
|
+
}
|
|
8153
|
+
} catch {
|
|
8154
|
+
}
|
|
8155
|
+
}
|
|
8156
|
+
function parseTranscript(filePath, start, end, totals, modelTotals, recentModelTotals) {
|
|
8157
|
+
const content = readFileSync(filePath, "utf-8");
|
|
8158
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
8159
|
+
let currentModel = null;
|
|
8160
|
+
let currentProvider = null;
|
|
8161
|
+
const fileMtime = statSync(filePath).mtimeMs;
|
|
8162
|
+
const recentStart = getRecentWindowStart(end);
|
|
8163
|
+
for (const line of lines) {
|
|
8164
|
+
let entry;
|
|
8165
|
+
try {
|
|
8166
|
+
entry = JSON.parse(line);
|
|
8167
|
+
} catch {
|
|
8168
|
+
continue;
|
|
8169
|
+
}
|
|
8170
|
+
const type = entry.type;
|
|
8171
|
+
if (type === "model_change") {
|
|
8172
|
+
currentModel = entry.modelId ?? currentModel;
|
|
8173
|
+
currentProvider = entry.provider ?? currentProvider;
|
|
8174
|
+
continue;
|
|
8175
|
+
}
|
|
8176
|
+
if (type === "custom" && entry.customType === "model-snapshot") {
|
|
8177
|
+
const data = entry.data;
|
|
8178
|
+
if (data) {
|
|
8179
|
+
currentModel = data.modelId ?? currentModel;
|
|
8180
|
+
currentProvider = data.provider ?? currentProvider;
|
|
8181
|
+
}
|
|
8182
|
+
continue;
|
|
8183
|
+
}
|
|
8184
|
+
if (type === "message") {
|
|
8185
|
+
const msg = entry.message;
|
|
8186
|
+
if (!msg || msg.role !== "assistant") continue;
|
|
8187
|
+
const model = msg.model ?? currentModel;
|
|
8188
|
+
if (!model) continue;
|
|
8189
|
+
const usage = msg.usage;
|
|
8190
|
+
if (!usage) continue;
|
|
8191
|
+
const input = Math.max(usage.input ?? 0, 0);
|
|
8192
|
+
const output = Math.max(usage.output ?? 0, 0);
|
|
8193
|
+
const cacheRead = Math.max(usage.cacheRead ?? 0, 0);
|
|
8194
|
+
const cacheWrite = Math.max(usage.cacheWrite ?? 0, 0);
|
|
8195
|
+
const total = Math.max(usage.totalTokens ?? 0, 0);
|
|
8196
|
+
if (total <= 0 && input <= 0 && output <= 0) continue;
|
|
8197
|
+
const ts = msg.timestamp ?? fileMtime;
|
|
8198
|
+
const timestamp = new Date(ts);
|
|
8199
|
+
if (timestamp < start || timestamp > end) continue;
|
|
8200
|
+
const normalizedModel = normalizeModelName(model);
|
|
8201
|
+
addDailyTokenTotals(
|
|
8202
|
+
totals,
|
|
8203
|
+
timestamp,
|
|
8204
|
+
{
|
|
8205
|
+
input,
|
|
8206
|
+
output,
|
|
8207
|
+
cache: { input: cacheRead, output: cacheWrite },
|
|
8208
|
+
total
|
|
8209
|
+
},
|
|
8210
|
+
normalizedModel
|
|
8211
|
+
);
|
|
8212
|
+
addModelTokenTotals(modelTotals, normalizedModel, {
|
|
8213
|
+
input,
|
|
8214
|
+
output,
|
|
8215
|
+
cache: { input: cacheRead, output: cacheWrite },
|
|
8216
|
+
total
|
|
8217
|
+
});
|
|
8218
|
+
if (timestamp >= recentStart) {
|
|
8219
|
+
addModelTokenTotals(recentModelTotals, normalizedModel, {
|
|
8220
|
+
input,
|
|
8221
|
+
output,
|
|
8222
|
+
cache: { input: cacheRead, output: cacheWrite },
|
|
8223
|
+
total
|
|
8224
|
+
});
|
|
8225
|
+
}
|
|
8226
|
+
}
|
|
8227
|
+
}
|
|
8228
|
+
}
|
|
8229
|
+
function isOpenClawAvailable() {
|
|
8230
|
+
return scanOpenClawJsonlFiles().length > 0;
|
|
8231
|
+
}
|
|
8232
|
+
async function loadOpenClawRows(start, end) {
|
|
8233
|
+
const files = scanOpenClawJsonlFiles();
|
|
8234
|
+
const totals = /* @__PURE__ */ new Map();
|
|
8235
|
+
const modelTotals = /* @__PURE__ */ new Map();
|
|
8236
|
+
const recentModelTotals = /* @__PURE__ */ new Map();
|
|
8237
|
+
for (const file of files) {
|
|
8238
|
+
try {
|
|
8239
|
+
parseTranscript(file, start, end, totals, modelTotals, recentModelTotals);
|
|
8240
|
+
} catch {
|
|
8241
|
+
}
|
|
8242
|
+
}
|
|
8243
|
+
const daily = totalsToRows(totals);
|
|
8244
|
+
return {
|
|
8245
|
+
provider: "openclaw",
|
|
8246
|
+
daily,
|
|
8247
|
+
insights: getProviderInsights(modelTotals, recentModelTotals, daily, end)
|
|
8248
|
+
};
|
|
8249
|
+
}
|
|
8250
|
+
|
|
8251
|
+
// src/lib/pi.ts
|
|
8252
|
+
import { existsSync as existsSync8 } from "fs";
|
|
8253
|
+
import { homedir as homedir8 } from "os";
|
|
8254
|
+
import { join as join9, resolve as resolve7 } from "path";
|
|
8095
8255
|
var PI_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
|
|
8096
8256
|
var CLASSIFICATION_PREFIX_BYTES2 = 16 * 1024;
|
|
8097
8257
|
function getPiAgentDir() {
|
|
8098
8258
|
const configuredAgentDir = process.env[PI_AGENT_DIR_ENV]?.trim();
|
|
8099
|
-
return configuredAgentDir ? resolve7(configuredAgentDir) :
|
|
8259
|
+
return configuredAgentDir ? resolve7(configuredAgentDir) : join9(homedir8(), ".pi", "agent");
|
|
8100
8260
|
}
|
|
8101
8261
|
async function getPiSessionFiles() {
|
|
8102
|
-
return listFilesRecursive(
|
|
8262
|
+
return listFilesRecursive(join9(getPiAgentDir(), "sessions"), ".jsonl");
|
|
8103
8263
|
}
|
|
8104
8264
|
function isPiAvailable() {
|
|
8105
|
-
return
|
|
8265
|
+
return existsSync8(join9(getPiAgentDir(), "sessions"));
|
|
8106
8266
|
}
|
|
8107
8267
|
function classifyPiRecord(prefix) {
|
|
8108
8268
|
if (prefix.includes('"type":"message"') && prefix.includes('"role":"assistant"')) {
|
|
@@ -8208,6 +8368,7 @@ function createEmptyProviderAvailability() {
|
|
|
8208
8368
|
cursor: false,
|
|
8209
8369
|
gemini: false,
|
|
8210
8370
|
opencode: false,
|
|
8371
|
+
openclaw: false,
|
|
8211
8372
|
pi: false
|
|
8212
8373
|
};
|
|
8213
8374
|
}
|
|
@@ -8225,6 +8386,8 @@ async function isProviderAvailable(provider) {
|
|
|
8225
8386
|
return isGeminiAvailable();
|
|
8226
8387
|
case "opencode":
|
|
8227
8388
|
return isOpenCodeAvailable();
|
|
8389
|
+
case "openclaw":
|
|
8390
|
+
return isOpenClawAvailable();
|
|
8228
8391
|
case "pi":
|
|
8229
8392
|
return isPiAvailable();
|
|
8230
8393
|
default: {
|
|
@@ -8260,6 +8423,7 @@ async function aggregateUsage({
|
|
|
8260
8423
|
cursor: null,
|
|
8261
8424
|
gemini: null,
|
|
8262
8425
|
opencode: null,
|
|
8426
|
+
openclaw: null,
|
|
8263
8427
|
pi: null
|
|
8264
8428
|
};
|
|
8265
8429
|
const warnings = [];
|
|
@@ -8284,6 +8448,9 @@ async function aggregateUsage({
|
|
|
8284
8448
|
case "opencode":
|
|
8285
8449
|
summary = await loadOpenCodeRows(start, end);
|
|
8286
8450
|
break;
|
|
8451
|
+
case "openclaw":
|
|
8452
|
+
summary = await loadOpenClawRows(start, end);
|
|
8453
|
+
break;
|
|
8287
8454
|
case "pi":
|
|
8288
8455
|
summary = await loadPiRows(start, end);
|
|
8289
8456
|
break;
|
|
@@ -8307,7 +8474,7 @@ var HELP_TEXT = `tokely
|
|
|
8307
8474
|
Generate rolling 1-year usage heatmap image(s) (today is the latest day).
|
|
8308
8475
|
|
|
8309
8476
|
Usage:
|
|
8310
|
-
tokely [--all] [--today] [--amp] [--claude] [--codex] [--cursor] [--gemini] [--opencode] [--pi] [--dark] [--format png|svg|json] [--output ./heatmap-last-year.png]
|
|
8477
|
+
tokely [--all] [--today] [--amp] [--claude] [--codex] [--cursor] [--gemini] [--opencode] [--openclaw] [--pi] [--dark] [--format png|svg|json] [--output ./heatmap-last-year.png]
|
|
8311
8478
|
|
|
8312
8479
|
Options:
|
|
8313
8480
|
--all Render one merged graph for all providers
|
|
@@ -8318,6 +8485,7 @@ Options:
|
|
|
8318
8485
|
--cursor Render Cursor graph
|
|
8319
8486
|
--gemini Render Gemini CLI graph
|
|
8320
8487
|
--opencode Render Open Code graph
|
|
8488
|
+
--openclaw Render OpenClaw graph
|
|
8321
8489
|
--pi Render Pi Coding Agent graph
|
|
8322
8490
|
--dark Render with the dark theme
|
|
8323
8491
|
-f, --format Output format: png, svg, or json (default: png)
|
|
@@ -8343,6 +8511,7 @@ function validateArgs(values) {
|
|
|
8343
8511
|
cursor: ow.boolean,
|
|
8344
8512
|
gemini: ow.boolean,
|
|
8345
8513
|
opencode: ow.boolean,
|
|
8514
|
+
openclaw: ow.boolean,
|
|
8346
8515
|
pi: ow.boolean
|
|
8347
8516
|
})
|
|
8348
8517
|
);
|
|
@@ -8409,7 +8578,7 @@ function getRequestedProviders(values) {
|
|
|
8409
8578
|
return providerIds.filter((id) => values[id]);
|
|
8410
8579
|
}
|
|
8411
8580
|
function getMergedNoDataMessage() {
|
|
8412
|
-
return "No usage data found for
|
|
8581
|
+
return "No usage data found for Claude Code, Codex, Cursor, Open Code, OpenClaw, Amp, Gemini CLI, or Pi Coding Agent.";
|
|
8413
8582
|
}
|
|
8414
8583
|
function getRequestedMissingProvidersMessage(missing) {
|
|
8415
8584
|
return `Requested provider data not found: ${missing.map((provider) => providerStatusLabel[provider]).join(", ")}`;
|
|
@@ -8441,7 +8610,7 @@ function getDefaultOutputProviderIds(rowsByProvider) {
|
|
|
8441
8610
|
continue;
|
|
8442
8611
|
}
|
|
8443
8612
|
selected.push(provider);
|
|
8444
|
-
if (selected.length ===
|
|
8613
|
+
if (selected.length === 5) {
|
|
8445
8614
|
return selected;
|
|
8446
8615
|
}
|
|
8447
8616
|
}
|
|
@@ -8561,6 +8730,7 @@ async function main() {
|
|
|
8561
8730
|
cursor: { type: "boolean", default: false },
|
|
8562
8731
|
gemini: { type: "boolean", default: false },
|
|
8563
8732
|
opencode: { type: "boolean", default: false },
|
|
8733
|
+
openclaw: { type: "boolean", default: false },
|
|
8564
8734
|
pi: { type: "boolean", default: false }
|
|
8565
8735
|
},
|
|
8566
8736
|
allowPositionals: false
|