ccusage-tracker 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 +41 -0
- package/dist/index.js +387 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ccusage-tracker
|
|
2
|
+
|
|
3
|
+
CLI for [ccusage-tracker](https://github.com/ericcai0814/ccusage-tracker) — a self-hosted Claude Code usage tracker for teams.
|
|
4
|
+
|
|
5
|
+
Install Claude Code SessionStart/SessionEnd hooks that report token usage to a self-hosted tracker server. Runs on macOS, Linux, and Windows.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx ccusage-tracker setup
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You'll be asked for your name, the team's server URL, and a team key (ask your admin).
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
ccusage-tracker setup Install hooks and configure server connection
|
|
19
|
+
ccusage-tracker report View team token usage report
|
|
20
|
+
ccusage-tracker status Check current configuration status
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
After installation, the binary is also available as `tracker` (if installed globally with `npm i -g ccusage-tracker`).
|
|
24
|
+
|
|
25
|
+
## What it does
|
|
26
|
+
|
|
27
|
+
`setup` writes a config file to `~/.config/ccusage-tracker/config.json` and adds two hooks to your Claude Code `~/.claude/settings.json`:
|
|
28
|
+
|
|
29
|
+
- **SessionStart** — records the model at session start
|
|
30
|
+
- **SessionEnd** — POSTs token usage (and session metrics in 0.2.1+) to your team's tracker server
|
|
31
|
+
|
|
32
|
+
Hook scripts are downloaded from your tracker server, so they always match the server version.
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- Node.js 18+
|
|
37
|
+
- A running ccusage-tracker server (see [main repo](https://github.com/ericcai0814/ccusage-tracker) for self-hosting)
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/setup.ts
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
|
|
6
|
+
// src/config.ts
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
function getConfigDir() {
|
|
11
|
+
return join(homedir(), ".config", "ccusage-tracker");
|
|
12
|
+
}
|
|
13
|
+
function getConfigPath() {
|
|
14
|
+
return join(getConfigDir(), "config.json");
|
|
15
|
+
}
|
|
16
|
+
function readConfig() {
|
|
17
|
+
const path = getConfigPath();
|
|
18
|
+
if (!existsSync(path))
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function writeConfig(config) {
|
|
27
|
+
const dir = getConfigDir();
|
|
28
|
+
if (!existsSync(dir)) {
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + `
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/hooks.ts
|
|
36
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, copyFileSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
37
|
+
import { join as join2 } from "node:path";
|
|
38
|
+
import { homedir as homedir2 } from "node:os";
|
|
39
|
+
function getClaudeSettingsPath() {
|
|
40
|
+
return join2(homedir2(), ".claude", "settings.json");
|
|
41
|
+
}
|
|
42
|
+
function getHookCommand() {
|
|
43
|
+
return "node " + join2(homedir2(), ".config", "ccusage-tracker", "session-end.mjs");
|
|
44
|
+
}
|
|
45
|
+
function getStartHookCommand() {
|
|
46
|
+
return "node " + join2(homedir2(), ".config", "ccusage-tracker", "session-start.mjs");
|
|
47
|
+
}
|
|
48
|
+
function isCcusageTrackerHook(command) {
|
|
49
|
+
return !!command && command.includes("ccusage-tracker");
|
|
50
|
+
}
|
|
51
|
+
function appendHookIfMissing(existing, command) {
|
|
52
|
+
const present = existing.some((m) => m.hooks?.some((h) => isCcusageTrackerHook(h.command)));
|
|
53
|
+
if (present) {
|
|
54
|
+
return { matchers: existing, installed: false };
|
|
55
|
+
}
|
|
56
|
+
const newMatcher = {
|
|
57
|
+
matcher: "",
|
|
58
|
+
hooks: [{ type: "command", command }]
|
|
59
|
+
};
|
|
60
|
+
return { matchers: [...existing, newMatcher], installed: true };
|
|
61
|
+
}
|
|
62
|
+
function applyTrackerHooks(settings) {
|
|
63
|
+
const start = appendHookIfMissing(settings.hooks?.SessionStart ?? [], getStartHookCommand());
|
|
64
|
+
const end = appendHookIfMissing(settings.hooks?.SessionEnd ?? [], getHookCommand());
|
|
65
|
+
const updated = start.installed || end.installed ? {
|
|
66
|
+
...settings,
|
|
67
|
+
hooks: {
|
|
68
|
+
...settings.hooks,
|
|
69
|
+
SessionStart: start.matchers,
|
|
70
|
+
SessionEnd: end.matchers
|
|
71
|
+
}
|
|
72
|
+
} : settings;
|
|
73
|
+
return {
|
|
74
|
+
updated,
|
|
75
|
+
sessionStartInstalled: start.installed,
|
|
76
|
+
sessionEndInstalled: end.installed
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function installHook(scripts) {
|
|
80
|
+
const settingsPath = getClaudeSettingsPath();
|
|
81
|
+
let settings = {};
|
|
82
|
+
let backedUp = false;
|
|
83
|
+
if (existsSync2(settingsPath)) {
|
|
84
|
+
const raw = readFileSync2(settingsPath, "utf-8");
|
|
85
|
+
settings = JSON.parse(raw);
|
|
86
|
+
const backupPath = settingsPath + ".backup";
|
|
87
|
+
copyFileSync(settingsPath, backupPath);
|
|
88
|
+
backedUp = true;
|
|
89
|
+
}
|
|
90
|
+
const destDir = join2(homedir2(), ".config", "ccusage-tracker");
|
|
91
|
+
mkdirSync2(destDir, { recursive: true });
|
|
92
|
+
writeFileSync2(join2(destDir, "session-end.mjs"), scripts.sessionEnd);
|
|
93
|
+
writeFileSync2(join2(destDir, "session-start.mjs"), scripts.sessionStart);
|
|
94
|
+
const { updated, sessionStartInstalled, sessionEndInstalled } = applyTrackerHooks(settings);
|
|
95
|
+
if (sessionStartInstalled || sessionEndInstalled) {
|
|
96
|
+
writeFileSync2(settingsPath, JSON.stringify(updated, null, 2) + `
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
return { sessionEndInstalled, sessionStartInstalled, backedUp };
|
|
100
|
+
}
|
|
101
|
+
function isHookInstalled() {
|
|
102
|
+
const settingsPath = getClaudeSettingsPath();
|
|
103
|
+
if (!existsSync2(settingsPath))
|
|
104
|
+
return false;
|
|
105
|
+
try {
|
|
106
|
+
const settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
107
|
+
return settings.hooks?.SessionEnd?.some((m) => m.hooks?.some((h) => isCcusageTrackerHook(h.command))) ?? false;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/commands/setup.ts
|
|
114
|
+
function defaultPrompt(question) {
|
|
115
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
rl.question(question, (answer) => {
|
|
118
|
+
rl.close();
|
|
119
|
+
resolve(answer.trim());
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async function defaultCheckServer(serverUrl) {
|
|
124
|
+
try {
|
|
125
|
+
const res = await fetch(`${serverUrl}/api/health`, { signal: AbortSignal.timeout(5000) });
|
|
126
|
+
const body = await res.json();
|
|
127
|
+
return body.ok === true;
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function defaultCheckCcusage() {
|
|
133
|
+
try {
|
|
134
|
+
Bun.spawnSync(["ccusage", "--version"]);
|
|
135
|
+
return true;
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function defaultFetchHookScript(serverUrl, scriptName) {
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(`${serverUrl}/scripts/${scriptName}`, { signal: AbortSignal.timeout(1e4) });
|
|
143
|
+
if (!res.ok)
|
|
144
|
+
return null;
|
|
145
|
+
return await res.text();
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
var defaultDeps = {
|
|
151
|
+
prompt: defaultPrompt,
|
|
152
|
+
writeConfig,
|
|
153
|
+
installHook,
|
|
154
|
+
fetchHookScript: defaultFetchHookScript,
|
|
155
|
+
checkServer: defaultCheckServer,
|
|
156
|
+
checkCcusage: defaultCheckCcusage,
|
|
157
|
+
log: (msg) => console.log(msg),
|
|
158
|
+
warn: (msg) => console.warn(msg),
|
|
159
|
+
exit: (code) => process.exit(code)
|
|
160
|
+
};
|
|
161
|
+
async function setupCommand(overrides) {
|
|
162
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
163
|
+
deps.log(`ccusage-tracker setup
|
|
164
|
+
`);
|
|
165
|
+
const name = await deps.prompt("Your name: ");
|
|
166
|
+
if (!name) {
|
|
167
|
+
deps.warn("Name is required.");
|
|
168
|
+
deps.exit(1);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const serverUrl = await deps.prompt("Server URL (e.g. https://tracker.example.com): ");
|
|
172
|
+
if (!serverUrl) {
|
|
173
|
+
deps.warn("Server URL is required.");
|
|
174
|
+
deps.exit(1);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const teamKey = await deps.prompt("Team Key (ask your admin): ");
|
|
178
|
+
if (!teamKey) {
|
|
179
|
+
deps.warn("Team Key is required.");
|
|
180
|
+
deps.exit(1);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const config = {
|
|
184
|
+
server_url: serverUrl.replace(/\/+$/, ""),
|
|
185
|
+
team_key: teamKey,
|
|
186
|
+
member_name: name
|
|
187
|
+
};
|
|
188
|
+
deps.writeConfig(config);
|
|
189
|
+
deps.log(`
|
|
190
|
+
Config saved.`);
|
|
191
|
+
const [sessionEnd, sessionStart] = await Promise.all([
|
|
192
|
+
deps.fetchHookScript(config.server_url, "session-end.mjs"),
|
|
193
|
+
deps.fetchHookScript(config.server_url, "session-start.mjs")
|
|
194
|
+
]);
|
|
195
|
+
if (sessionEnd && sessionStart) {
|
|
196
|
+
try {
|
|
197
|
+
const { sessionEndInstalled, sessionStartInstalled, backedUp } = deps.installHook({
|
|
198
|
+
sessionEnd,
|
|
199
|
+
sessionStart
|
|
200
|
+
});
|
|
201
|
+
if (sessionEndInstalled || sessionStartInstalled) {
|
|
202
|
+
deps.log("SessionStart + SessionEnd hooks installed." + (backedUp ? " (settings.json backed up)" : ""));
|
|
203
|
+
} else {
|
|
204
|
+
deps.log("SessionStart + SessionEnd hooks already installed.");
|
|
205
|
+
}
|
|
206
|
+
} catch (err) {
|
|
207
|
+
deps.warn("Warning: Could not install hooks automatically. " + err.message);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
deps.warn("Warning: Could not download hook scripts from " + config.server_url);
|
|
211
|
+
}
|
|
212
|
+
const serverOk = await deps.checkServer(config.server_url);
|
|
213
|
+
if (serverOk) {
|
|
214
|
+
deps.log("Server is reachable.");
|
|
215
|
+
} else {
|
|
216
|
+
deps.warn("Warning: Server is not reachable at " + config.server_url);
|
|
217
|
+
}
|
|
218
|
+
const hasCcusage = deps.checkCcusage();
|
|
219
|
+
if (hasCcusage) {
|
|
220
|
+
deps.log("ccusage is installed.");
|
|
221
|
+
} else {
|
|
222
|
+
deps.warn("Warning: ccusage not found. Install with: npx ccusage@latest");
|
|
223
|
+
}
|
|
224
|
+
deps.log(`
|
|
225
|
+
Setup complete!`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/commands/report.ts
|
|
229
|
+
function padRight(str, len) {
|
|
230
|
+
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
231
|
+
}
|
|
232
|
+
function padLeft(str, len) {
|
|
233
|
+
return str.length >= len ? str : " ".repeat(len - str.length) + str;
|
|
234
|
+
}
|
|
235
|
+
function formatTable(data) {
|
|
236
|
+
const lines = [];
|
|
237
|
+
lines.push(`Period: ${data.period} (${data.from} ~ ${data.to})`);
|
|
238
|
+
lines.push(`Total Cost: $${data.total_cost_usd.toFixed(2)} | Total Tokens: ${data.total_tokens.toLocaleString()} | Active Members: ${data.active_members}`);
|
|
239
|
+
lines.push("");
|
|
240
|
+
if (data.members.length === 0) {
|
|
241
|
+
lines.push("No usage data for this period.");
|
|
242
|
+
return lines.join(`
|
|
243
|
+
`);
|
|
244
|
+
}
|
|
245
|
+
const header = [
|
|
246
|
+
padRight("Member", 15),
|
|
247
|
+
padLeft("Input", 12),
|
|
248
|
+
padLeft("Output", 12),
|
|
249
|
+
padLeft("Cache Create", 14),
|
|
250
|
+
padLeft("Cache Read", 12),
|
|
251
|
+
padLeft("Cost", 10)
|
|
252
|
+
].join(" ");
|
|
253
|
+
const separator = "-".repeat(header.length);
|
|
254
|
+
lines.push(header);
|
|
255
|
+
lines.push(separator);
|
|
256
|
+
for (const m of data.members) {
|
|
257
|
+
lines.push([
|
|
258
|
+
padRight(m.member_name, 15),
|
|
259
|
+
padLeft(m.input_tokens.toLocaleString(), 12),
|
|
260
|
+
padLeft(m.output_tokens.toLocaleString(), 12),
|
|
261
|
+
padLeft(m.cache_creation_tokens.toLocaleString(), 14),
|
|
262
|
+
padLeft(m.cache_read_tokens.toLocaleString(), 12),
|
|
263
|
+
padLeft(`$${m.total_cost_usd.toFixed(2)}`, 10)
|
|
264
|
+
].join(" "));
|
|
265
|
+
}
|
|
266
|
+
return lines.join(`
|
|
267
|
+
`);
|
|
268
|
+
}
|
|
269
|
+
async function reportCommand(args) {
|
|
270
|
+
const config = readConfig();
|
|
271
|
+
if (!config) {
|
|
272
|
+
console.error("Not configured. Run `tracker setup` first.");
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
let period = "month";
|
|
276
|
+
let jsonOutput = false;
|
|
277
|
+
for (let i = 0;i < args.length; i++) {
|
|
278
|
+
if (args[i] === "--period" && args[i + 1]) {
|
|
279
|
+
period = args[i + 1];
|
|
280
|
+
i++;
|
|
281
|
+
}
|
|
282
|
+
if (args[i] === "--json") {
|
|
283
|
+
jsonOutput = true;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const res = await fetch(`${config.server_url}/api/report/summary?period=${period}`, {
|
|
288
|
+
headers: { Authorization: `Bearer ${config.team_key}` },
|
|
289
|
+
signal: AbortSignal.timeout(1e4)
|
|
290
|
+
});
|
|
291
|
+
if (!res.ok) {
|
|
292
|
+
const body = await res.json().catch(() => ({}));
|
|
293
|
+
console.error(`Server error: ${res.status}`, body);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
const data = await res.json();
|
|
297
|
+
if (jsonOutput) {
|
|
298
|
+
console.log(JSON.stringify(data, null, 2));
|
|
299
|
+
} else {
|
|
300
|
+
console.log(formatTable(data));
|
|
301
|
+
}
|
|
302
|
+
} catch (err) {
|
|
303
|
+
console.error("Failed to fetch report:", err.message);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/commands/status.ts
|
|
309
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
310
|
+
import { join as join3, dirname } from "node:path";
|
|
311
|
+
async function statusCommand() {
|
|
312
|
+
const configPath = getConfigPath();
|
|
313
|
+
const config = readConfig();
|
|
314
|
+
console.log(`ccusage-tracker status
|
|
315
|
+
`);
|
|
316
|
+
if (existsSync3(configPath)) {
|
|
317
|
+
console.log(`Config: ${configPath} (exists)`);
|
|
318
|
+
} else {
|
|
319
|
+
console.log(`Config: ${configPath} (not found)`);
|
|
320
|
+
console.log("\nRun `tracker setup` to configure.");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (config) {
|
|
324
|
+
console.log(` Member: ${config.member_name}`);
|
|
325
|
+
console.log(` Server: ${config.server_url}`);
|
|
326
|
+
console.log(` Team Key: ${config.team_key.slice(0, 15)}...`);
|
|
327
|
+
}
|
|
328
|
+
const hookInstalled = isHookInstalled();
|
|
329
|
+
console.log(`
|
|
330
|
+
Hook: ${hookInstalled ? "installed" : "not installed"}`);
|
|
331
|
+
if (config) {
|
|
332
|
+
try {
|
|
333
|
+
const res = await fetch(`${config.server_url}/api/health`, {
|
|
334
|
+
signal: AbortSignal.timeout(5000)
|
|
335
|
+
});
|
|
336
|
+
const body = await res.json();
|
|
337
|
+
if (body.ok) {
|
|
338
|
+
console.log(`Server: reachable (v${body.version || "unknown"})`);
|
|
339
|
+
} else {
|
|
340
|
+
console.log("Server: responded but not healthy");
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
console.log("Server: unreachable");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const bufferPath = join3(dirname(configPath), "buffer.jsonl");
|
|
347
|
+
if (existsSync3(bufferPath)) {
|
|
348
|
+
const content = readFileSync3(bufferPath, "utf-8").trim();
|
|
349
|
+
const lineCount = content ? content.split(`
|
|
350
|
+
`).length : 0;
|
|
351
|
+
console.log(`Buffer: ${lineCount} pending entr${lineCount === 1 ? "y" : "ies"} (${bufferPath})`);
|
|
352
|
+
} else {
|
|
353
|
+
console.log("Buffer: none");
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const result = Bun.spawnSync(["ccusage", "--version"]);
|
|
357
|
+
const version = new TextDecoder().decode(result.stdout).trim();
|
|
358
|
+
console.log(`ccusage: installed (${version || "version unknown"})`);
|
|
359
|
+
} catch {
|
|
360
|
+
console.log("ccusage: not found (install with: npx ccusage@latest)");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/index.ts
|
|
365
|
+
var args = process.argv.slice(2);
|
|
366
|
+
var command = args[0];
|
|
367
|
+
switch (command) {
|
|
368
|
+
case "setup":
|
|
369
|
+
await setupCommand();
|
|
370
|
+
break;
|
|
371
|
+
case "report":
|
|
372
|
+
await reportCommand(args.slice(1));
|
|
373
|
+
break;
|
|
374
|
+
case "status":
|
|
375
|
+
await statusCommand();
|
|
376
|
+
break;
|
|
377
|
+
default:
|
|
378
|
+
console.log(`ccusage-tracker CLI
|
|
379
|
+
`);
|
|
380
|
+
console.log(`Usage: tracker <command>
|
|
381
|
+
`);
|
|
382
|
+
console.log("Commands:");
|
|
383
|
+
console.log(" setup Configure hook and server connection");
|
|
384
|
+
console.log(" report View team token usage report");
|
|
385
|
+
console.log(" status Check current configuration status");
|
|
386
|
+
process.exit(command ? 1 : 0);
|
|
387
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ccusage-tracker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for ccusage-tracker — install Claude Code SessionStart/SessionEnd hooks to report token usage to a self-hosted team tracker.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ccusage-tracker": "dist/index.js",
|
|
8
|
+
"tracker": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "bun build src/index.ts --outdir dist --target node",
|
|
16
|
+
"test": "bun test",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepublishOnly": "bun run build"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/ericcai0814/ccusage-tracker.git",
|
|
23
|
+
"directory": "packages/cli"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/ericcai0814/ccusage-tracker#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/ericcai0814/ccusage-tracker/issues"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"claude-code",
|
|
35
|
+
"claude",
|
|
36
|
+
"ccusage",
|
|
37
|
+
"tracker",
|
|
38
|
+
"usage",
|
|
39
|
+
"tokens",
|
|
40
|
+
"cli",
|
|
41
|
+
"hook"
|
|
42
|
+
],
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/bun": "latest",
|
|
48
|
+
"bun-types": "latest",
|
|
49
|
+
"typescript": "^5.8.0"
|
|
50
|
+
}
|
|
51
|
+
}
|