claude-usage-rzp 0.1.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 +239 -0
- package/dist/commands/config.js +17 -0
- package/dist/commands/interactive.js +217 -0
- package/dist/commands/overview.js +98 -0
- package/dist/commands/project.js +78 -0
- package/dist/commands/projects.js +66 -0
- package/dist/commands/session.js +98 -0
- package/dist/config.js +33 -0
- package/dist/index.js +65 -0
- package/dist/loader.js +299 -0
- package/dist/pricing.js +55 -0
- package/dist/types.js +2 -0
- package/package.json +48 -0
- package/src/commands/config.ts +16 -0
- package/src/commands/interactive.ts +271 -0
- package/src/commands/overview.ts +126 -0
- package/src/commands/project.ts +85 -0
- package/src/commands/projects.ts +83 -0
- package/src/commands/session.ts +105 -0
- package/src/config.ts +34 -0
- package/src/index.ts +73 -0
- package/src/loader.ts +360 -0
- package/src/pricing.ts +68 -0
- package/src/types.ts +78 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.projectsCommand = projectsCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
9
|
+
const loader_1 = require("../loader");
|
|
10
|
+
const pricing_1 = require("../pricing");
|
|
11
|
+
const config_1 = require("../config");
|
|
12
|
+
function projectsCommand() {
|
|
13
|
+
if (!(0, config_1.hasProjects)()) {
|
|
14
|
+
console.log(chalk_1.default.red(`✗ No projects directory found in ${(0, config_1.getDataDir)()}`));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const projectSummaries = (0, loader_1.buildProjectSummaries)();
|
|
18
|
+
if (Object.keys(projectSummaries).length === 0) {
|
|
19
|
+
console.log(chalk_1.default.yellow('No projects found'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const projectData = [];
|
|
23
|
+
for (const [encodedPath, sessions] of Object.entries(projectSummaries)) {
|
|
24
|
+
const displayPath = decodeURIComponent(encodedPath);
|
|
25
|
+
const sessionCount = sessions.length;
|
|
26
|
+
const totalInput = sessions.reduce((sum, s) => sum + s.totalInputTokens, 0);
|
|
27
|
+
const totalOutput = sessions.reduce((sum, s) => sum + s.totalOutputTokens, 0);
|
|
28
|
+
const totalCost = sessions.reduce((sum, s) => sum + s.totalCostUsd, 0);
|
|
29
|
+
projectData.push({
|
|
30
|
+
path: displayPath,
|
|
31
|
+
encoded: encodedPath,
|
|
32
|
+
sessions: sessionCount,
|
|
33
|
+
input: totalInput,
|
|
34
|
+
output: totalOutput,
|
|
35
|
+
cost: totalCost,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Sort by cost descending
|
|
39
|
+
projectData.sort((a, b) => b.cost - a.cost);
|
|
40
|
+
console.log(chalk_1.default.cyan.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
41
|
+
console.log(chalk_1.default.cyan.bold(` Projects (${projectData.length} total)`));
|
|
42
|
+
console.log(chalk_1.default.cyan.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
43
|
+
const table = new cli_table3_1.default({
|
|
44
|
+
head: [
|
|
45
|
+
chalk_1.default.cyan('Project Path'),
|
|
46
|
+
chalk_1.default.white('Sessions'),
|
|
47
|
+
chalk_1.default.blue('Input Tokens'),
|
|
48
|
+
chalk_1.default.green('Output Tokens'),
|
|
49
|
+
chalk_1.default.green.bold('Total Cost'),
|
|
50
|
+
],
|
|
51
|
+
colAligns: ['left', 'right', 'right', 'right', 'right'],
|
|
52
|
+
colWidths: [40, 10, 15, 15, 12],
|
|
53
|
+
wordWrap: true,
|
|
54
|
+
});
|
|
55
|
+
for (const proj of projectData) {
|
|
56
|
+
table.push([
|
|
57
|
+
proj.path,
|
|
58
|
+
proj.sessions.toString(),
|
|
59
|
+
(0, pricing_1.formatTokens)(proj.input),
|
|
60
|
+
(0, pricing_1.formatTokens)(proj.output),
|
|
61
|
+
(0, pricing_1.formatCost)(proj.cost),
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
console.log(table.toString());
|
|
65
|
+
console.log(chalk_1.default.dim('\n💡 Tip: Use "claude-usage project <path>" to see sessions in a project\n'));
|
|
66
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sessionCommand = sessionCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
9
|
+
const loader_1 = require("../loader");
|
|
10
|
+
const pricing_1 = require("../pricing");
|
|
11
|
+
function sessionCommand(sessionId, options) {
|
|
12
|
+
const projectSummaries = (0, loader_1.buildProjectSummaries)();
|
|
13
|
+
let foundSession = null;
|
|
14
|
+
let foundProject = '';
|
|
15
|
+
if (options.project) {
|
|
16
|
+
// User specified project
|
|
17
|
+
const encoded = encodeURIComponent(options.project);
|
|
18
|
+
if (encoded in projectSummaries) {
|
|
19
|
+
for (const sess of projectSummaries[encoded]) {
|
|
20
|
+
if (sess.sessionId === sessionId || sess.sessionId.startsWith(sessionId)) {
|
|
21
|
+
foundSession = sess;
|
|
22
|
+
foundProject = encoded;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Search all projects
|
|
30
|
+
for (const [encodedPath, sessions] of Object.entries(projectSummaries)) {
|
|
31
|
+
for (const sess of sessions) {
|
|
32
|
+
if (sess.sessionId === sessionId || sess.sessionId.startsWith(sessionId)) {
|
|
33
|
+
foundSession = sess;
|
|
34
|
+
foundProject = encodedPath;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (foundSession)
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!foundSession) {
|
|
43
|
+
console.log(chalk_1.default.red(`✗ Session not found: ${sessionId}`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// Load messages
|
|
47
|
+
const messages = (0, loader_1.loadSessionMessages)(foundProject, foundSession.sessionId);
|
|
48
|
+
if (messages.length === 0) {
|
|
49
|
+
console.log(chalk_1.default.yellow('No messages found in this session'));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log(chalk_1.default.cyan.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
53
|
+
console.log(chalk_1.default.cyan.bold(` Session: ${foundSession.slug}`));
|
|
54
|
+
console.log(chalk_1.default.dim(` Project: ${decodeURIComponent(foundProject)}`));
|
|
55
|
+
console.log(chalk_1.default.dim(` ID: ${foundSession.sessionId}`));
|
|
56
|
+
console.log(chalk_1.default.dim(` ${messages.length} messages`));
|
|
57
|
+
console.log(chalk_1.default.cyan.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
58
|
+
// Summary
|
|
59
|
+
console.log(chalk_1.default.bold('Summary:'));
|
|
60
|
+
console.log(` Total Input Tokens: ${chalk_1.default.blue((0, pricing_1.formatTokens)(foundSession.totalInputTokens))}`);
|
|
61
|
+
console.log(` Total Output Tokens: ${chalk_1.default.green((0, pricing_1.formatTokens)(foundSession.totalOutputTokens))}`);
|
|
62
|
+
console.log(` Total Cost: ${chalk_1.default.green.bold((0, pricing_1.formatCost)(foundSession.totalCostUsd))}`);
|
|
63
|
+
console.log(` Models Used: ${chalk_1.default.yellow(Array.from(foundSession.modelsUsed).join(', '))}`);
|
|
64
|
+
// Message breakdown
|
|
65
|
+
console.log(chalk_1.default.bold('\nMessage Details:'));
|
|
66
|
+
const table = new cli_table3_1.default({
|
|
67
|
+
head: [
|
|
68
|
+
chalk_1.default.dim('#'),
|
|
69
|
+
chalk_1.default.dim('Timestamp'),
|
|
70
|
+
chalk_1.default.cyan('Model'),
|
|
71
|
+
chalk_1.default.blue('Input'),
|
|
72
|
+
chalk_1.default.green('Output'),
|
|
73
|
+
chalk_1.default.yellow('Cache Write'),
|
|
74
|
+
chalk_1.default.magenta('Cache Read'),
|
|
75
|
+
chalk_1.default.green.bold('Cost'),
|
|
76
|
+
],
|
|
77
|
+
colAligns: ['right', 'left', 'left', 'right', 'right', 'right', 'right', 'right'],
|
|
78
|
+
});
|
|
79
|
+
for (let i = 0; i < messages.length; i++) {
|
|
80
|
+
const msg = messages[i];
|
|
81
|
+
// Simplify model name
|
|
82
|
+
let modelDisplay = msg.model.replace('claude-', '').replace(/-20\d{6}/, (match) => ` (${match.substring(1)})`);
|
|
83
|
+
// Format timestamp
|
|
84
|
+
const timestamp = msg.timestamp ? new Date(msg.timestamp).toLocaleString('en-US') : 'N/A';
|
|
85
|
+
table.push([
|
|
86
|
+
(i + 1).toString(),
|
|
87
|
+
timestamp,
|
|
88
|
+
modelDisplay,
|
|
89
|
+
(0, pricing_1.formatTokens)(msg.inputTokens),
|
|
90
|
+
(0, pricing_1.formatTokens)(msg.outputTokens),
|
|
91
|
+
(0, pricing_1.formatTokens)(msg.cacheCreationInputTokens),
|
|
92
|
+
(0, pricing_1.formatTokens)(msg.cacheReadInputTokens),
|
|
93
|
+
(0, pricing_1.formatCost)(msg.costUsd),
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
console.log(table.toString());
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDataDir = getDataDir;
|
|
4
|
+
exports.setDataDir = setDataDir;
|
|
5
|
+
exports.hasStatsCache = hasStatsCache;
|
|
6
|
+
exports.hasProjects = hasProjects;
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
let dataDir = null;
|
|
11
|
+
function getDataDir() {
|
|
12
|
+
if (dataDir) {
|
|
13
|
+
return dataDir;
|
|
14
|
+
}
|
|
15
|
+
// Check environment variable
|
|
16
|
+
const envDir = process.env.CLAUDE_DATA_DIR;
|
|
17
|
+
if (envDir) {
|
|
18
|
+
dataDir = envDir;
|
|
19
|
+
return dataDir;
|
|
20
|
+
}
|
|
21
|
+
// Default to ~/.claude
|
|
22
|
+
dataDir = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
23
|
+
return dataDir;
|
|
24
|
+
}
|
|
25
|
+
function setDataDir(path) {
|
|
26
|
+
dataDir = path;
|
|
27
|
+
}
|
|
28
|
+
function hasStatsCache() {
|
|
29
|
+
return (0, fs_1.existsSync)((0, path_1.join)(getDataDir(), 'stats-cache.json'));
|
|
30
|
+
}
|
|
31
|
+
function hasProjects() {
|
|
32
|
+
return (0, fs_1.existsSync)((0, path_1.join)(getDataDir(), 'projects'));
|
|
33
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const config_1 = require("./config");
|
|
6
|
+
const interactive_1 = require("./commands/interactive");
|
|
7
|
+
const overview_1 = require("./commands/overview");
|
|
8
|
+
const projects_1 = require("./commands/projects");
|
|
9
|
+
const project_1 = require("./commands/project");
|
|
10
|
+
const session_1 = require("./commands/session");
|
|
11
|
+
const config_2 = require("./commands/config");
|
|
12
|
+
const program = new commander_1.Command();
|
|
13
|
+
program
|
|
14
|
+
.name('claude-usage')
|
|
15
|
+
.description('View Claude Code API usage directly in your terminal')
|
|
16
|
+
.version('0.1.0')
|
|
17
|
+
.option('--data-dir <path>', 'Path to Claude data directory')
|
|
18
|
+
.hook('preAction', (thisCommand) => {
|
|
19
|
+
const opts = thisCommand.opts();
|
|
20
|
+
if (opts.dataDir) {
|
|
21
|
+
(0, config_1.setDataDir)(opts.dataDir);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
// Default command (interactive)
|
|
25
|
+
program
|
|
26
|
+
.action(async () => {
|
|
27
|
+
await (0, interactive_1.interactiveCommand)();
|
|
28
|
+
});
|
|
29
|
+
// Overview command (non-interactive, one-shot)
|
|
30
|
+
program
|
|
31
|
+
.command('overview')
|
|
32
|
+
.description('Show usage overview and statistics (non-interactive)')
|
|
33
|
+
.action(() => {
|
|
34
|
+
(0, overview_1.overviewCommand)();
|
|
35
|
+
});
|
|
36
|
+
// Projects command
|
|
37
|
+
program
|
|
38
|
+
.command('projects')
|
|
39
|
+
.description('List all projects with session counts and costs')
|
|
40
|
+
.action(() => {
|
|
41
|
+
(0, projects_1.projectsCommand)();
|
|
42
|
+
});
|
|
43
|
+
// Project command
|
|
44
|
+
program
|
|
45
|
+
.command('project <path>')
|
|
46
|
+
.description('Show sessions for a specific project')
|
|
47
|
+
.action((path) => {
|
|
48
|
+
(0, project_1.projectCommand)(path);
|
|
49
|
+
});
|
|
50
|
+
// Session command
|
|
51
|
+
program
|
|
52
|
+
.command('session <session-id>')
|
|
53
|
+
.description('Show detailed message breakdown for a session')
|
|
54
|
+
.option('--project <path>', 'Project path (encoded or decoded)')
|
|
55
|
+
.action((sessionId, options) => {
|
|
56
|
+
(0, session_1.sessionCommand)(sessionId, options);
|
|
57
|
+
});
|
|
58
|
+
// Config command
|
|
59
|
+
program
|
|
60
|
+
.command('config')
|
|
61
|
+
.description('Show current configuration')
|
|
62
|
+
.action(() => {
|
|
63
|
+
(0, config_2.configCommand)();
|
|
64
|
+
});
|
|
65
|
+
program.parse();
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadStatsCache = loadStatsCache;
|
|
4
|
+
exports.loadHistory = loadHistory;
|
|
5
|
+
exports.loadSessionMessages = loadSessionMessages;
|
|
6
|
+
exports.buildSessionSummary = buildSessionSummary;
|
|
7
|
+
exports.buildProjectSummaries = buildProjectSummaries;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const pricing_1 = require("./pricing");
|
|
12
|
+
function loadStatsCache() {
|
|
13
|
+
const statsPath = (0, path_1.join)((0, config_1.getDataDir)(), 'stats-cache.json');
|
|
14
|
+
if (!(0, fs_1.existsSync)(statsPath)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const raw = JSON.parse((0, fs_1.readFileSync)(statsPath, 'utf-8'));
|
|
19
|
+
// Parse daily activity
|
|
20
|
+
const dailyActivity = (raw.dailyActivity || []).map((d) => ({
|
|
21
|
+
date: d.date,
|
|
22
|
+
messageCount: d.messageCount || 0,
|
|
23
|
+
sessionCount: d.sessionCount || 0,
|
|
24
|
+
toolCallCount: d.toolCallCount || 0,
|
|
25
|
+
}));
|
|
26
|
+
// Parse daily model tokens
|
|
27
|
+
const dailyModelTokens = (raw.dailyModelTokens || []).map((d) => ({
|
|
28
|
+
date: d.date,
|
|
29
|
+
tokensByModel: d.tokensByModel || {},
|
|
30
|
+
}));
|
|
31
|
+
// Parse model stats
|
|
32
|
+
const modelStats = {};
|
|
33
|
+
let totalInputTokens = 0;
|
|
34
|
+
let totalOutputTokens = 0;
|
|
35
|
+
for (const [modelId, usage] of Object.entries(raw.modelUsage || {})) {
|
|
36
|
+
const u = usage;
|
|
37
|
+
const stats = {
|
|
38
|
+
modelId,
|
|
39
|
+
inputTokens: u.inputTokens || 0,
|
|
40
|
+
outputTokens: u.outputTokens || 0,
|
|
41
|
+
cacheCreationInputTokens: u.cacheCreationInputTokens || 0,
|
|
42
|
+
cacheReadInputTokens: u.cacheReadInputTokens || 0,
|
|
43
|
+
};
|
|
44
|
+
modelStats[modelId] = stats;
|
|
45
|
+
totalInputTokens += stats.inputTokens;
|
|
46
|
+
totalOutputTokens += stats.outputTokens;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
totalSessions: raw.totalSessions || 0,
|
|
50
|
+
totalMessages: raw.totalMessages || 0,
|
|
51
|
+
totalInputTokens,
|
|
52
|
+
totalOutputTokens,
|
|
53
|
+
firstSessionDate: raw.firstSessionDate,
|
|
54
|
+
lastComputedDate: raw.lastComputedDate,
|
|
55
|
+
dailyActivity,
|
|
56
|
+
dailyModelTokens,
|
|
57
|
+
modelStats,
|
|
58
|
+
hourCounts: raw.hourCounts || {},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error('Failed to load stats-cache.json:', error);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function loadHistory() {
|
|
67
|
+
const historyPath = (0, path_1.join)((0, config_1.getDataDir)(), 'history.jsonl');
|
|
68
|
+
if (!(0, fs_1.existsSync)(historyPath)) {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
const history = {};
|
|
72
|
+
try {
|
|
73
|
+
const lines = (0, fs_1.readFileSync)(historyPath, 'utf-8').split('\n');
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
if (!line.trim())
|
|
76
|
+
continue;
|
|
77
|
+
try {
|
|
78
|
+
const entry = JSON.parse(line);
|
|
79
|
+
const sessionId = entry.sessionId;
|
|
80
|
+
if (!sessionId)
|
|
81
|
+
continue;
|
|
82
|
+
const display = (entry.display || '').trim();
|
|
83
|
+
// Check for /rename command
|
|
84
|
+
if (display.startsWith('/rename')) {
|
|
85
|
+
const renamed = display.substring(7).trim();
|
|
86
|
+
if (renamed) {
|
|
87
|
+
if (sessionId in history) {
|
|
88
|
+
history[sessionId].display = renamed;
|
|
89
|
+
history[sessionId].renamed = true;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
history[sessionId] = {
|
|
93
|
+
project: entry.project,
|
|
94
|
+
timestamp: entry.timestamp,
|
|
95
|
+
display: renamed,
|
|
96
|
+
renamed: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Regular entry
|
|
103
|
+
if (!(sessionId in history)) {
|
|
104
|
+
history[sessionId] = {
|
|
105
|
+
project: entry.project,
|
|
106
|
+
timestamp: entry.timestamp,
|
|
107
|
+
display,
|
|
108
|
+
renamed: false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
// Skip malformed lines
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.error('Failed to read history.jsonl:', error);
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
return history;
|
|
124
|
+
}
|
|
125
|
+
function parseMessageUsage(usage) {
|
|
126
|
+
return {
|
|
127
|
+
inputTokens: usage.input_tokens || 0,
|
|
128
|
+
outputTokens: usage.output_tokens || 0,
|
|
129
|
+
cacheCreationInputTokens: usage.cache_creation_input_tokens || 0,
|
|
130
|
+
cacheReadInputTokens: usage.cache_read_input_tokens || 0,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function loadSessionMessages(encodedPath, sessionId) {
|
|
134
|
+
const sessionPath = (0, path_1.join)((0, config_1.getDataDir)(), 'projects', encodedPath, `${sessionId}.jsonl`);
|
|
135
|
+
if (!(0, fs_1.existsSync)(sessionPath)) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const messages = [];
|
|
139
|
+
try {
|
|
140
|
+
const lines = (0, fs_1.readFileSync)(sessionPath, 'utf-8').split('\n');
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
if (!line.trim())
|
|
143
|
+
continue;
|
|
144
|
+
try {
|
|
145
|
+
const msg = JSON.parse(line);
|
|
146
|
+
// Skip non-assistant messages
|
|
147
|
+
if (msg.type !== 'assistant')
|
|
148
|
+
continue;
|
|
149
|
+
// Skip synthetic models
|
|
150
|
+
if (msg.message?.model === '<synthetic>')
|
|
151
|
+
continue;
|
|
152
|
+
// Skip sidechain
|
|
153
|
+
if (msg.isSidechain)
|
|
154
|
+
continue;
|
|
155
|
+
const usage = msg.message?.usage;
|
|
156
|
+
if (!usage)
|
|
157
|
+
continue;
|
|
158
|
+
const parsed = parseMessageUsage(usage);
|
|
159
|
+
const model = msg.message?.model || 'unknown';
|
|
160
|
+
const cost = (0, pricing_1.estimateCost)(model, parsed.inputTokens, parsed.outputTokens, parsed.cacheCreationInputTokens, parsed.cacheReadInputTokens);
|
|
161
|
+
messages.push({
|
|
162
|
+
timestamp: msg.timestamp || '',
|
|
163
|
+
model,
|
|
164
|
+
inputTokens: parsed.inputTokens,
|
|
165
|
+
outputTokens: parsed.outputTokens,
|
|
166
|
+
cacheCreationInputTokens: parsed.cacheCreationInputTokens,
|
|
167
|
+
cacheReadInputTokens: parsed.cacheReadInputTokens,
|
|
168
|
+
costUsd: cost,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
// Skip malformed lines
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error(`Failed to read session ${sessionId}:`, error);
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return messages;
|
|
182
|
+
}
|
|
183
|
+
function buildSessionSummary(encodedPath, sessionId, history) {
|
|
184
|
+
const messages = loadSessionMessages(encodedPath, sessionId);
|
|
185
|
+
if (messages.length === 0) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
// Compute totals
|
|
189
|
+
let totalInput = messages.reduce((sum, m) => sum + m.inputTokens, 0);
|
|
190
|
+
let totalOutput = messages.reduce((sum, m) => sum + m.outputTokens, 0);
|
|
191
|
+
let totalCost = messages.reduce((sum, m) => sum + m.costUsd, 0);
|
|
192
|
+
const modelsUsed = new Set(messages.map(m => m.model));
|
|
193
|
+
const firstMsgTimestamp = messages[0]?.timestamp || '';
|
|
194
|
+
const histEntry = history[sessionId] || {};
|
|
195
|
+
const project = histEntry.project || `/${encodedPath}`;
|
|
196
|
+
let slug = histEntry.display?.trim() || '';
|
|
197
|
+
// Handle renamed sessions
|
|
198
|
+
if (histEntry.renamed && slug) {
|
|
199
|
+
slug = slug.substring(0, 100);
|
|
200
|
+
}
|
|
201
|
+
else if (slug) {
|
|
202
|
+
slug = slug.replace(/\n/g, ' ').trim();
|
|
203
|
+
// Skip meta commands
|
|
204
|
+
if (slug.startsWith('/') || slug.startsWith('!') || ['yes', 'Sure', 'go ahead'].includes(slug)) {
|
|
205
|
+
slug = '';
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
slug = slug.substring(0, 80);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!slug) {
|
|
212
|
+
slug = sessionId.substring(0, 8);
|
|
213
|
+
}
|
|
214
|
+
// Merge subagent tokens
|
|
215
|
+
const subagentDir = (0, path_1.join)((0, config_1.getDataDir)(), 'projects', encodedPath, sessionId, 'subagents');
|
|
216
|
+
if ((0, fs_1.existsSync)(subagentDir)) {
|
|
217
|
+
try {
|
|
218
|
+
const agentFiles = (0, fs_1.readdirSync)(subagentDir).filter(f => f.startsWith('agent-') && f.endsWith('.jsonl'));
|
|
219
|
+
for (const agentFile of agentFiles) {
|
|
220
|
+
const agentPath = (0, path_1.join)(subagentDir, agentFile);
|
|
221
|
+
const lines = (0, fs_1.readFileSync)(agentPath, 'utf-8').split('\n');
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
if (!line.trim())
|
|
224
|
+
continue;
|
|
225
|
+
try {
|
|
226
|
+
const msg = JSON.parse(line);
|
|
227
|
+
if (msg.type !== 'assistant')
|
|
228
|
+
continue;
|
|
229
|
+
if (msg.message?.model === '<synthetic>')
|
|
230
|
+
continue;
|
|
231
|
+
const usage = msg.message?.usage;
|
|
232
|
+
if (!usage)
|
|
233
|
+
continue;
|
|
234
|
+
const parsed = parseMessageUsage(usage);
|
|
235
|
+
const model = msg.message?.model || 'unknown';
|
|
236
|
+
const cost = (0, pricing_1.estimateCost)(model, parsed.inputTokens, parsed.outputTokens, parsed.cacheCreationInputTokens, parsed.cacheReadInputTokens);
|
|
237
|
+
totalInput += parsed.inputTokens;
|
|
238
|
+
totalOutput += parsed.outputTokens;
|
|
239
|
+
totalCost += cost;
|
|
240
|
+
modelsUsed.add(model);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
// Ignore subagent errors
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
sessionId,
|
|
254
|
+
slug,
|
|
255
|
+
project,
|
|
256
|
+
timestamp: firstMsgTimestamp,
|
|
257
|
+
messageCount: messages.length,
|
|
258
|
+
totalInputTokens: totalInput,
|
|
259
|
+
totalOutputTokens: totalOutput,
|
|
260
|
+
totalCostUsd: totalCost,
|
|
261
|
+
modelsUsed,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function buildProjectSummaries() {
|
|
265
|
+
const projectsDir = (0, path_1.join)((0, config_1.getDataDir)(), 'projects');
|
|
266
|
+
if (!(0, fs_1.existsSync)(projectsDir)) {
|
|
267
|
+
return {};
|
|
268
|
+
}
|
|
269
|
+
const history = loadHistory();
|
|
270
|
+
const projects = {};
|
|
271
|
+
try {
|
|
272
|
+
const encodedDirs = (0, fs_1.readdirSync)(projectsDir);
|
|
273
|
+
for (const encodedDir of encodedDirs) {
|
|
274
|
+
const dirPath = (0, path_1.join)(projectsDir, encodedDir);
|
|
275
|
+
const stat = require('fs').statSync(dirPath);
|
|
276
|
+
if (!stat.isDirectory())
|
|
277
|
+
continue;
|
|
278
|
+
const sessions = [];
|
|
279
|
+
const files = (0, fs_1.readdirSync)(dirPath);
|
|
280
|
+
for (const file of files) {
|
|
281
|
+
if (!file.endsWith('.jsonl'))
|
|
282
|
+
continue;
|
|
283
|
+
const sessionId = file.replace('.jsonl', '');
|
|
284
|
+
const summary = buildSessionSummary(encodedDir, sessionId, history);
|
|
285
|
+
if (summary) {
|
|
286
|
+
sessions.push(summary);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (sessions.length > 0) {
|
|
290
|
+
projects[encodedDir] = sessions;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
console.error('Failed to build project summaries:', error);
|
|
296
|
+
return {};
|
|
297
|
+
}
|
|
298
|
+
return projects;
|
|
299
|
+
}
|
package/dist/pricing.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getModelPricing = getModelPricing;
|
|
4
|
+
exports.estimateCost = estimateCost;
|
|
5
|
+
exports.formatCost = formatCost;
|
|
6
|
+
exports.formatTokens = formatTokens;
|
|
7
|
+
// Pricing in $/MTok (million tokens)
|
|
8
|
+
const FALLBACK_PRICING = {
|
|
9
|
+
'claude-opus-4-6': {
|
|
10
|
+
input: 15.0,
|
|
11
|
+
output: 75.0,
|
|
12
|
+
cacheWrite: 18.75,
|
|
13
|
+
cacheRead: 1.5,
|
|
14
|
+
},
|
|
15
|
+
'claude-opus-4-5-20251101': {
|
|
16
|
+
input: 15.0,
|
|
17
|
+
output: 75.0,
|
|
18
|
+
cacheWrite: 18.75,
|
|
19
|
+
cacheRead: 1.5,
|
|
20
|
+
},
|
|
21
|
+
'claude-sonnet-4-5-20250929': {
|
|
22
|
+
input: 3.0,
|
|
23
|
+
output: 15.0,
|
|
24
|
+
cacheWrite: 3.75,
|
|
25
|
+
cacheRead: 0.3,
|
|
26
|
+
},
|
|
27
|
+
'claude-haiku-4-5-20251001': {
|
|
28
|
+
input: 0.8,
|
|
29
|
+
output: 4.0,
|
|
30
|
+
cacheWrite: 1.0,
|
|
31
|
+
cacheRead: 0.08,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const DEFAULT_PRICING = FALLBACK_PRICING['claude-sonnet-4-5-20250929'];
|
|
35
|
+
function getModelPricing(modelId) {
|
|
36
|
+
if (modelId in FALLBACK_PRICING) {
|
|
37
|
+
return FALLBACK_PRICING[modelId];
|
|
38
|
+
}
|
|
39
|
+
return DEFAULT_PRICING;
|
|
40
|
+
}
|
|
41
|
+
function estimateCost(modelId, inputTokens = 0, outputTokens = 0, cacheCreationInputTokens = 0, cacheReadInputTokens = 0) {
|
|
42
|
+
const pricing = getModelPricing(modelId);
|
|
43
|
+
const cost = (inputTokens * pricing.input +
|
|
44
|
+
outputTokens * pricing.output +
|
|
45
|
+
cacheCreationInputTokens * pricing.cacheWrite +
|
|
46
|
+
cacheReadInputTokens * pricing.cacheRead) /
|
|
47
|
+
1_000_000;
|
|
48
|
+
return cost;
|
|
49
|
+
}
|
|
50
|
+
function formatCost(cost) {
|
|
51
|
+
return `$${cost.toFixed(4)}`;
|
|
52
|
+
}
|
|
53
|
+
function formatTokens(tokens) {
|
|
54
|
+
return tokens.toLocaleString('en-US');
|
|
55
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-usage-rzp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "View Claude Code API usage directly in your terminal",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-usage": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude",
|
|
17
|
+
"anthropic",
|
|
18
|
+
"usage",
|
|
19
|
+
"cli",
|
|
20
|
+
"tokens",
|
|
21
|
+
"cost"
|
|
22
|
+
],
|
|
23
|
+
"author": "Hamid",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/hamid-miran/claude-usage-cli"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/hamid-miran/claude-usage-cli#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/hamid-miran/claude-usage-cli/issues"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"chalk": "^5.3.0",
|
|
35
|
+
"cli-table3": "^0.6.5",
|
|
36
|
+
"commander": "^12.1.0",
|
|
37
|
+
"inquirer": "^9.2.12"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/inquirer": "^9.0.9",
|
|
41
|
+
"@types/node": "^22.10.2",
|
|
42
|
+
"tsx": "^4.19.2",
|
|
43
|
+
"typescript": "^5.7.2"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getDataDir, hasStatsCache, hasProjects } from '../config';
|
|
3
|
+
|
|
4
|
+
export function configCommand() {
|
|
5
|
+
console.log(chalk.cyan.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
6
|
+
console.log(chalk.cyan.bold(' Configuration'));
|
|
7
|
+
console.log(chalk.cyan.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
8
|
+
|
|
9
|
+
console.log(` Data Directory: ${chalk.green(getDataDir())}`);
|
|
10
|
+
console.log(` Has stats-cache.json: ${hasStatsCache() ? chalk.green('✓') : chalk.red('✗')}`);
|
|
11
|
+
console.log(` Has projects/: ${hasProjects() ? chalk.green('✓') : chalk.red('✗')}`);
|
|
12
|
+
|
|
13
|
+
console.log(
|
|
14
|
+
chalk.dim('\n💡 Set CLAUDE_DATA_DIR environment variable or use --data-dir option\n')
|
|
15
|
+
);
|
|
16
|
+
}
|