claude-cost-cli 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/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +448 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 claude-cost-cli contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# claude-cost-cli
|
|
2
|
+
|
|
3
|
+
CLI for Claude API usage and cost reports. Uses the Anthropic Admin API ([Usage & Cost API](https://platform.claude.com/docs/en/build-with-claude/usage-cost-api)).
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- macOS (uses Keychain for credential storage)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g claude-cost-cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or run without installing:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx claude-cost-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
### config set-key
|
|
25
|
+
|
|
26
|
+
Store your Admin API key in macOS Keychain. Requires an Admin API key (starts with `sk-ant-admin`) from Claude Console → Settings → Admin Keys.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
claude-cost config set-key
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### config show
|
|
33
|
+
|
|
34
|
+
Display masked API key.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
claude-cost config show
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### config remove-key
|
|
41
|
+
|
|
42
|
+
Remove API key from Keychain.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
claude-cost config remove-key
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### usage
|
|
49
|
+
|
|
50
|
+
Retrieve usage report with token counts.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
claude-cost usage
|
|
54
|
+
claude-cost usage --period 30d --model claude-opus-4
|
|
55
|
+
claude-cost usage --from 2025-01-01 --to 2025-01-31 --json
|
|
56
|
+
claude-cost usage --api-keys apikey_xxx --group-by model,api_key_id
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Options: `--from`, `--to`, `--period` (7d/30d/90d), `--model`, `--api-keys`, `--group-by`, `--bucket` (1d/1h/1m), `--json`
|
|
60
|
+
|
|
61
|
+
### cost
|
|
62
|
+
|
|
63
|
+
Retrieve cost report in USD.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
claude-cost cost
|
|
67
|
+
claude-cost cost --period 30d --group-by workspace_id,description
|
|
68
|
+
claude-cost cost --sum
|
|
69
|
+
claude-cost cost --json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Options: `--from`, `--to`, `--period`, `--group-by`, `--sum` (output total only), `--json`
|
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import Table from "cli-table3";
|
|
7
|
+
|
|
8
|
+
//#region src/infrastructure/anthropic-cost-repository.ts
|
|
9
|
+
const BASE_URL$1 = "https://api.anthropic.com";
|
|
10
|
+
const COST_ENDPOINT = "/v1/organizations/cost_report";
|
|
11
|
+
function mapToCostRecord(bucket, result) {
|
|
12
|
+
const amountCents = parseFloat(result.amount);
|
|
13
|
+
const amountDollars = Number.isNaN(amountCents) ? 0 : amountCents / 100;
|
|
14
|
+
return {
|
|
15
|
+
date: bucket.starting_at,
|
|
16
|
+
description: result.description ?? null,
|
|
17
|
+
model: result.model ?? null,
|
|
18
|
+
amountDollars,
|
|
19
|
+
currency: result.currency ?? "USD",
|
|
20
|
+
tokenType: result.token_type ?? null,
|
|
21
|
+
costType: result.cost_type ?? null,
|
|
22
|
+
serviceTier: result.service_tier ?? null
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createAnthropicCostRepository(getApiKey) {
|
|
26
|
+
return { async query(params) {
|
|
27
|
+
const apiKey = await getApiKey();
|
|
28
|
+
const allRecords = [];
|
|
29
|
+
let page;
|
|
30
|
+
do {
|
|
31
|
+
const searchParams = new URLSearchParams();
|
|
32
|
+
searchParams.set("starting_at", params.dateRange.startingAt);
|
|
33
|
+
searchParams.set("ending_at", params.dateRange.endingAt);
|
|
34
|
+
searchParams.set("bucket_width", "1d");
|
|
35
|
+
if (params.groupBy?.length) for (const g of params.groupBy) searchParams.append("group_by[]", g);
|
|
36
|
+
if (page) searchParams.set("page", page);
|
|
37
|
+
const url = `${BASE_URL$1}${COST_ENDPOINT}?${searchParams}`;
|
|
38
|
+
const res = await fetch(url, { headers: {
|
|
39
|
+
"anthropic-version": "2023-06-01",
|
|
40
|
+
"x-api-key": apiKey
|
|
41
|
+
} });
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const body = await res.text();
|
|
44
|
+
throw new Error(`Cost API error (${res.status}): ${body || res.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
const json = await res.json();
|
|
47
|
+
for (const bucket of json.data) for (const result of bucket.results) allRecords.push(mapToCostRecord(bucket, result));
|
|
48
|
+
page = json.has_more ? json.next_page : void 0;
|
|
49
|
+
} while (page);
|
|
50
|
+
return allRecords;
|
|
51
|
+
} };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/infrastructure/anthropic-usage-repository.ts
|
|
56
|
+
const BASE_URL = "https://api.anthropic.com";
|
|
57
|
+
const USAGE_ENDPOINT = "/v1/organizations/usage_report/messages";
|
|
58
|
+
function mapToUsageRecord(bucket, result) {
|
|
59
|
+
const cacheCreation = result.cache_creation ?? {};
|
|
60
|
+
const cacheCreationTokens = (cacheCreation.ephemeral_1h_input_tokens ?? 0) + (cacheCreation.ephemeral_5m_input_tokens ?? 0);
|
|
61
|
+
return {
|
|
62
|
+
date: bucket.starting_at,
|
|
63
|
+
model: result.model ?? null,
|
|
64
|
+
uncachedInputTokens: result.uncached_input_tokens,
|
|
65
|
+
cachedInputTokens: result.cache_read_input_tokens,
|
|
66
|
+
cacheCreationTokens,
|
|
67
|
+
outputTokens: result.output_tokens,
|
|
68
|
+
webSearchRequests: result.server_tool_use?.web_search_requests ?? 0,
|
|
69
|
+
apiKeyId: result.api_key_id ?? null,
|
|
70
|
+
workspaceId: result.workspace_id ?? null,
|
|
71
|
+
serviceTier: result.service_tier ?? null
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function createAnthropicUsageRepository(getApiKey) {
|
|
75
|
+
return { async query(params) {
|
|
76
|
+
const apiKey = await getApiKey();
|
|
77
|
+
const allRecords = [];
|
|
78
|
+
let page;
|
|
79
|
+
do {
|
|
80
|
+
const searchParams = new URLSearchParams();
|
|
81
|
+
searchParams.set("starting_at", params.dateRange.startingAt);
|
|
82
|
+
searchParams.set("ending_at", params.dateRange.endingAt);
|
|
83
|
+
searchParams.set("bucket_width", params.bucketWidth ?? "1d");
|
|
84
|
+
if (params.models?.length) for (const m of params.models) searchParams.append("models[]", m);
|
|
85
|
+
if (params.apiKeyIds?.length) for (const id of params.apiKeyIds) searchParams.append("api_key_ids[]", id);
|
|
86
|
+
if (params.groupBy?.length) for (const g of params.groupBy) searchParams.append("group_by[]", g);
|
|
87
|
+
if (page) searchParams.set("page", page);
|
|
88
|
+
const url = `${BASE_URL}${USAGE_ENDPOINT}?${searchParams}`;
|
|
89
|
+
const res = await fetch(url, { headers: {
|
|
90
|
+
"anthropic-version": "2023-06-01",
|
|
91
|
+
"x-api-key": apiKey
|
|
92
|
+
} });
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const body = await res.text();
|
|
95
|
+
throw new Error(`Usage API error (${res.status}): ${body || res.statusText}`);
|
|
96
|
+
}
|
|
97
|
+
const json = await res.json();
|
|
98
|
+
for (const bucket of json.data) for (const result of bucket.results) allRecords.push(mapToUsageRecord(bucket, result));
|
|
99
|
+
page = json.has_more ? json.next_page : void 0;
|
|
100
|
+
} while (page);
|
|
101
|
+
return allRecords;
|
|
102
|
+
} };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/infrastructure/json-presenter.ts
|
|
107
|
+
function createJsonPresenter() {
|
|
108
|
+
return {
|
|
109
|
+
present(records) {
|
|
110
|
+
console.log(JSON.stringify(records, null, 2));
|
|
111
|
+
},
|
|
112
|
+
presentSum(total, currency) {
|
|
113
|
+
console.log(JSON.stringify({
|
|
114
|
+
total,
|
|
115
|
+
currency
|
|
116
|
+
}, null, 2));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/infrastructure/keychain-credential-store.ts
|
|
123
|
+
const execFileAsync = promisify(execFile);
|
|
124
|
+
const SERVICE = "claude-cost-cli";
|
|
125
|
+
const ACCOUNT = "claude-cost-cli";
|
|
126
|
+
function createKeychainCredentialStore() {
|
|
127
|
+
return {
|
|
128
|
+
async save(credential) {
|
|
129
|
+
try {
|
|
130
|
+
await execFileAsync("security", [
|
|
131
|
+
"add-generic-password",
|
|
132
|
+
"-a",
|
|
133
|
+
ACCOUNT,
|
|
134
|
+
"-s",
|
|
135
|
+
SERVICE,
|
|
136
|
+
"-w",
|
|
137
|
+
credential,
|
|
138
|
+
"-U"
|
|
139
|
+
]);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
142
|
+
if (message.includes("already exists")) {
|
|
143
|
+
await execFileAsync("security", [
|
|
144
|
+
"delete-generic-password",
|
|
145
|
+
"-a",
|
|
146
|
+
ACCOUNT,
|
|
147
|
+
"-s",
|
|
148
|
+
SERVICE
|
|
149
|
+
]);
|
|
150
|
+
await this.save(credential);
|
|
151
|
+
} else throw new Error(`Failed to store credential in Keychain: ${message}`);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
async load() {
|
|
155
|
+
try {
|
|
156
|
+
const { stdout } = await execFileAsync("security", [
|
|
157
|
+
"find-generic-password",
|
|
158
|
+
"-a",
|
|
159
|
+
ACCOUNT,
|
|
160
|
+
"-s",
|
|
161
|
+
SERVICE,
|
|
162
|
+
"-w"
|
|
163
|
+
]);
|
|
164
|
+
return stdout.trim();
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
167
|
+
if (message.includes("could not be found") || message.includes("The specified item could not be found")) throw new Error("No API key stored. Run: claude-cost config set-key");
|
|
168
|
+
throw new Error(`Failed to retrieve credential from Keychain: ${message}`);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
async clear() {
|
|
172
|
+
try {
|
|
173
|
+
await execFileAsync("security", [
|
|
174
|
+
"delete-generic-password",
|
|
175
|
+
"-a",
|
|
176
|
+
ACCOUNT,
|
|
177
|
+
"-s",
|
|
178
|
+
SERVICE
|
|
179
|
+
]);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
182
|
+
if (!message.includes("could not be found") && !message.includes("The specified item could not be found")) throw new Error(`Failed to remove credential from Keychain: ${message}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/infrastructure/table-presenter.ts
|
|
190
|
+
function formatDate(iso) {
|
|
191
|
+
return new Date(iso).toISOString().slice(0, 10);
|
|
192
|
+
}
|
|
193
|
+
function createTablePresenter() {
|
|
194
|
+
return {
|
|
195
|
+
present(records) {
|
|
196
|
+
if (records.length === 0) {
|
|
197
|
+
console.log("No data found for the specified period.");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if ("uncachedInputTokens" in records[0]) presentUsage(records);
|
|
201
|
+
else presentCost(records);
|
|
202
|
+
},
|
|
203
|
+
presentSum(total, currency) {
|
|
204
|
+
console.log(`Total: $${total.toFixed(2)} ${currency}`);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function presentUsage(records) {
|
|
209
|
+
const table = new Table({ head: [
|
|
210
|
+
chalk.green("Date"),
|
|
211
|
+
chalk.green("Model"),
|
|
212
|
+
chalk.green("Input Tokens"),
|
|
213
|
+
chalk.green("Cached Tokens"),
|
|
214
|
+
chalk.green("Output Tokens"),
|
|
215
|
+
chalk.green("Web Searches")
|
|
216
|
+
] });
|
|
217
|
+
for (const r of records) table.push([
|
|
218
|
+
chalk.dim(formatDate(r.date)),
|
|
219
|
+
r.model ?? "-",
|
|
220
|
+
String(r.uncachedInputTokens + r.cacheCreationTokens),
|
|
221
|
+
String(r.cachedInputTokens),
|
|
222
|
+
String(r.outputTokens),
|
|
223
|
+
String(r.webSearchRequests)
|
|
224
|
+
]);
|
|
225
|
+
console.log(table.toString());
|
|
226
|
+
}
|
|
227
|
+
function presentCost(records) {
|
|
228
|
+
const table = new Table({ head: [
|
|
229
|
+
chalk.green("Date"),
|
|
230
|
+
chalk.green("Description"),
|
|
231
|
+
chalk.green("Model"),
|
|
232
|
+
chalk.green("Amount (USD)"),
|
|
233
|
+
chalk.green("Token Type"),
|
|
234
|
+
chalk.green("Tier")
|
|
235
|
+
] });
|
|
236
|
+
for (const r of records) table.push([
|
|
237
|
+
chalk.dim(formatDate(r.date)),
|
|
238
|
+
(r.description ?? "-").slice(0, 40),
|
|
239
|
+
r.model ?? "-",
|
|
240
|
+
r.amountDollars.toFixed(4),
|
|
241
|
+
r.tokenType ?? "-",
|
|
242
|
+
r.serviceTier ?? "-"
|
|
243
|
+
]);
|
|
244
|
+
console.log(table.toString());
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
//#endregion
|
|
248
|
+
//#region src/application/use-cases/remove-api-key.use-case.ts
|
|
249
|
+
function createRemoveApiKeyUseCase(credentialStore) {
|
|
250
|
+
return async () => {
|
|
251
|
+
await credentialStore.clear();
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/application/date-range.helper.ts
|
|
257
|
+
const ADMIN_KEY_PREFIX = "sk-ant-admin";
|
|
258
|
+
function parseDateRange(period, from, to) {
|
|
259
|
+
const now = /* @__PURE__ */ new Date();
|
|
260
|
+
let startingAt;
|
|
261
|
+
let endingAt;
|
|
262
|
+
if (from && to) {
|
|
263
|
+
startingAt = new Date(from);
|
|
264
|
+
endingAt = new Date(to);
|
|
265
|
+
} else if (period) {
|
|
266
|
+
const days = parsePeriod(period);
|
|
267
|
+
endingAt = now;
|
|
268
|
+
startingAt = new Date(now);
|
|
269
|
+
startingAt.setDate(startingAt.getDate() - days);
|
|
270
|
+
} else {
|
|
271
|
+
endingAt = now;
|
|
272
|
+
startingAt = new Date(now);
|
|
273
|
+
startingAt.setDate(startingAt.getDate() - 7);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
startingAt: startingAt.toISOString(),
|
|
277
|
+
endingAt: endingAt.toISOString()
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function parsePeriod(period) {
|
|
281
|
+
const match = period.match(/^(\d+)(d|days?)$/i);
|
|
282
|
+
if (!match) return 7;
|
|
283
|
+
const value = parseInt(match[1], 10);
|
|
284
|
+
return Number.isNaN(value) ? 7 : value;
|
|
285
|
+
}
|
|
286
|
+
function isValidAdminKey(key) {
|
|
287
|
+
return key.startsWith(ADMIN_KEY_PREFIX);
|
|
288
|
+
}
|
|
289
|
+
function maskApiKey(key) {
|
|
290
|
+
if (key.length <= 19) return "***";
|
|
291
|
+
return `${key.slice(0, 15)}...${key.slice(-4)}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
//#endregion
|
|
295
|
+
//#region src/application/use-cases/show-api-key.use-case.ts
|
|
296
|
+
function createShowApiKeyUseCase(credentialStore) {
|
|
297
|
+
return async () => {
|
|
298
|
+
return maskApiKey(await credentialStore.load());
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/application/use-cases/store-api-key.use-case.ts
|
|
304
|
+
function createStoreApiKeyUseCase(credentialStore) {
|
|
305
|
+
return async (key) => {
|
|
306
|
+
const trimmed = key.trim();
|
|
307
|
+
if (!trimmed) throw new Error("API key cannot be empty");
|
|
308
|
+
if (!isValidAdminKey(trimmed)) throw new Error("Invalid admin API key: must start with sk-ant-admin. Get your key from Claude Console → Settings → Admin Keys.");
|
|
309
|
+
await credentialStore.save(trimmed);
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/presentation/commands/config.command.ts
|
|
315
|
+
function registerConfigCommand(program, credentialStore) {
|
|
316
|
+
const config = program.command("config").description("Manage API key storage");
|
|
317
|
+
config.command("set-key").description("Store Admin API key securely in macOS Keychain").action(async () => {
|
|
318
|
+
const { password } = await import("@inquirer/prompts");
|
|
319
|
+
const storeApiKey = createStoreApiKeyUseCase(credentialStore);
|
|
320
|
+
try {
|
|
321
|
+
await storeApiKey(await password({
|
|
322
|
+
message: "Enter your Admin API key (sk-ant-admin...):",
|
|
323
|
+
mask: true,
|
|
324
|
+
validate: (v) => v.trim().length > 0 ? true : "Key cannot be empty"
|
|
325
|
+
}));
|
|
326
|
+
console.log("API key stored successfully.");
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
config.command("show").description("Display masked API key").action(async () => {
|
|
333
|
+
const showApiKey = createShowApiKeyUseCase(credentialStore);
|
|
334
|
+
try {
|
|
335
|
+
const masked = await showApiKey();
|
|
336
|
+
console.log(masked);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
config.command("remove-key").description("Remove API key from Keychain").action(async () => {
|
|
343
|
+
const removeApiKey = createRemoveApiKeyUseCase(credentialStore);
|
|
344
|
+
try {
|
|
345
|
+
await removeApiKey();
|
|
346
|
+
console.log("API key removed.");
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/application/use-cases/get-cost-report.use-case.ts
|
|
356
|
+
function createGetCostReportUseCase(costRepository, costPresenter) {
|
|
357
|
+
return async (params) => {
|
|
358
|
+
const query = {
|
|
359
|
+
dateRange: parseDateRange(params.period, params.from, params.to),
|
|
360
|
+
groupBy: params.groupBy
|
|
361
|
+
};
|
|
362
|
+
const records = await costRepository.query(query);
|
|
363
|
+
if (params.sumOnly) {
|
|
364
|
+
const total = records.reduce((acc, r) => acc + r.amountDollars, 0);
|
|
365
|
+
const currency = records[0]?.currency ?? "USD";
|
|
366
|
+
costPresenter.presentSum(total, currency);
|
|
367
|
+
} else costPresenter.present(records);
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/presentation/commands/cost.command.ts
|
|
373
|
+
function registerCostCommand(program, costRepository, getCostPresenter) {
|
|
374
|
+
program.command("cost").description("Retrieve cost report").option("--from <date>", "Start date (YYYY-MM-DD or ISO)").option("--to <date>", "End date (YYYY-MM-DD or ISO)").option("--period <days>", "Period in days (e.g. 7d, 30d, 90d)", "7d").option("--group-by <fields>", "Group by workspace_id, description (comma-separated)").option("--json", "Output as JSON").option("--sum", "Output total sum only").action(async (opts) => {
|
|
375
|
+
const getCost = createGetCostReportUseCase(costRepository, getCostPresenter(Boolean(opts.json)));
|
|
376
|
+
try {
|
|
377
|
+
const groupBy = opts.groupBy ? opts.groupBy.split(",").map((s) => s.trim()) : ["description"];
|
|
378
|
+
await getCost({
|
|
379
|
+
from: opts.from,
|
|
380
|
+
to: opts.to,
|
|
381
|
+
period: opts.period,
|
|
382
|
+
groupBy,
|
|
383
|
+
sumOnly: Boolean(opts.sum)
|
|
384
|
+
});
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/application/use-cases/get-usage-report.use-case.ts
|
|
394
|
+
function createGetUsageReportUseCase(usageRepository, usagePresenter) {
|
|
395
|
+
return async (params) => {
|
|
396
|
+
const query = {
|
|
397
|
+
dateRange: parseDateRange(params.period, params.from, params.to),
|
|
398
|
+
models: params.models,
|
|
399
|
+
apiKeyIds: params.apiKeyIds,
|
|
400
|
+
groupBy: params.groupBy,
|
|
401
|
+
bucketWidth: params.bucketWidth ?? "1d"
|
|
402
|
+
};
|
|
403
|
+
const records = await usageRepository.query(query);
|
|
404
|
+
usagePresenter.present(records);
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/presentation/commands/usage.command.ts
|
|
410
|
+
function registerUsageCommand(program, usageRepository, getUsagePresenter) {
|
|
411
|
+
program.command("usage").description("Retrieve usage report").option("--from <date>", "Start date (YYYY-MM-DD or ISO)").option("--to <date>", "End date (YYYY-MM-DD or ISO)").option("--period <days>", "Period in days (e.g. 7d, 30d, 90d)", "7d").option("--model <models>", "Filter by model(s), comma-separated").option("--api-keys <ids>", "Filter by API key ID(s), comma-separated").option("--group-by <fields>", "Group by model, api_key_id, workspace_id, service_tier (comma-separated)").option("--bucket <width>", "Bucket width: 1d, 1h, 1m", "1d").option("--json", "Output as JSON").action(async (opts) => {
|
|
412
|
+
const getUsage = createGetUsageReportUseCase(usageRepository, getUsagePresenter(Boolean(opts.json)));
|
|
413
|
+
try {
|
|
414
|
+
const models = opts.model ? opts.model.split(",").map((s) => s.trim()) : void 0;
|
|
415
|
+
const apiKeyIds = opts.apiKeys ? opts.apiKeys.split(",").map((s) => s.trim()) : void 0;
|
|
416
|
+
const groupBy = opts.groupBy ? opts.groupBy.split(",").map((s) => s.trim()) : ["model"];
|
|
417
|
+
await getUsage({
|
|
418
|
+
from: opts.from,
|
|
419
|
+
to: opts.to,
|
|
420
|
+
period: opts.period,
|
|
421
|
+
models,
|
|
422
|
+
apiKeyIds,
|
|
423
|
+
groupBy,
|
|
424
|
+
bucketWidth: opts.bucket === "1h" || opts.bucket === "1m" ? opts.bucket : "1d"
|
|
425
|
+
});
|
|
426
|
+
} catch (err) {
|
|
427
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/index.ts
|
|
435
|
+
const credentialStore = createKeychainCredentialStore();
|
|
436
|
+
const usageRepository = createAnthropicUsageRepository(() => credentialStore.load());
|
|
437
|
+
const costRepository = createAnthropicCostRepository(() => credentialStore.load());
|
|
438
|
+
const getPresenter = (json) => json ? createJsonPresenter() : createTablePresenter();
|
|
439
|
+
const program = new Command();
|
|
440
|
+
program.name("claude-cost").description("CLI for Claude API usage and cost reports").version("1.0.0");
|
|
441
|
+
registerConfigCommand(program, credentialStore);
|
|
442
|
+
registerCostCommand(program, costRepository, getPresenter);
|
|
443
|
+
registerUsageCommand(program, usageRepository, getPresenter);
|
|
444
|
+
program.parse();
|
|
445
|
+
|
|
446
|
+
//#endregion
|
|
447
|
+
export { };
|
|
448
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["BASE_URL"],"sources":["../src/infrastructure/anthropic-cost-repository.ts","../src/infrastructure/anthropic-usage-repository.ts","../src/infrastructure/json-presenter.ts","../src/infrastructure/keychain-credential-store.ts","../src/infrastructure/table-presenter.ts","../src/application/use-cases/remove-api-key.use-case.ts","../src/application/date-range.helper.ts","../src/application/use-cases/show-api-key.use-case.ts","../src/application/use-cases/store-api-key.use-case.ts","../src/presentation/commands/config.command.ts","../src/application/use-cases/get-cost-report.use-case.ts","../src/presentation/commands/cost.command.ts","../src/application/use-cases/get-usage-report.use-case.ts","../src/presentation/commands/usage.command.ts","../src/index.ts"],"sourcesContent":["import type { CostRepository } from '../domain/cost-repository.port.js';\nimport type { CostRecord, CostReportQuery } from '../domain/entities.js';\n\nconst BASE_URL = 'https://api.anthropic.com';\nconst COST_ENDPOINT = '/v1/organizations/cost_report';\n\ntype RawCostBucket = {\n starting_at: string;\n ending_at: string;\n results: RawCostResult[];\n};\n\ntype RawCostResult = {\n amount: string;\n currency: string;\n description: string | null;\n model: string | null;\n token_type: string | null;\n cost_type: string | null;\n service_tier: string | null;\n workspace_id: string | null;\n};\n\ntype RawCostResponse = {\n data: RawCostBucket[];\n has_more: boolean;\n next_page?: string;\n};\n\nfunction mapToCostRecord(\n bucket: RawCostBucket,\n result: RawCostResult,\n): CostRecord {\n const amountCents = parseFloat(result.amount);\n const amountDollars = Number.isNaN(amountCents) ? 0 : amountCents / 100;\n return {\n date: bucket.starting_at,\n description: result.description ?? null,\n model: result.model ?? null,\n amountDollars,\n currency: result.currency ?? 'USD',\n tokenType: result.token_type ?? null,\n costType: result.cost_type ?? null,\n serviceTier: result.service_tier ?? null,\n };\n}\n\nexport function createAnthropicCostRepository(\n getApiKey: () => Promise<string>,\n): CostRepository {\n return {\n async query(params: CostReportQuery) {\n const apiKey = await getApiKey();\n const allRecords: CostRecord[] = [];\n let page: string | undefined;\n\n do {\n const searchParams = new URLSearchParams();\n searchParams.set('starting_at', params.dateRange.startingAt);\n searchParams.set('ending_at', params.dateRange.endingAt);\n searchParams.set('bucket_width', '1d');\n\n if (params.groupBy?.length) {\n for (const g of params.groupBy) {\n searchParams.append('group_by[]', g);\n }\n }\n if (page) {\n searchParams.set('page', page);\n }\n\n const url = `${BASE_URL}${COST_ENDPOINT}?${searchParams}`;\n const res = await fetch(url, {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'x-api-key': apiKey,\n },\n });\n\n if (!res.ok) {\n const body = await res.text();\n throw new Error(\n `Cost API error (${res.status}): ${body || res.statusText}`,\n );\n }\n\n const json = (await res.json()) as RawCostResponse;\n\n for (const bucket of json.data) {\n for (const result of bucket.results) {\n allRecords.push(mapToCostRecord(bucket, result));\n }\n }\n\n page = json.has_more ? json.next_page : undefined;\n } while (page);\n\n return allRecords;\n },\n };\n}\n","import type { UsageRecord, UsageReportQuery } from '../domain/entities.js';\nimport type { UsageRepository } from '../domain/usage-repository.port.js';\n\nconst BASE_URL = 'https://api.anthropic.com';\nconst USAGE_ENDPOINT = '/v1/organizations/usage_report/messages';\n\ntype RawUsageBucket = {\n starting_at: string;\n ending_at: string;\n results: RawUsageResult[];\n};\n\ntype RawUsageResult = {\n api_key_id: string | null;\n uncached_input_tokens: number;\n cache_read_input_tokens: number;\n cache_creation?: {\n ephemeral_1h_input_tokens?: number;\n ephemeral_5m_input_tokens?: number;\n };\n output_tokens: number;\n server_tool_use?: { web_search_requests?: number };\n model: string | null;\n workspace_id: string | null;\n service_tier: string | null;\n};\n\ntype RawUsageResponse = {\n data: RawUsageBucket[];\n has_more: boolean;\n next_page?: string;\n};\n\nfunction mapToUsageRecord(\n bucket: RawUsageBucket,\n result: RawUsageResult,\n): UsageRecord {\n const cacheCreation = result.cache_creation ?? {};\n const cacheCreationTokens =\n (cacheCreation.ephemeral_1h_input_tokens ?? 0) +\n (cacheCreation.ephemeral_5m_input_tokens ?? 0);\n return {\n date: bucket.starting_at,\n model: result.model ?? null,\n uncachedInputTokens: result.uncached_input_tokens,\n cachedInputTokens: result.cache_read_input_tokens,\n cacheCreationTokens,\n outputTokens: result.output_tokens,\n webSearchRequests: result.server_tool_use?.web_search_requests ?? 0,\n apiKeyId: result.api_key_id ?? null,\n workspaceId: result.workspace_id ?? null,\n serviceTier: result.service_tier ?? null,\n };\n}\n\nexport function createAnthropicUsageRepository(\n getApiKey: () => Promise<string>,\n): UsageRepository {\n return {\n async query(params: UsageReportQuery) {\n const apiKey = await getApiKey();\n const allRecords: UsageRecord[] = [];\n let page: string | undefined;\n\n do {\n const searchParams = new URLSearchParams();\n searchParams.set('starting_at', params.dateRange.startingAt);\n searchParams.set('ending_at', params.dateRange.endingAt);\n searchParams.set('bucket_width', params.bucketWidth ?? '1d');\n\n if (params.models?.length) {\n for (const m of params.models) {\n searchParams.append('models[]', m);\n }\n }\n if (params.apiKeyIds?.length) {\n for (const id of params.apiKeyIds) {\n searchParams.append('api_key_ids[]', id);\n }\n }\n if (params.groupBy?.length) {\n for (const g of params.groupBy) {\n searchParams.append('group_by[]', g);\n }\n }\n if (page) {\n searchParams.set('page', page);\n }\n\n const url = `${BASE_URL}${USAGE_ENDPOINT}?${searchParams}`;\n const res = await fetch(url, {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'x-api-key': apiKey,\n },\n });\n\n if (!res.ok) {\n const body = await res.text();\n throw new Error(\n `Usage API error (${res.status}): ${body || res.statusText}`,\n );\n }\n\n const json = (await res.json()) as RawUsageResponse;\n\n for (const bucket of json.data) {\n for (const result of bucket.results) {\n allRecords.push(mapToUsageRecord(bucket, result));\n }\n }\n\n page = json.has_more ? json.next_page : undefined;\n } while (page);\n\n return allRecords;\n },\n };\n}\n","import type { CostPresenter } from '../application/ports/cost-presenter.port.js';\nimport type { UsagePresenter } from '../application/ports/usage-presenter.port.js';\nimport type { CostRecord, UsageRecord } from '../domain/entities.js';\n\nexport function createJsonPresenter(): UsagePresenter & CostPresenter {\n return {\n present(records: UsageRecord[] | CostRecord[]): void {\n console.log(JSON.stringify(records, null, 2));\n },\n\n presentSum(total: number, currency: string): void {\n console.log(JSON.stringify({ total, currency }, null, 2));\n },\n };\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { CredentialStore } from '../application/ports/credential-store.port.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst SERVICE = 'claude-cost-cli';\nconst ACCOUNT = 'claude-cost-cli';\n\nexport function createKeychainCredentialStore(): CredentialStore {\n return {\n async save(credential: string) {\n try {\n await execFileAsync('security', [\n 'add-generic-password',\n '-a',\n ACCOUNT,\n '-s',\n SERVICE,\n '-w',\n credential,\n '-U',\n ]);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes('already exists')) {\n await execFileAsync('security', [\n 'delete-generic-password',\n '-a',\n ACCOUNT,\n '-s',\n SERVICE,\n ]);\n await this.save(credential);\n } else {\n throw new Error(`Failed to store credential in Keychain: ${message}`);\n }\n }\n },\n\n async load() {\n try {\n const { stdout } = await execFileAsync('security', [\n 'find-generic-password',\n '-a',\n ACCOUNT,\n '-s',\n SERVICE,\n '-w',\n ]);\n return stdout.trim();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (\n message.includes('could not be found') ||\n message.includes('The specified item could not be found')\n ) {\n throw new Error('No API key stored. Run: claude-cost config set-key');\n }\n throw new Error(\n `Failed to retrieve credential from Keychain: ${message}`,\n );\n }\n },\n\n async clear() {\n try {\n await execFileAsync('security', [\n 'delete-generic-password',\n '-a',\n ACCOUNT,\n '-s',\n SERVICE,\n ]);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (\n !message.includes('could not be found') &&\n !message.includes('The specified item could not be found')\n ) {\n throw new Error(\n `Failed to remove credential from Keychain: ${message}`,\n );\n }\n }\n },\n };\n}\n","import chalk from 'chalk';\nimport Table from 'cli-table3';\nimport type { CostPresenter } from '../application/ports/cost-presenter.port.js';\nimport type { UsagePresenter } from '../application/ports/usage-presenter.port.js';\nimport type { CostRecord, UsageRecord } from '../domain/entities.js';\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toISOString().slice(0, 10);\n}\n\nexport function createTablePresenter(): UsagePresenter & CostPresenter {\n return {\n present(records: UsageRecord[] | CostRecord[]): void {\n if (records.length === 0) {\n console.log('No data found for the specified period.');\n return;\n }\n\n const first = records[0];\n if ('uncachedInputTokens' in first) {\n presentUsage(records as UsageRecord[]);\n } else {\n presentCost(records as CostRecord[]);\n }\n },\n\n presentSum(total: number, currency: string): void {\n console.log(`Total: $${total.toFixed(2)} ${currency}`);\n },\n };\n}\n\nfunction presentUsage(records: UsageRecord[]) {\n const table = new Table({\n head: [\n chalk.green('Date'),\n chalk.green('Model'),\n chalk.green('Input Tokens'),\n chalk.green('Cached Tokens'),\n chalk.green('Output Tokens'),\n chalk.green('Web Searches'),\n ],\n });\n\n for (const r of records) {\n table.push([\n chalk.dim(formatDate(r.date)),\n r.model ?? '-',\n String(r.uncachedInputTokens + r.cacheCreationTokens),\n String(r.cachedInputTokens),\n String(r.outputTokens),\n String(r.webSearchRequests),\n ]);\n }\n\n console.log(table.toString());\n}\n\nfunction presentCost(records: CostRecord[]) {\n const table = new Table({\n head: [\n chalk.green('Date'),\n chalk.green('Description'),\n chalk.green('Model'),\n chalk.green('Amount (USD)'),\n chalk.green('Token Type'),\n chalk.green('Tier'),\n ],\n });\n\n for (const r of records) {\n table.push([\n chalk.dim(formatDate(r.date)),\n (r.description ?? '-').slice(0, 40),\n r.model ?? '-',\n r.amountDollars.toFixed(4),\n r.tokenType ?? '-',\n r.serviceTier ?? '-',\n ]);\n }\n\n console.log(table.toString());\n}\n","import type { CredentialStore } from '../ports/credential-store.port.js';\n\nexport function createRemoveApiKeyUseCase(\n credentialStore: CredentialStore,\n): () => Promise<void> {\n return async () => {\n await credentialStore.clear();\n };\n}\n","import type { DateRange } from '../domain/entities.js';\n\nconst ADMIN_KEY_PREFIX = 'sk-ant-admin';\n\nexport function parseDateRange(\n period?: string,\n from?: string,\n to?: string,\n): DateRange {\n const now = new Date();\n let startingAt: Date;\n let endingAt: Date;\n\n if (from && to) {\n startingAt = new Date(from);\n endingAt = new Date(to);\n } else if (period) {\n const days = parsePeriod(period);\n endingAt = now;\n startingAt = new Date(now);\n startingAt.setDate(startingAt.getDate() - days);\n } else {\n endingAt = now;\n startingAt = new Date(now);\n startingAt.setDate(startingAt.getDate() - 7);\n }\n\n return {\n startingAt: startingAt.toISOString(),\n endingAt: endingAt.toISOString(),\n };\n}\n\nfunction parsePeriod(period: string): number {\n const match = period.match(/^(\\d+)(d|days?)$/i);\n if (!match) return 7;\n const value = parseInt(match[1], 10);\n return Number.isNaN(value) ? 7 : value;\n}\n\nexport function isValidAdminKey(key: string): boolean {\n return key.startsWith(ADMIN_KEY_PREFIX);\n}\n\nexport function maskApiKey(key: string): string {\n if (key.length <= 19) return '***';\n return `${key.slice(0, 15)}...${key.slice(-4)}`;\n}\n","import { maskApiKey } from '../date-range.helper.js';\nimport type { CredentialStore } from '../ports/credential-store.port.js';\n\nexport function createShowApiKeyUseCase(\n credentialStore: CredentialStore,\n): () => Promise<string> {\n return async () => {\n const key = await credentialStore.load();\n return maskApiKey(key);\n };\n}\n","import { isValidAdminKey } from '../date-range.helper.js';\nimport type { CredentialStore } from '../ports/credential-store.port.js';\n\nexport function createStoreApiKeyUseCase(\n credentialStore: CredentialStore,\n): (key: string) => Promise<void> {\n return async (key: string) => {\n const trimmed = key.trim();\n if (!trimmed) throw new Error('API key cannot be empty');\n if (!isValidAdminKey(trimmed)) {\n throw new Error(\n 'Invalid admin API key: must start with sk-ant-admin. Get your key from Claude Console → Settings → Admin Keys.',\n );\n }\n await credentialStore.save(trimmed);\n };\n}\n","import type { Command } from 'commander';\nimport type { CredentialStore } from '../../application/ports/credential-store.port.js';\nimport { createRemoveApiKeyUseCase } from '../../application/use-cases/remove-api-key.use-case.js';\nimport { createShowApiKeyUseCase } from '../../application/use-cases/show-api-key.use-case.js';\nimport { createStoreApiKeyUseCase } from '../../application/use-cases/store-api-key.use-case.js';\n\nexport function registerConfigCommand(\n program: Command,\n credentialStore: CredentialStore,\n): void {\n const config = program\n .command('config')\n .description('Manage API key storage');\n\n config\n .command('set-key')\n .description('Store Admin API key securely in macOS Keychain')\n .action(async () => {\n const { password } = await import('@inquirer/prompts');\n const storeApiKey = createStoreApiKeyUseCase(credentialStore);\n try {\n const key = await password({\n message: 'Enter your Admin API key (sk-ant-admin...):',\n mask: true,\n validate: (v) => (v.trim().length > 0 ? true : 'Key cannot be empty'),\n });\n await storeApiKey(key);\n console.log('API key stored successfully.');\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n config\n .command('show')\n .description('Display masked API key')\n .action(async () => {\n const showApiKey = createShowApiKeyUseCase(credentialStore);\n try {\n const masked = await showApiKey();\n console.log(masked);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n config\n .command('remove-key')\n .description('Remove API key from Keychain')\n .action(async () => {\n const removeApiKey = createRemoveApiKeyUseCase(credentialStore);\n try {\n await removeApiKey();\n console.log('API key removed.');\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n","import type { CostRepository } from '../../domain/cost-repository.port.js';\nimport type { CostReportQuery } from '../../domain/entities.js';\nimport { parseDateRange } from '../date-range.helper.js';\nimport type { CostPresenter } from '../ports/cost-presenter.port.js';\n\ntype GetCostReportParams = {\n period?: string;\n from?: string;\n to?: string;\n groupBy?: ('workspace_id' | 'description')[];\n sumOnly?: boolean;\n};\n\nexport function createGetCostReportUseCase(\n costRepository: CostRepository,\n costPresenter: CostPresenter,\n): (params: GetCostReportParams) => Promise<void> {\n return async (params) => {\n const dateRange = parseDateRange(params.period, params.from, params.to);\n const query: CostReportQuery = {\n dateRange,\n groupBy: params.groupBy,\n };\n const records = await costRepository.query(query);\n if (params.sumOnly) {\n const total = records.reduce((acc, r) => acc + r.amountDollars, 0);\n const currency = records[0]?.currency ?? 'USD';\n costPresenter.presentSum(total, currency);\n } else {\n costPresenter.present(records);\n }\n };\n}\n","import type { Command } from 'commander';\nimport type { CostPresenter } from '../../application/ports/cost-presenter.port.js';\nimport { createGetCostReportUseCase } from '../../application/use-cases/get-cost-report.use-case.js';\nimport type { CostRepository } from '../../domain/cost-repository.port.js';\n\nexport function registerCostCommand(\n program: Command,\n costRepository: CostRepository,\n getCostPresenter: (json: boolean) => CostPresenter,\n): void {\n program\n .command('cost')\n .description('Retrieve cost report')\n .option('--from <date>', 'Start date (YYYY-MM-DD or ISO)')\n .option('--to <date>', 'End date (YYYY-MM-DD or ISO)')\n .option('--period <days>', 'Period in days (e.g. 7d, 30d, 90d)', '7d')\n .option(\n '--group-by <fields>',\n 'Group by workspace_id, description (comma-separated)',\n )\n .option('--json', 'Output as JSON')\n .option('--sum', 'Output total sum only')\n .action(async (opts) => {\n const costPresenter = getCostPresenter(Boolean(opts.json));\n const getCost = createGetCostReportUseCase(costRepository, costPresenter);\n try {\n const groupBy = opts.groupBy\n ? (opts.groupBy as string).split(',').map((s: string) => s.trim())\n : ['description'];\n await getCost({\n from: opts.from,\n to: opts.to,\n period: opts.period,\n groupBy: groupBy as ('workspace_id' | 'description')[] | undefined,\n sumOnly: Boolean(opts.sum),\n });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n","import type { UsageReportQuery } from '../../domain/entities.js';\nimport type { UsageRepository } from '../../domain/usage-repository.port.js';\nimport { parseDateRange } from '../date-range.helper.js';\nimport type { UsagePresenter } from '../ports/usage-presenter.port.js';\n\ntype GetUsageReportParams = {\n period?: string;\n from?: string;\n to?: string;\n models?: string[];\n apiKeyIds?: string[];\n groupBy?: string[];\n bucketWidth?: '1d' | '1h' | '1m';\n};\n\nexport function createGetUsageReportUseCase(\n usageRepository: UsageRepository,\n usagePresenter: UsagePresenter,\n): (params: GetUsageReportParams) => Promise<void> {\n return async (params) => {\n const dateRange = parseDateRange(params.period, params.from, params.to);\n const query: UsageReportQuery = {\n dateRange,\n models: params.models,\n apiKeyIds: params.apiKeyIds,\n groupBy: params.groupBy,\n bucketWidth: params.bucketWidth ?? '1d',\n };\n const records = await usageRepository.query(query);\n usagePresenter.present(records);\n };\n}\n","import type { Command } from 'commander';\nimport type { UsagePresenter } from '../../application/ports/usage-presenter.port.js';\nimport { createGetUsageReportUseCase } from '../../application/use-cases/get-usage-report.use-case.js';\nimport type { UsageRepository } from '../../domain/usage-repository.port.js';\n\nexport function registerUsageCommand(\n program: Command,\n usageRepository: UsageRepository,\n getUsagePresenter: (json: boolean) => UsagePresenter,\n): void {\n program\n .command('usage')\n .description('Retrieve usage report')\n .option('--from <date>', 'Start date (YYYY-MM-DD or ISO)')\n .option('--to <date>', 'End date (YYYY-MM-DD or ISO)')\n .option('--period <days>', 'Period in days (e.g. 7d, 30d, 90d)', '7d')\n .option('--model <models>', 'Filter by model(s), comma-separated')\n .option('--api-keys <ids>', 'Filter by API key ID(s), comma-separated')\n .option(\n '--group-by <fields>',\n 'Group by model, api_key_id, workspace_id, service_tier (comma-separated)',\n )\n .option('--bucket <width>', 'Bucket width: 1d, 1h, 1m', '1d')\n .option('--json', 'Output as JSON')\n .action(async (opts) => {\n const usagePresenter = getUsagePresenter(Boolean(opts.json));\n const getUsage = createGetUsageReportUseCase(\n usageRepository,\n usagePresenter,\n );\n try {\n const models = opts.model\n ? (opts.model as string).split(',').map((s: string) => s.trim())\n : undefined;\n const apiKeyIds = opts.apiKeys\n ? (opts.apiKeys as string).split(',').map((s: string) => s.trim())\n : undefined;\n const groupBy = opts.groupBy\n ? (opts.groupBy as string).split(',').map((s: string) => s.trim())\n : ['model'];\n await getUsage({\n from: opts.from,\n to: opts.to,\n period: opts.period,\n models,\n apiKeyIds,\n groupBy,\n bucketWidth:\n opts.bucket === '1h' || opts.bucket === '1m' ? opts.bucket : '1d',\n });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n","import { Command } from 'commander';\nimport { createAnthropicCostRepository } from './infrastructure/anthropic-cost-repository.js';\nimport { createAnthropicUsageRepository } from './infrastructure/anthropic-usage-repository.js';\nimport { createJsonPresenter } from './infrastructure/json-presenter.js';\nimport { createKeychainCredentialStore } from './infrastructure/keychain-credential-store.js';\nimport { createTablePresenter } from './infrastructure/table-presenter.js';\nimport { registerConfigCommand } from './presentation/commands/config.command.js';\nimport { registerCostCommand } from './presentation/commands/cost.command.js';\nimport { registerUsageCommand } from './presentation/commands/usage.command.js';\n\nconst credentialStore = createKeychainCredentialStore();\nconst usageRepository = createAnthropicUsageRepository(() =>\n credentialStore.load(),\n);\nconst costRepository = createAnthropicCostRepository(() =>\n credentialStore.load(),\n);\n\nconst getPresenter = (json: boolean) =>\n json ? createJsonPresenter() : createTablePresenter();\n\nconst program = new Command();\nprogram\n .name('claude-cost')\n .description('CLI for Claude API usage and cost reports')\n .version('1.0.0');\n\nregisterConfigCommand(program, credentialStore);\nregisterCostCommand(program, costRepository, getPresenter);\nregisterUsageCommand(program, usageRepository, getPresenter);\n\nprogram.parse();\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-cost-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI for Claude API usage and cost reports",
|
|
6
|
+
"main": "dist/index.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claude-cost": "./dist/index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsdown",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"start": "node dist/index.mjs",
|
|
19
|
+
"lint": "biome lint",
|
|
20
|
+
"format": "biome format --write",
|
|
21
|
+
"check": "biome check --write"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"claude",
|
|
25
|
+
"anthropic",
|
|
26
|
+
"usage",
|
|
27
|
+
"cost",
|
|
28
|
+
"cli",
|
|
29
|
+
"api"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/cyberash-dev/claude-cost-cli.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/cyberash-dev/claude-cost-cli/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/cyberash-dev/claude-cost-cli#readme",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@inquirer/prompts": "8.2.0",
|
|
45
|
+
"chalk": "5.6.2",
|
|
46
|
+
"cli-table3": "0.6.5",
|
|
47
|
+
"commander": "14.0.3"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@biomejs/biome": "2.3.14",
|
|
51
|
+
"@types/node": "25.2.3",
|
|
52
|
+
"tsdown": "0.20.3",
|
|
53
|
+
"typescript": "5.9.3"
|
|
54
|
+
}
|
|
55
|
+
}
|