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,271 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
|
+
import { loadStatsCache, buildProjectSummaries } from '../loader';
|
|
5
|
+
import { estimateCost, formatCost, formatTokens } from '../pricing';
|
|
6
|
+
import { getDataDir, hasStatsCache } from '../config';
|
|
7
|
+
|
|
8
|
+
async function showSummary() {
|
|
9
|
+
const stats = loadStatsCache();
|
|
10
|
+
if (!stats) return false;
|
|
11
|
+
|
|
12
|
+
// Calculate total cost
|
|
13
|
+
const totalCost = Object.entries(stats.modelStats).reduce((sum, [modelId, model]) => {
|
|
14
|
+
return (
|
|
15
|
+
sum +
|
|
16
|
+
estimateCost(
|
|
17
|
+
modelId,
|
|
18
|
+
model.inputTokens,
|
|
19
|
+
model.outputTokens,
|
|
20
|
+
model.cacheCreationInputTokens,
|
|
21
|
+
model.cacheReadInputTokens
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
}, 0);
|
|
25
|
+
|
|
26
|
+
// Calculate total cache tokens across all models
|
|
27
|
+
let totalCacheWrite = 0;
|
|
28
|
+
let totalCacheRead = 0;
|
|
29
|
+
for (const model of Object.values(stats.modelStats)) {
|
|
30
|
+
totalCacheWrite += model.cacheCreationInputTokens;
|
|
31
|
+
totalCacheRead += model.cacheReadInputTokens;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const totalTokens = stats.totalInputTokens + stats.totalOutputTokens + totalCacheWrite + totalCacheRead;
|
|
35
|
+
|
|
36
|
+
console.log(chalk.bold('\nš Summary:'));
|
|
37
|
+
console.log(` š¬ Total Sessions: ${chalk.green(stats.totalSessions.toLocaleString('en-US'))}`);
|
|
38
|
+
console.log(` šØ Total Messages: ${chalk.green(stats.totalMessages.toLocaleString('en-US'))}`);
|
|
39
|
+
console.log(` š¢ Total Tokens Used: ${chalk.green.bold(formatTokens(totalTokens))}`);
|
|
40
|
+
console.log(` ā¬ļø Total Input Tokens: ${chalk.cyan(formatTokens(stats.totalInputTokens))}`);
|
|
41
|
+
console.log(` ā¬ļø Total Output Tokens: ${chalk.green(formatTokens(stats.totalOutputTokens))}`);
|
|
42
|
+
console.log(` š° Estimated Cost: ${chalk.hex('#FF6B35').bold(formatCost(totalCost))}`);
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function showLast7Days() {
|
|
48
|
+
const stats = loadStatsCache();
|
|
49
|
+
if (!stats) return;
|
|
50
|
+
|
|
51
|
+
const recent = stats.dailyActivity.slice(-7);
|
|
52
|
+
|
|
53
|
+
console.log(chalk.bold('\nš
Recent Activity (Last 7 Days):'));
|
|
54
|
+
|
|
55
|
+
const activityTable = new Table({
|
|
56
|
+
head: [
|
|
57
|
+
chalk.cyan('Date'),
|
|
58
|
+
chalk.white('Sessions'),
|
|
59
|
+
chalk.white('Messages'),
|
|
60
|
+
chalk.white('Tool Calls'),
|
|
61
|
+
],
|
|
62
|
+
colAligns: ['left', 'right', 'right', 'right'],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
for (const day of recent) {
|
|
66
|
+
activityTable.push([
|
|
67
|
+
day.date,
|
|
68
|
+
day.sessionCount.toString(),
|
|
69
|
+
day.messageCount.toString(),
|
|
70
|
+
day.toolCallCount.toString(),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(activityTable.toString());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function showTokensByModel() {
|
|
78
|
+
const stats = loadStatsCache();
|
|
79
|
+
if (!stats) return;
|
|
80
|
+
|
|
81
|
+
console.log(chalk.bold('\nš Token Usage by Model:'));
|
|
82
|
+
|
|
83
|
+
const table = new Table({
|
|
84
|
+
head: [
|
|
85
|
+
chalk.cyan('Model'),
|
|
86
|
+
chalk.blue('Input'),
|
|
87
|
+
chalk.green('Output'),
|
|
88
|
+
chalk.yellow('Cache Write'),
|
|
89
|
+
chalk.magenta('Cache Read'),
|
|
90
|
+
chalk.green.bold('Cost'),
|
|
91
|
+
],
|
|
92
|
+
colAligns: ['left', 'right', 'right', 'right', 'right', 'right'],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
for (const [modelId, model] of Object.entries(stats.modelStats)) {
|
|
96
|
+
const cost = estimateCost(
|
|
97
|
+
modelId,
|
|
98
|
+
model.inputTokens,
|
|
99
|
+
model.outputTokens,
|
|
100
|
+
model.cacheCreationInputTokens,
|
|
101
|
+
model.cacheReadInputTokens
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
let displayName = modelId.replace('claude-', '').replace(/-20\d{6}/, (match) => ` (${match.substring(1)})`);
|
|
105
|
+
|
|
106
|
+
table.push([
|
|
107
|
+
displayName,
|
|
108
|
+
formatTokens(model.inputTokens),
|
|
109
|
+
formatTokens(model.outputTokens),
|
|
110
|
+
formatTokens(model.cacheCreationInputTokens),
|
|
111
|
+
formatTokens(model.cacheReadInputTokens),
|
|
112
|
+
formatCost(cost),
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(table.toString());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function showProjects() {
|
|
120
|
+
const projectSummaries = buildProjectSummaries();
|
|
121
|
+
|
|
122
|
+
if (Object.keys(projectSummaries).length === 0) {
|
|
123
|
+
console.log(chalk.yellow('\nNo projects found'));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface ProjectData {
|
|
128
|
+
path: string;
|
|
129
|
+
encoded: string;
|
|
130
|
+
sessions: number;
|
|
131
|
+
input: number;
|
|
132
|
+
output: number;
|
|
133
|
+
cost: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const projectData: ProjectData[] = [];
|
|
137
|
+
|
|
138
|
+
for (const [encodedPath, sessions] of Object.entries(projectSummaries)) {
|
|
139
|
+
const displayPath = decodeURIComponent(encodedPath);
|
|
140
|
+
const sessionCount = sessions.length;
|
|
141
|
+
const totalInput = sessions.reduce((sum, s) => sum + s.totalInputTokens, 0);
|
|
142
|
+
const totalOutput = sessions.reduce((sum, s) => sum + s.totalOutputTokens, 0);
|
|
143
|
+
const totalCost = sessions.reduce((sum, s) => sum + s.totalCostUsd, 0);
|
|
144
|
+
|
|
145
|
+
projectData.push({
|
|
146
|
+
path: displayPath,
|
|
147
|
+
encoded: encodedPath,
|
|
148
|
+
sessions: sessionCount,
|
|
149
|
+
input: totalInput,
|
|
150
|
+
output: totalOutput,
|
|
151
|
+
cost: totalCost,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
projectData.sort((a, b) => b.cost - a.cost);
|
|
156
|
+
|
|
157
|
+
console.log(chalk.bold(`\nš Projects (${projectData.length} total):`));
|
|
158
|
+
|
|
159
|
+
const table = new Table({
|
|
160
|
+
head: [
|
|
161
|
+
chalk.cyan('Project Path'),
|
|
162
|
+
chalk.white('Sessions'),
|
|
163
|
+
chalk.blue('Input Tokens'),
|
|
164
|
+
chalk.green('Output Tokens'),
|
|
165
|
+
chalk.green.bold('Total Cost'),
|
|
166
|
+
],
|
|
167
|
+
colAligns: ['left', 'right', 'right', 'right', 'right'],
|
|
168
|
+
colWidths: [40, 10, 15, 15, 12],
|
|
169
|
+
wordWrap: true,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
for (const proj of projectData) {
|
|
173
|
+
table.push([
|
|
174
|
+
proj.path,
|
|
175
|
+
proj.sessions.toString(),
|
|
176
|
+
formatTokens(proj.input),
|
|
177
|
+
formatTokens(proj.output),
|
|
178
|
+
formatCost(proj.cost),
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log(table.toString());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function interactiveCommand() {
|
|
186
|
+
if (!hasStatsCache()) {
|
|
187
|
+
console.log(chalk.red(`\nā No stats-cache.json found in ${getDataDir()}`));
|
|
188
|
+
console.log(
|
|
189
|
+
chalk.dim('Make sure CLAUDE_DATA_DIR is set correctly or use --data-dir option.\n')
|
|
190
|
+
);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Clear screen and show header
|
|
195
|
+
console.clear();
|
|
196
|
+
|
|
197
|
+
// ASCII Art Header - Claude orange color
|
|
198
|
+
console.log(chalk.hex('#FF6B35').bold(`
|
|
199
|
+
āāāāāāāāāā āāāāāā āāā āāāāāāāāāā āāāāāāāā
|
|
200
|
+
āāāāāāāāāāā āāāāāāāāāāā āāāāāāāāāāāāāāāāāāā
|
|
201
|
+
āāā āāā āāāāāāāāāāā āāāāāā āāāāāāāāā
|
|
202
|
+
āāā āāā āāāāāāāāāāā āāāāāā āāāāāāāāā
|
|
203
|
+
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
204
|
+
āāāāāāāāāāāāāāāāāā āāā āāāāāāā āāāāāāā āāāāāāāā
|
|
205
|
+
`));
|
|
206
|
+
console.log(chalk.hex('#FF6B35')(' Usage Visualizer') + chalk.dim(' ć» Track your AI costs\n'));
|
|
207
|
+
console.log(chalk.dim('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
208
|
+
|
|
209
|
+
// Show summary first
|
|
210
|
+
const hasData = await showSummary();
|
|
211
|
+
if (!hasData) {
|
|
212
|
+
console.log(chalk.red('\nā Failed to load stats cache\n'));
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Interactive menu loop
|
|
217
|
+
let running = true;
|
|
218
|
+
|
|
219
|
+
while (running) {
|
|
220
|
+
console.log(); // spacing
|
|
221
|
+
|
|
222
|
+
const { action } = await inquirer.prompt([
|
|
223
|
+
{
|
|
224
|
+
type: 'list',
|
|
225
|
+
name: 'action',
|
|
226
|
+
message: 'What would you like to see?',
|
|
227
|
+
choices: [
|
|
228
|
+
{ name: 'š
Last 7 Days Activity', value: 'last7days' },
|
|
229
|
+
{ name: 'š Token Usage by Model', value: 'tokens' },
|
|
230
|
+
{ name: 'š All Projects', value: 'projects' },
|
|
231
|
+
new inquirer.Separator(),
|
|
232
|
+
{ name: 'š Refresh Data', value: 'refresh' },
|
|
233
|
+
{ name: 'ā Exit', value: 'exit' },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
console.log(); // spacing
|
|
239
|
+
|
|
240
|
+
switch (action) {
|
|
241
|
+
case 'last7days':
|
|
242
|
+
await showLast7Days();
|
|
243
|
+
break;
|
|
244
|
+
case 'tokens':
|
|
245
|
+
await showTokensByModel();
|
|
246
|
+
break;
|
|
247
|
+
case 'projects':
|
|
248
|
+
await showProjects();
|
|
249
|
+
break;
|
|
250
|
+
case 'refresh':
|
|
251
|
+
console.clear();
|
|
252
|
+
// ASCII Art Header - Claude orange color
|
|
253
|
+
console.log(chalk.hex('#FF6B35').bold(`
|
|
254
|
+
āāāāāāāāāā āāāāāā āāā āāāāāāāāāā āāāāāāāā
|
|
255
|
+
āāāāāāāāāāā āāāāāāāāāāā āāāāāāāāāāāāāāāāāāā
|
|
256
|
+
āāā āāā āāāāāāāāāāā āāāāāā āāāāāāāāā
|
|
257
|
+
āāā āāā āāāāāāāāāāā āāāāāā āāāāāāāāā
|
|
258
|
+
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
259
|
+
āāāāāāāāāāāāāāāāāā āāā āāāāāāā āāāāāāā āāāāāāāā
|
|
260
|
+
`));
|
|
261
|
+
console.log(chalk.hex('#FF6B35')(' Usage Visualizer') + chalk.dim(' ć» Track your AI costs\n'));
|
|
262
|
+
console.log(chalk.dim('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
263
|
+
await showSummary();
|
|
264
|
+
break;
|
|
265
|
+
case 'exit':
|
|
266
|
+
console.log(chalk.dim('\nHamid said Goodbye! š\n'));
|
|
267
|
+
running = false;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { loadStatsCache } from '../loader';
|
|
4
|
+
import { estimateCost, formatCost, formatTokens } from '../pricing';
|
|
5
|
+
import { getDataDir, hasStatsCache } from '../config';
|
|
6
|
+
|
|
7
|
+
export function overviewCommand() {
|
|
8
|
+
if (!hasStatsCache()) {
|
|
9
|
+
console.log(chalk.red(`ā No stats-cache.json found in ${getDataDir()}`));
|
|
10
|
+
console.log(
|
|
11
|
+
chalk.dim('\nMake sure CLAUDE_DATA_DIR is set correctly or use --data-dir option.')
|
|
12
|
+
);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const stats = loadStatsCache();
|
|
17
|
+
|
|
18
|
+
if (!stats) {
|
|
19
|
+
console.log(chalk.red('ā Failed to load stats cache'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Header
|
|
24
|
+
console.log(chalk.cyan.bold('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
25
|
+
console.log(chalk.cyan.bold(' Claude Code Usage Overview'));
|
|
26
|
+
console.log(chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
27
|
+
|
|
28
|
+
// Calculate total cost
|
|
29
|
+
const totalCost = Object.entries(stats.modelStats).reduce((sum, [modelId, model]) => {
|
|
30
|
+
return (
|
|
31
|
+
sum +
|
|
32
|
+
estimateCost(
|
|
33
|
+
modelId,
|
|
34
|
+
model.inputTokens,
|
|
35
|
+
model.outputTokens,
|
|
36
|
+
model.cacheCreationInputTokens,
|
|
37
|
+
model.cacheReadInputTokens
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
}, 0);
|
|
41
|
+
|
|
42
|
+
// Summary stats
|
|
43
|
+
console.log(chalk.bold('Summary:'));
|
|
44
|
+
console.log(` Total Sessions: ${chalk.green(stats.totalSessions.toLocaleString('en-US'))}`);
|
|
45
|
+
console.log(` Total Messages: ${chalk.green(stats.totalMessages.toLocaleString('en-US'))}`);
|
|
46
|
+
console.log(` Total Input Tokens: ${chalk.blue(formatTokens(stats.totalInputTokens))}`);
|
|
47
|
+
console.log(` Total Output Tokens: ${chalk.green(formatTokens(stats.totalOutputTokens))}`);
|
|
48
|
+
console.log(` Estimated Cost: ${chalk.green.bold(formatCost(totalCost))}`);
|
|
49
|
+
|
|
50
|
+
if (stats.firstSessionDate) {
|
|
51
|
+
console.log(` First Session: ${chalk.dim(stats.firstSessionDate)}`);
|
|
52
|
+
}
|
|
53
|
+
if (stats.lastComputedDate) {
|
|
54
|
+
console.log(` Last Computed: ${chalk.dim(stats.lastComputedDate)}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Token breakdown by model
|
|
58
|
+
if (Object.keys(stats.modelStats).length > 0) {
|
|
59
|
+
console.log(chalk.bold('\nToken Usage by Model:'));
|
|
60
|
+
|
|
61
|
+
const table = new Table({
|
|
62
|
+
head: [
|
|
63
|
+
chalk.cyan('Model'),
|
|
64
|
+
chalk.blue('Input'),
|
|
65
|
+
chalk.green('Output'),
|
|
66
|
+
chalk.yellow('Cache Write'),
|
|
67
|
+
chalk.magenta('Cache Read'),
|
|
68
|
+
chalk.green.bold('Cost'),
|
|
69
|
+
],
|
|
70
|
+
colAligns: ['left', 'right', 'right', 'right', 'right', 'right'],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
for (const [modelId, model] of Object.entries(stats.modelStats)) {
|
|
74
|
+
const cost = estimateCost(
|
|
75
|
+
modelId,
|
|
76
|
+
model.inputTokens,
|
|
77
|
+
model.outputTokens,
|
|
78
|
+
model.cacheCreationInputTokens,
|
|
79
|
+
model.cacheReadInputTokens
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Simplify model name
|
|
83
|
+
let displayName = modelId.replace('claude-', '').replace(/-20\d{6}/, (match) => ` (${match.substring(1)})`);
|
|
84
|
+
|
|
85
|
+
table.push([
|
|
86
|
+
displayName,
|
|
87
|
+
formatTokens(model.inputTokens),
|
|
88
|
+
formatTokens(model.outputTokens),
|
|
89
|
+
formatTokens(model.cacheCreationInputTokens),
|
|
90
|
+
formatTokens(model.cacheReadInputTokens),
|
|
91
|
+
formatCost(cost),
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(table.toString());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Recent activity
|
|
99
|
+
if (stats.dailyActivity.length > 0) {
|
|
100
|
+
const recent = stats.dailyActivity.slice(-7); // Last 7 days
|
|
101
|
+
console.log(chalk.bold('\nRecent Activity (Last 7 Days):'));
|
|
102
|
+
|
|
103
|
+
const activityTable = new Table({
|
|
104
|
+
head: [
|
|
105
|
+
chalk.cyan('Date'),
|
|
106
|
+
chalk.white('Sessions'),
|
|
107
|
+
chalk.white('Messages'),
|
|
108
|
+
chalk.white('Tool Calls'),
|
|
109
|
+
],
|
|
110
|
+
colAligns: ['left', 'right', 'right', 'right'],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
for (const day of recent) {
|
|
114
|
+
activityTable.push([
|
|
115
|
+
day.date,
|
|
116
|
+
day.sessionCount.toString(),
|
|
117
|
+
day.messageCount.toString(),
|
|
118
|
+
day.toolCallCount.toString(),
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(activityTable.toString());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(chalk.dim('\nš” Tip: Use "claude-usage projects" to see all projects\n'));
|
|
126
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { buildProjectSummaries } from '../loader';
|
|
4
|
+
import { formatCost, formatTokens } from '../pricing';
|
|
5
|
+
import { SessionSummary } from '../types';
|
|
6
|
+
|
|
7
|
+
export function projectCommand(projectPath: string) {
|
|
8
|
+
const projectSummaries = buildProjectSummaries();
|
|
9
|
+
|
|
10
|
+
// Try to find the project (handle both encoded and unencoded paths)
|
|
11
|
+
const encodedPath = encodeURIComponent(projectPath);
|
|
12
|
+
let sessions: SessionSummary[] | undefined;
|
|
13
|
+
let foundPath = '';
|
|
14
|
+
|
|
15
|
+
if (encodedPath in projectSummaries) {
|
|
16
|
+
sessions = projectSummaries[encodedPath];
|
|
17
|
+
foundPath = encodedPath;
|
|
18
|
+
} else {
|
|
19
|
+
// Try to find by partial match
|
|
20
|
+
for (const [path, sess] of Object.entries(projectSummaries)) {
|
|
21
|
+
if (projectPath.includes(decodeURIComponent(path)) || decodeURIComponent(path).includes(projectPath)) {
|
|
22
|
+
sessions = sess;
|
|
23
|
+
foundPath = path;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!sessions) {
|
|
30
|
+
console.log(chalk.red(`ā Project not found: ${projectPath}`));
|
|
31
|
+
console.log(chalk.dim('\nAvailable projects:'));
|
|
32
|
+
for (const path of Object.keys(projectSummaries)) {
|
|
33
|
+
console.log(` - ${decodeURIComponent(path)}`);
|
|
34
|
+
}
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Sort sessions by timestamp descending
|
|
39
|
+
sessions = sessions.slice().sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
40
|
+
|
|
41
|
+
console.log(chalk.cyan.bold('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
42
|
+
console.log(chalk.cyan.bold(` Project: ${decodeURIComponent(foundPath)}`));
|
|
43
|
+
console.log(chalk.dim(` ${sessions.length} sessions`));
|
|
44
|
+
console.log(chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
45
|
+
|
|
46
|
+
const table = new Table({
|
|
47
|
+
head: [
|
|
48
|
+
chalk.cyan('Session'),
|
|
49
|
+
chalk.dim('Timestamp'),
|
|
50
|
+
chalk.white('Messages'),
|
|
51
|
+
chalk.blue('Input'),
|
|
52
|
+
chalk.green('Output'),
|
|
53
|
+
chalk.green.bold('Cost'),
|
|
54
|
+
chalk.yellow('Models'),
|
|
55
|
+
],
|
|
56
|
+
colAligns: ['left', 'left', 'right', 'right', 'right', 'right', 'left'],
|
|
57
|
+
colWidths: [30, 20, 10, 12, 12, 10, 20],
|
|
58
|
+
wordWrap: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
for (const session of sessions) {
|
|
62
|
+
// Truncate slug if too long
|
|
63
|
+
const slugDisplay = session.slug.length > 60 ? session.slug.substring(0, 57) + '...' : session.slug;
|
|
64
|
+
|
|
65
|
+
// Format timestamp
|
|
66
|
+
const timestamp = session.timestamp ? new Date(session.timestamp).toLocaleString('en-US') : 'N/A';
|
|
67
|
+
|
|
68
|
+
table.push([
|
|
69
|
+
slugDisplay,
|
|
70
|
+
timestamp,
|
|
71
|
+
session.messageCount.toString(),
|
|
72
|
+
formatTokens(session.totalInputTokens),
|
|
73
|
+
formatTokens(session.totalOutputTokens),
|
|
74
|
+
formatCost(session.totalCostUsd),
|
|
75
|
+
Array.from(session.modelsUsed)
|
|
76
|
+
.map(m => m.replace('claude-', ''))
|
|
77
|
+
.join(', '),
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(table.toString());
|
|
82
|
+
console.log(
|
|
83
|
+
chalk.dim('\nš” Tip: Use "claude-usage session <session-id>" to see message details\n')
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { buildProjectSummaries } from '../loader';
|
|
4
|
+
import { formatCost, formatTokens } from '../pricing';
|
|
5
|
+
import { hasProjects, getDataDir } from '../config';
|
|
6
|
+
|
|
7
|
+
export function projectsCommand() {
|
|
8
|
+
if (!hasProjects()) {
|
|
9
|
+
console.log(chalk.red(`ā No projects directory found in ${getDataDir()}`));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const projectSummaries = buildProjectSummaries();
|
|
14
|
+
|
|
15
|
+
if (Object.keys(projectSummaries).length === 0) {
|
|
16
|
+
console.log(chalk.yellow('No projects found'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Build project-level aggregates
|
|
21
|
+
interface ProjectData {
|
|
22
|
+
path: string;
|
|
23
|
+
encoded: string;
|
|
24
|
+
sessions: number;
|
|
25
|
+
input: number;
|
|
26
|
+
output: number;
|
|
27
|
+
cost: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const projectData: ProjectData[] = [];
|
|
31
|
+
|
|
32
|
+
for (const [encodedPath, sessions] of Object.entries(projectSummaries)) {
|
|
33
|
+
const displayPath = decodeURIComponent(encodedPath);
|
|
34
|
+
const sessionCount = sessions.length;
|
|
35
|
+
const totalInput = sessions.reduce((sum, s) => sum + s.totalInputTokens, 0);
|
|
36
|
+
const totalOutput = sessions.reduce((sum, s) => sum + s.totalOutputTokens, 0);
|
|
37
|
+
const totalCost = sessions.reduce((sum, s) => sum + s.totalCostUsd, 0);
|
|
38
|
+
|
|
39
|
+
projectData.push({
|
|
40
|
+
path: displayPath,
|
|
41
|
+
encoded: encodedPath,
|
|
42
|
+
sessions: sessionCount,
|
|
43
|
+
input: totalInput,
|
|
44
|
+
output: totalOutput,
|
|
45
|
+
cost: totalCost,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Sort by cost descending
|
|
50
|
+
projectData.sort((a, b) => b.cost - a.cost);
|
|
51
|
+
|
|
52
|
+
console.log(chalk.cyan.bold('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
53
|
+
console.log(chalk.cyan.bold(` Projects (${projectData.length} total)`));
|
|
54
|
+
console.log(chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
55
|
+
|
|
56
|
+
const table = new Table({
|
|
57
|
+
head: [
|
|
58
|
+
chalk.cyan('Project Path'),
|
|
59
|
+
chalk.white('Sessions'),
|
|
60
|
+
chalk.blue('Input Tokens'),
|
|
61
|
+
chalk.green('Output Tokens'),
|
|
62
|
+
chalk.green.bold('Total Cost'),
|
|
63
|
+
],
|
|
64
|
+
colAligns: ['left', 'right', 'right', 'right', 'right'],
|
|
65
|
+
colWidths: [40, 10, 15, 15, 12],
|
|
66
|
+
wordWrap: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
for (const proj of projectData) {
|
|
70
|
+
table.push([
|
|
71
|
+
proj.path,
|
|
72
|
+
proj.sessions.toString(),
|
|
73
|
+
formatTokens(proj.input),
|
|
74
|
+
formatTokens(proj.output),
|
|
75
|
+
formatCost(proj.cost),
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(table.toString());
|
|
80
|
+
console.log(
|
|
81
|
+
chalk.dim('\nš” Tip: Use "claude-usage project <path>" to see sessions in a project\n')
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { buildProjectSummaries, loadSessionMessages } from '../loader';
|
|
4
|
+
import { formatCost, formatTokens } from '../pricing';
|
|
5
|
+
|
|
6
|
+
export function sessionCommand(sessionId: string, options: { project?: string }) {
|
|
7
|
+
const projectSummaries = buildProjectSummaries();
|
|
8
|
+
|
|
9
|
+
let foundSession: any = null;
|
|
10
|
+
let foundProject = '';
|
|
11
|
+
|
|
12
|
+
if (options.project) {
|
|
13
|
+
// User specified project
|
|
14
|
+
const encoded = encodeURIComponent(options.project);
|
|
15
|
+
if (encoded in projectSummaries) {
|
|
16
|
+
for (const sess of projectSummaries[encoded]) {
|
|
17
|
+
if (sess.sessionId === sessionId || sess.sessionId.startsWith(sessionId)) {
|
|
18
|
+
foundSession = sess;
|
|
19
|
+
foundProject = encoded;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
// Search all projects
|
|
26
|
+
for (const [encodedPath, sessions] of Object.entries(projectSummaries)) {
|
|
27
|
+
for (const sess of sessions) {
|
|
28
|
+
if (sess.sessionId === sessionId || sess.sessionId.startsWith(sessionId)) {
|
|
29
|
+
foundSession = sess;
|
|
30
|
+
foundProject = encodedPath;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (foundSession) break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!foundSession) {
|
|
39
|
+
console.log(chalk.red(`ā Session not found: ${sessionId}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Load messages
|
|
44
|
+
const messages = loadSessionMessages(foundProject, foundSession.sessionId);
|
|
45
|
+
|
|
46
|
+
if (messages.length === 0) {
|
|
47
|
+
console.log(chalk.yellow('No messages found in this session'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(chalk.cyan.bold('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
52
|
+
console.log(chalk.cyan.bold(` Session: ${foundSession.slug}`));
|
|
53
|
+
console.log(chalk.dim(` Project: ${decodeURIComponent(foundProject)}`));
|
|
54
|
+
console.log(chalk.dim(` ID: ${foundSession.sessionId}`));
|
|
55
|
+
console.log(chalk.dim(` ${messages.length} messages`));
|
|
56
|
+
console.log(chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
57
|
+
|
|
58
|
+
// Summary
|
|
59
|
+
console.log(chalk.bold('Summary:'));
|
|
60
|
+
console.log(` Total Input Tokens: ${chalk.blue(formatTokens(foundSession.totalInputTokens))}`);
|
|
61
|
+
console.log(` Total Output Tokens: ${chalk.green(formatTokens(foundSession.totalOutputTokens))}`);
|
|
62
|
+
console.log(` Total Cost: ${chalk.green.bold(formatCost(foundSession.totalCostUsd))}`);
|
|
63
|
+
console.log(` Models Used: ${chalk.yellow(Array.from(foundSession.modelsUsed).join(', '))}`);
|
|
64
|
+
|
|
65
|
+
// Message breakdown
|
|
66
|
+
console.log(chalk.bold('\nMessage Details:'));
|
|
67
|
+
|
|
68
|
+
const table = new Table({
|
|
69
|
+
head: [
|
|
70
|
+
chalk.dim('#'),
|
|
71
|
+
chalk.dim('Timestamp'),
|
|
72
|
+
chalk.cyan('Model'),
|
|
73
|
+
chalk.blue('Input'),
|
|
74
|
+
chalk.green('Output'),
|
|
75
|
+
chalk.yellow('Cache Write'),
|
|
76
|
+
chalk.magenta('Cache Read'),
|
|
77
|
+
chalk.green.bold('Cost'),
|
|
78
|
+
],
|
|
79
|
+
colAligns: ['right', 'left', 'left', 'right', 'right', 'right', 'right', 'right'],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < messages.length; i++) {
|
|
83
|
+
const msg = messages[i];
|
|
84
|
+
|
|
85
|
+
// Simplify model name
|
|
86
|
+
let modelDisplay = msg.model.replace('claude-', '').replace(/-20\d{6}/, (match) => ` (${match.substring(1)})`);
|
|
87
|
+
|
|
88
|
+
// Format timestamp
|
|
89
|
+
const timestamp = msg.timestamp ? new Date(msg.timestamp).toLocaleString('en-US') : 'N/A';
|
|
90
|
+
|
|
91
|
+
table.push([
|
|
92
|
+
(i + 1).toString(),
|
|
93
|
+
timestamp,
|
|
94
|
+
modelDisplay,
|
|
95
|
+
formatTokens(msg.inputTokens),
|
|
96
|
+
formatTokens(msg.outputTokens),
|
|
97
|
+
formatTokens(msg.cacheCreationInputTokens),
|
|
98
|
+
formatTokens(msg.cacheReadInputTokens),
|
|
99
|
+
formatCost(msg.costUsd),
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(table.toString());
|
|
104
|
+
console.log();
|
|
105
|
+
}
|