letmecode 0.1.5 → 0.1.6
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/ink-app/dist/providers/claude.js +86 -4
- package/ink-app/dist/reporting.js +16 -0
- package/package.json +13 -11
|
@@ -58,7 +58,8 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
58
58
|
this.now = options.now ?? (() => new Date());
|
|
59
59
|
}
|
|
60
60
|
async getStats(options = {}) {
|
|
61
|
-
const
|
|
61
|
+
const resolvedSessionsRoot = await resolveClaudeSessionsRoot(this.root);
|
|
62
|
+
const sessionsRoot = resolvedSessionsRoot.rootPath;
|
|
62
63
|
const agentName = normalizeAnalyticsAgentName(this.label);
|
|
63
64
|
const userIdHash = await readClaudeUserIdHash(this.root, this.usageCommandKind, this.readAuthStatusOutput, agentName);
|
|
64
65
|
const byModel = new Map();
|
|
@@ -149,7 +150,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
149
150
|
totals: summaryTotals,
|
|
150
151
|
distinctModels: modelUsage.map((row) => row.modelId),
|
|
151
152
|
distinctPlanTypes: [...planTypes].sort(),
|
|
152
|
-
rootLabel:
|
|
153
|
+
rootLabel: resolvedSessionsRoot.rootLabel,
|
|
153
154
|
rootPath: sessionsRoot
|
|
154
155
|
},
|
|
155
156
|
modelUsage,
|
|
@@ -236,7 +237,87 @@ function resolveClaudeCacheWriteBreakdown(usage) {
|
|
|
236
237
|
};
|
|
237
238
|
}
|
|
238
239
|
function isSessionFile(filePath) {
|
|
239
|
-
return filePath.endsWith(".jsonl")
|
|
240
|
+
return filePath.endsWith(".jsonl");
|
|
241
|
+
}
|
|
242
|
+
async function resolveClaudeSessionsRoot(root) {
|
|
243
|
+
const candidates = buildClaudeSessionsRootCandidates(root);
|
|
244
|
+
for (const candidate of candidates) {
|
|
245
|
+
if (await isDirectory(candidate.rootPath)) {
|
|
246
|
+
return candidate;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return candidates[0] ?? {
|
|
250
|
+
rootLabel: "~/.claude/projects",
|
|
251
|
+
rootPath: path.join(path.resolve(root), ".claude", "projects")
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function buildClaudeSessionsRootCandidates(root) {
|
|
255
|
+
const resolvedRoot = path.resolve(root);
|
|
256
|
+
const baseName = path.basename(resolvedRoot);
|
|
257
|
+
const parentBaseName = path.basename(path.dirname(resolvedRoot));
|
|
258
|
+
const candidates = [];
|
|
259
|
+
if (baseName === "projects") {
|
|
260
|
+
if (parentBaseName === ".claude") {
|
|
261
|
+
candidates.push({
|
|
262
|
+
rootLabel: "~/.claude/projects",
|
|
263
|
+
rootPath: resolvedRoot
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
else if (parentBaseName === "claude" || parentBaseName === "Claude") {
|
|
267
|
+
candidates.push({
|
|
268
|
+
rootLabel: `~/.config/${parentBaseName}/projects`,
|
|
269
|
+
rootPath: resolvedRoot
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
candidates.push({
|
|
274
|
+
rootLabel: "projects",
|
|
275
|
+
rootPath: resolvedRoot
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (baseName === ".claude") {
|
|
280
|
+
candidates.push({
|
|
281
|
+
rootLabel: "~/.claude/projects",
|
|
282
|
+
rootPath: path.join(resolvedRoot, "projects")
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (parentBaseName === ".config" && (baseName === "claude" || baseName === "Claude")) {
|
|
286
|
+
candidates.push({
|
|
287
|
+
rootLabel: `~/.config/${baseName}/projects`,
|
|
288
|
+
rootPath: path.join(resolvedRoot, "projects")
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
candidates.push({
|
|
292
|
+
rootLabel: "~/.claude/projects",
|
|
293
|
+
rootPath: path.join(resolvedRoot, ".claude", "projects")
|
|
294
|
+
}, {
|
|
295
|
+
rootLabel: "~/.config/claude/projects",
|
|
296
|
+
rootPath: path.join(resolvedRoot, ".config", "claude", "projects")
|
|
297
|
+
}, {
|
|
298
|
+
rootLabel: "~/.config/Claude/projects",
|
|
299
|
+
rootPath: path.join(resolvedRoot, ".config", "Claude", "projects")
|
|
300
|
+
});
|
|
301
|
+
const dedupedCandidates = new Map();
|
|
302
|
+
for (const candidate of candidates) {
|
|
303
|
+
const normalizedPath = path.resolve(candidate.rootPath);
|
|
304
|
+
if (!dedupedCandidates.has(normalizedPath)) {
|
|
305
|
+
dedupedCandidates.set(normalizedPath, {
|
|
306
|
+
rootLabel: candidate.rootLabel,
|
|
307
|
+
rootPath: normalizedPath
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return [...dedupedCandidates.values()];
|
|
312
|
+
}
|
|
313
|
+
async function isDirectory(directoryPath) {
|
|
314
|
+
try {
|
|
315
|
+
const stats = await fs.promises.stat(directoryPath);
|
|
316
|
+
return stats.isDirectory();
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
240
321
|
}
|
|
241
322
|
async function* walkSessionFiles(directory) {
|
|
242
323
|
let entries;
|
|
@@ -544,7 +625,8 @@ async function resolveClaudeBinaryPath(root, usageCommandKind) {
|
|
|
544
625
|
async function resolveVsCodeClaudeBinaryPath(root) {
|
|
545
626
|
const boosterDirectories = [
|
|
546
627
|
path.join(root, ".vscode", "extensions"),
|
|
547
|
-
path.join(root, ".vscode-server", "extensions")
|
|
628
|
+
path.join(root, ".vscode-server", "extensions"),
|
|
629
|
+
path.join(root, ".vscode-server-insiders", "extensions")
|
|
548
630
|
];
|
|
549
631
|
for (const directory of boosterDirectories) {
|
|
550
632
|
const binaryPath = await resolveClaudeBinaryFromExtensionDirectory(directory);
|
|
@@ -37,9 +37,25 @@ function buildAnonymousUsageReport(stats, window, letmecodeVersion) {
|
|
|
37
37
|
used_percents: resolveReportedUsedPercents(window),
|
|
38
38
|
used_exhausted: window.maxUsedPercent >= 100,
|
|
39
39
|
value_dollars: roundDollars(window.totals.estimatedCredits * CREDIT_TO_DOLLARS),
|
|
40
|
+
usage_raw: buildUsageRaw(stats.providerId, window),
|
|
40
41
|
letmecode_version: letmecodeVersion
|
|
41
42
|
};
|
|
42
43
|
}
|
|
44
|
+
function buildUsageRaw(providerId, window) {
|
|
45
|
+
const usageRaw = {
|
|
46
|
+
output: window.totals.outputTokens,
|
|
47
|
+
input_non_cache: window.totals.inputTokens,
|
|
48
|
+
input_cache_read: window.totals.cacheReadInputTokens
|
|
49
|
+
};
|
|
50
|
+
if (isAnthropicProvider(providerId)) {
|
|
51
|
+
usageRaw.input_cache_w5m = window.totals.cacheWrite5mInputTokens;
|
|
52
|
+
usageRaw.input_cache_w1h = window.totals.cacheWrite1hInputTokens;
|
|
53
|
+
}
|
|
54
|
+
return usageRaw;
|
|
55
|
+
}
|
|
56
|
+
function isAnthropicProvider(providerId) {
|
|
57
|
+
return providerId === "claude" || providerId === "claude-vscode";
|
|
58
|
+
}
|
|
43
59
|
function resolveReportedUsedPercents(window) {
|
|
44
60
|
if (window.minUsedPercent === window.maxUsedPercent) {
|
|
45
61
|
return clampPercent(window.maxUsedPercent);
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letmecode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Provider-based terminal usage dashboard for LetMeCode.",
|
|
5
5
|
"author": "devforth.io",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"packageManager": "pnpm@10.28.2",
|
|
7
8
|
"type": "commonjs",
|
|
8
9
|
"bin": {
|
|
9
10
|
"letmecode": "./bin/letmecode.js"
|
|
@@ -20,6 +21,16 @@
|
|
|
20
21
|
"publishConfig": {
|
|
21
22
|
"access": "public"
|
|
22
23
|
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true }); require('node:fs').rmSync('ink-app/dist', { recursive: true, force: true });\"",
|
|
26
|
+
"build": "npm run clean && tsc -p tsconfig.json && tsc -p ink-app/tsconfig.json",
|
|
27
|
+
"prepack": "npm run build",
|
|
28
|
+
"prestart": "npm run build",
|
|
29
|
+
"start": "node ./bin/letmecode.js",
|
|
30
|
+
"pretest": "npm run build",
|
|
31
|
+
"smoke": "node ./bin/letmecode.js",
|
|
32
|
+
"test": "node --test ink-app/test/*.test.mjs"
|
|
33
|
+
},
|
|
23
34
|
"keywords": [
|
|
24
35
|
"cli",
|
|
25
36
|
"ink",
|
|
@@ -36,14 +47,5 @@
|
|
|
36
47
|
"@types/node": "^24.0.7",
|
|
37
48
|
"@types/react": "^18.3.24",
|
|
38
49
|
"typescript": "^5.8.3"
|
|
39
|
-
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true }); require('node:fs').rmSync('ink-app/dist', { recursive: true, force: true });\"",
|
|
42
|
-
"build": "npm run clean && tsc -p tsconfig.json && tsc -p ink-app/tsconfig.json",
|
|
43
|
-
"prestart": "npm run build",
|
|
44
|
-
"start": "node ./bin/letmecode.js",
|
|
45
|
-
"pretest": "npm run build",
|
|
46
|
-
"smoke": "node ./bin/letmecode.js",
|
|
47
|
-
"test": "node --test ink-app/test/*.test.mjs"
|
|
48
50
|
}
|
|
49
|
-
}
|
|
51
|
+
}
|