ccusage-collector 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/.env.example ADDED
@@ -0,0 +1,9 @@
1
+ # Example usage:
2
+ # npx ccusage-collector --api-key=your-key --endpoint=https://example.com/api/usage-sync
3
+ # npx ccusage-collector --api-key=your-key --endpoint=https://example.com/api/usage-sync --schedule="0 */4 * * *"
4
+
5
+ # API endpoint for syncing usage data
6
+ API_ENDPOINT=https://example.com/api/usage-sync
7
+
8
+ # API key for authentication
9
+ API_KEY=your-api-key-here
@@ -0,0 +1,7 @@
1
+
2
+ 
3
+ > ccusage-collector@0.1.0 lint /Users/didi/Documents/Code/MyCCusage/packages/ccusage-collector
4
+ > eslint . --max-warnings 0
5
+
6
+ sh: eslint: command not found
7
+  ELIFECYCLE  Command failed.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Richard Wang
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,242 @@
1
+ # ccusage-collector
2
+
3
+ A command-line tool for collecting Claude Code usage statistics and syncing them to your self-hosted My Claude Code Usage Dashboard.
4
+
5
+ ## Overview
6
+
7
+ This package automatically collects your local Claude Code usage data and syncs it to your dashboard, giving you insights into your AI-assisted development workflow.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install -g ccusage-collector
13
+ npm install -g pm2
14
+ ```
15
+
16
+ ## Prerequisites
17
+
18
+ - Node.js ≥20
19
+ - PM2 process manager (for reliable background execution)
20
+ - Claude Code CLI installed and configured
21
+ - Access to a deployed My Claude Code Usage Dashboard
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Basic sync (run once)
26
+
27
+ ```bash
28
+ ccusage-collector --api-key=your-api-key --endpoint=https://example.com/api/usage-sync
29
+ ```
30
+
31
+ ### 2. Scheduled sync (recommended - runs in background)
32
+
33
+ ```bash
34
+ pm2 start ccusage-collector -- --api-key=your-api-key --endpoint=https://example.com/api/usage-sync --schedule="0 */4 * * *"
35
+ ```
36
+
37
+ ### 3. Test data collection (dry run)
38
+
39
+ ```bash
40
+ ccusage-collector --api-key=test --endpoint=https://example.com/api/usage-sync --dry-run
41
+ ```
42
+
43
+ ## CLI Options
44
+
45
+ | Option | Description | Required | Default |
46
+ |--------|-------------|----------|---------|
47
+ | `-k, --api-key <key>` | API key for authentication | Yes | - |
48
+ | `-e, --endpoint <url>` | API endpoint URL | Yes | - |
49
+ | `-s, --schedule <cron>` | Cron schedule for periodic sync | No | - |
50
+ | `-r, --max-retries <number>` | Maximum retry attempts | No | 3 |
51
+ | `-d, --retry-delay <ms>` | Delay between retries (ms) | No | 1000 |
52
+ | `--dry-run` | Collect data but don't sync | No | false |
53
+ | `-h, --help` | Show help | No | - |
54
+ | `-V, --version` | Show version | No | - |
55
+
56
+ ## Usage Examples
57
+
58
+ ### One-time sync
59
+ ```bash
60
+ ccusage-collector --api-key=sk-1234567890abcdef --endpoint=https://myccusage.example.com/api/usage-sync
61
+ ```
62
+
63
+ ### Scheduled sync (every 6 hours) - Background execution
64
+ ```bash
65
+ pm2 start ccusage-collector -- \
66
+ --api-key=sk-1234567890abcdef \
67
+ --endpoint=https://myccusage.example.com/api/usage-sync \
68
+ --schedule="0 */6 * * *"
69
+ ```
70
+
71
+ ### Test without syncing
72
+ ```bash
73
+ ccusage-collector \
74
+ --api-key=test \
75
+ --endpoint=https://myccusage.example.com/api/usage-sync \
76
+ --dry-run
77
+ ```
78
+
79
+ ## Cron Schedule Examples
80
+
81
+ | Schedule | Description |
82
+ |----------|-------------|
83
+ | `"0 */4 * * *"` | Every 4 hours |
84
+ | `"*/10 * * * *"` | Every 10 minutes |
85
+ | `"0 0 * * *"` | Daily at midnight |
86
+ | `"0 */1 * * *"` | Every hour |
87
+ | `"0 9,17 * * 1-5"` | 9 AM and 5 PM on weekdays |
88
+
89
+ ## Setup Guide
90
+
91
+ ### 1. Deploy the Dashboard
92
+ First, deploy your own instance of My Claude Code Usage Dashboard:
93
+ - Clone the repository
94
+ - Set up PostgreSQL database
95
+ - Configure environment variables
96
+ - Deploy to your preferred platform
97
+
98
+ ### 2. Get API Key
99
+ Set the `API_KEY` environment variable in your dashboard deployment:
100
+ ```env
101
+ API_KEY=your-secret-api-key-here
102
+ ```
103
+
104
+ ### 3. Install and Run Collector
105
+ ```bash
106
+ npm install -g ccusage-collector
107
+ npm install -g pm2
108
+
109
+ # Start background sync
110
+ pm2 start ccusage-collector -- --api-key=your-secret-api-key-here --endpoint=https://your-domain.com/api/usage-sync --schedule="0 */4 * * *"
111
+ ```
112
+
113
+ ## Data Collection
114
+
115
+ The collector:
116
+ - Uses `npx ccusage daily --json` to gather usage statistics
117
+ - Collects historical data (not just recent usage)
118
+ - Syncs complete usage records to your dashboard
119
+ - Supports upsert operations (updates existing records)
120
+
121
+ ### Data Format
122
+ The collector syncs:
123
+ - Daily usage metrics (tokens, costs, models)
124
+ - Token breakdowns (input, output, cache creation/read)
125
+ - Model usage statistics
126
+ - Raw usage data for detailed analysis
127
+
128
+ ## Error Handling
129
+
130
+ - **Network failures**: Automatic retry with exponential backoff
131
+ - **Authentication errors**: Immediate failure (no retry on 401/403)
132
+ - **Individual record failures**: Continues processing other records
133
+ - **Detailed logging**: Clear error messages and debugging info
134
+
135
+ ## Process Management with PM2
136
+
137
+ For reliable background execution, use PM2 to manage the collector process:
138
+
139
+ ### Start Background Sync
140
+ ```bash
141
+ pm2 start ccusage-collector -- --api-key=your-key --endpoint=https://your-domain.com/api/usage-sync --schedule="0 */4 * * *"
142
+ ```
143
+
144
+ ### Check Status
145
+ ```bash
146
+ pm2 list # Show all processes
147
+ pm2 show ccusage-collector # Show detailed info
148
+ pm2 logs ccusage-collector # View logs
149
+ pm2 monit # Real-time monitoring
150
+ ```
151
+
152
+ ### Control Process
153
+ ```bash
154
+ pm2 stop ccusage-collector # Stop process
155
+ pm2 restart ccusage-collector # Restart process
156
+ pm2 delete ccusage-collector # Remove process
157
+ ```
158
+
159
+ ### Auto-start on Boot
160
+ ```bash
161
+ pm2 startup # Generate startup script
162
+ pm2 save # Save current process list
163
+ ```
164
+
165
+ ### Benefits of PM2
166
+ - **Automatic restart** if process crashes
167
+ - **Background execution** - no need to keep terminal open
168
+ - **Process monitoring** and logging
169
+ - **Prevents duplicate instances** - safe to run multiple times
170
+ - **Cross-platform** - works on Windows, Linux, and macOS
171
+
172
+ ## Troubleshooting
173
+
174
+ ### Common Issues
175
+
176
+ **"ccusage command not found"**
177
+ - Ensure Claude Code CLI is installed and in PATH
178
+ - Run `npx ccusage --help` to verify installation
179
+
180
+ **"Authentication failed"**
181
+ - Verify API key matches your dashboard configuration
182
+ - Check that API key is correctly set in dashboard environment
183
+
184
+ **"Connection refused"**
185
+ - Verify endpoint URL is correct and accessible
186
+ - Check that your dashboard is running and deployed
187
+
188
+ **"Invalid cron expression"**
189
+ - Use online cron validators to test expressions
190
+ - Ensure cron format is correct (5 fields: minute hour day month weekday)
191
+
192
+ ### Debug Mode
193
+ ```bash
194
+ # Enable verbose logging
195
+ DEBUG=ccusage-collector ccusage-collector --api-key=... --endpoint=...
196
+ ```
197
+
198
+ ## Contributing
199
+
200
+ 1. Fork the repository
201
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
202
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
203
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
204
+ 5. Open a Pull Request
205
+
206
+ ## Development
207
+
208
+ ```bash
209
+ # Clone the main repository
210
+ git clone https://github.com/i-richardwang/MyCCusage.git
211
+ cd MyCCusage/packages/ccusage-collector
212
+
213
+ # Install dependencies
214
+ pnpm install
215
+
216
+ # Build the package
217
+ pnpm build
218
+
219
+ # Test CLI locally
220
+ pnpm cli --help
221
+ pnpm cli --dry-run --api-key=test --endpoint=http://localhost:3000
222
+
223
+ # Run type checking
224
+ pnpm typecheck
225
+
226
+ # Run linting
227
+ pnpm lint
228
+ ```
229
+
230
+ ## License
231
+
232
+ MIT License - see [LICENSE](LICENSE) file for details.
233
+
234
+ ## Related Projects
235
+
236
+ - [My Claude Code Usage Dashboard](https://github.com/i-richardwang/MyCCusage) - The main dashboard application
237
+ - [Claude Code](https://claude.ai/code) - The AI-powered coding assistant
238
+
239
+ ## Support
240
+
241
+ - 🐛 [Report bugs](https://github.com/i-richardwang/MyCCusage/issues)
242
+ - 💡 [Request features](https://github.com/i-richardwang/MyCCusage/issues)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import * as cron from 'node-cron';
4
+ import { UsageCollector } from './collector.js';
5
+ program
6
+ .name('ccusage-collector')
7
+ .description('Collect and sync Claude Code usage statistics')
8
+ .version('0.1.0');
9
+ program
10
+ .option('-k, --api-key <key>', 'API key for authentication')
11
+ .option('-e, --endpoint <url>', 'API endpoint URL')
12
+ .option('-s, --schedule <cron>', `Cron schedule for periodic sync
13
+ Examples:
14
+ "0 */4 * * *" - Every 4 hours
15
+ "*/10 * * * *" - Every 10 minutes`)
16
+ .option('-r, --max-retries <number>', 'Maximum number of retry attempts', '3')
17
+ .option('-d, --retry-delay <ms>', 'Delay between retry attempts in milliseconds', '1000')
18
+ .option('--dry-run', 'Collect data but don\'t sync to server')
19
+ .action(async (options) => {
20
+ // Validate required options
21
+ if (!options.apiKey) {
22
+ console.error('Error: API key is required (--api-key)');
23
+ process.exit(1);
24
+ }
25
+ if (!options.endpoint) {
26
+ console.error('Error: API endpoint is required (--endpoint)');
27
+ process.exit(1);
28
+ }
29
+ const config = {
30
+ apiKey: options.apiKey,
31
+ endpoint: options.endpoint,
32
+ maxRetries: parseInt(options.maxRetries),
33
+ retryDelay: parseInt(options.retryDelay)
34
+ };
35
+ const collector = new UsageCollector(config);
36
+ if (options.dryRun) {
37
+ console.log('Dry run mode: collecting data only');
38
+ try {
39
+ const data = await collector.collectUsageData();
40
+ console.log('Collected data:', JSON.stringify(data, null, 2));
41
+ }
42
+ catch (error) {
43
+ console.error('Failed to collect data:', error instanceof Error ? error.message : error);
44
+ process.exit(1);
45
+ }
46
+ return;
47
+ }
48
+ if (options.schedule) {
49
+ console.log(`Starting scheduled sync with cron: ${options.schedule}`);
50
+ // Validate cron expression
51
+ if (!cron.validate(options.schedule)) {
52
+ console.error('Error: Invalid cron expression');
53
+ process.exit(1);
54
+ }
55
+ // Run once immediately
56
+ console.log('Running initial sync...');
57
+ await collector.run();
58
+ // Schedule periodic runs
59
+ cron.schedule(options.schedule, async () => {
60
+ console.log(`\n[${new Date().toISOString()}] Running scheduled sync...`);
61
+ await collector.run();
62
+ });
63
+ console.log('Scheduled sync is running. Press Ctrl+C to stop.');
64
+ // Keep the process running
65
+ process.on('SIGINT', () => {
66
+ console.log('\nStopping scheduled sync...');
67
+ process.exit(0);
68
+ });
69
+ }
70
+ else {
71
+ // Run once
72
+ await collector.run();
73
+ }
74
+ });
75
+ program.parse();
76
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { UsageData, SyncResult, CollectorConfig } from './types.js';
2
+ export declare class UsageCollector {
3
+ private config;
4
+ constructor(config: CollectorConfig);
5
+ collectUsageData(): Promise<UsageData>;
6
+ syncData(data: UsageData): Promise<SyncResult>;
7
+ run(): Promise<void>;
8
+ }
9
+ //# sourceMappingURL=collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIxE,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,EAAE,eAAe;IAQ7B,gBAAgB,IAAI,OAAO,CAAC,SAAS,CAAC;IAqBtC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAqE9C,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAU3B"}
@@ -0,0 +1,102 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import axios from 'axios';
4
+ const execAsync = promisify(exec);
5
+ export class UsageCollector {
6
+ config;
7
+ constructor(config) {
8
+ this.config = {
9
+ maxRetries: 3,
10
+ retryDelay: 1000,
11
+ ...config
12
+ };
13
+ }
14
+ async collectUsageData() {
15
+ try {
16
+ console.log('Collecting usage data...');
17
+ const { stdout } = await execAsync('npx ccusage daily --json');
18
+ const data = JSON.parse(stdout);
19
+ if (!data.daily || !Array.isArray(data.daily)) {
20
+ throw new Error('Invalid usage data format');
21
+ }
22
+ console.log(`Collected ${data.daily.length} daily records`);
23
+ return data;
24
+ }
25
+ catch (error) {
26
+ if (error instanceof Error) {
27
+ throw new Error(`Failed to collect usage data: ${error.message}`);
28
+ }
29
+ throw new Error('Failed to collect usage data: Unknown error');
30
+ }
31
+ }
32
+ async syncData(data) {
33
+ const { endpoint, apiKey, maxRetries = 3, retryDelay = 1000 } = this.config;
34
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
35
+ try {
36
+ console.log(`Syncing data (attempt ${attempt}/${maxRetries})...`);
37
+ const response = await axios.post(endpoint, data, {
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'x-api-key': apiKey
41
+ },
42
+ timeout: 30000
43
+ });
44
+ const result = response.data;
45
+ if (result.success) {
46
+ console.log(`Successfully synced ${result.processed} records`);
47
+ // Log any errors for individual records
48
+ const errors = result.results.filter(r => r.status === 'error');
49
+ if (errors.length > 0) {
50
+ console.warn(`${errors.length} records had errors:`);
51
+ errors.forEach(error => {
52
+ console.warn(` - ${error.date}: ${error.message}`);
53
+ });
54
+ }
55
+ return result;
56
+ }
57
+ else {
58
+ throw new Error('Sync failed: ' + JSON.stringify(result));
59
+ }
60
+ }
61
+ catch (error) {
62
+ const isLastAttempt = attempt === maxRetries;
63
+ if (axios.isAxiosError(error)) {
64
+ const status = error.response?.status;
65
+ const message = error.response?.data?.error || error.message;
66
+ console.error(`Sync failed (attempt ${attempt}/${maxRetries}): ${status} - ${message}`);
67
+ // Don't retry on authentication errors
68
+ if (status === 401 || status === 403) {
69
+ throw new Error(`Authentication failed: ${message}`);
70
+ }
71
+ if (isLastAttempt) {
72
+ throw new Error(`Sync failed after ${maxRetries} attempts: ${message}`);
73
+ }
74
+ }
75
+ else {
76
+ console.error(`Sync failed (attempt ${attempt}/${maxRetries}):`, error);
77
+ if (isLastAttempt) {
78
+ throw error;
79
+ }
80
+ }
81
+ // Wait before retrying
82
+ if (!isLastAttempt) {
83
+ console.log(`Retrying in ${retryDelay}ms...`);
84
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
85
+ }
86
+ }
87
+ }
88
+ throw new Error('Sync failed after all retries');
89
+ }
90
+ async run() {
91
+ try {
92
+ const data = await this.collectUsageData();
93
+ await this.syncData(data);
94
+ console.log('Usage data sync completed successfully');
95
+ }
96
+ catch (error) {
97
+ console.error('Usage data sync failed:', error instanceof Error ? error.message : error);
98
+ process.exit(1);
99
+ }
100
+ }
101
+ }
102
+ //# sourceMappingURL=collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collector.js","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { UsageCollector } from './collector.js';
2
+ export type { DailyUsageRecord, UsageData, SyncResult, CollectorConfig } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,eAAe,EAChB,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { UsageCollector } from './collector.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,46 @@
1
+ export interface DailyUsageRecord {
2
+ date: string;
3
+ inputTokens: number;
4
+ outputTokens: number;
5
+ cacheCreationTokens: number;
6
+ cacheReadTokens: number;
7
+ totalTokens: number;
8
+ totalCost: number;
9
+ modelsUsed: string[];
10
+ modelBreakdowns: Array<{
11
+ modelName: string;
12
+ inputTokens: number;
13
+ outputTokens: number;
14
+ cacheCreationTokens: number;
15
+ cacheReadTokens: number;
16
+ cost: number;
17
+ }>;
18
+ }
19
+ export interface UsageData {
20
+ daily: DailyUsageRecord[];
21
+ totals: {
22
+ inputTokens: number;
23
+ outputTokens: number;
24
+ cacheCreationTokens: number;
25
+ cacheReadTokens: number;
26
+ totalCost: number;
27
+ totalTokens: number;
28
+ };
29
+ }
30
+ export interface SyncResult {
31
+ success: boolean;
32
+ processed: number;
33
+ results: Array<{
34
+ date: string;
35
+ status: 'success' | 'error';
36
+ message?: string;
37
+ }>;
38
+ }
39
+ export interface CollectorConfig {
40
+ endpoint: string;
41
+ apiKey: string;
42
+ schedule?: string;
43
+ maxRetries?: number;
44
+ retryDelay?: number;
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,eAAe,EAAE,KAAK,CAAC;QACrB,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,mBAAmB,EAAE,MAAM,CAAA;QAC3B,eAAe,EAAE,MAAM,CAAA;QACvB,IAAI,EAAE,MAAM,CAAA;KACb,CAAC,CAAA;CACH;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACzB,MAAM,EAAE;QACN,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,mBAAmB,EAAE,MAAM,CAAA;QAC3B,eAAe,EAAE,MAAM,CAAA;QACvB,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,SAAS,GAAG,OAAO,CAAA;QAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,CAAC,CAAA;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import baseConfig from '@workspace/eslint-config/base.js'
2
+
3
+ export default [
4
+ ...baseConfig,
5
+ {
6
+ languageOptions: {
7
+ parserOptions: {
8
+ project: true,
9
+ tsconfigRootDir: import.meta.dirname
10
+ }
11
+ }
12
+ }
13
+ ]
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "ccusage-collector",
3
+ "version": "0.1.0",
4
+ "description": "Local collector for Claude Code usage statistics",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "ccusage-collector": "dist/cli.js"
9
+ },
10
+ "keywords": [
11
+ "claude",
12
+ "claude-code",
13
+ "usage",
14
+ "collector",
15
+ "statistics",
16
+ "cli",
17
+ "monitoring",
18
+ "dashboard"
19
+ ],
20
+ "author": "Richard Wang",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/i-richardwang/MyCCusage.git",
24
+ "directory": "packages/ccusage-collector"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/i-richardwang/MyCCusage/issues"
28
+ },
29
+ "homepage": "https://github.com/i-richardwang/MyCCusage#readme",
30
+ "license": "MIT",
31
+ "devDependencies": {
32
+ "@types/node": "^20",
33
+ "@types/node-cron": "^3.0.11",
34
+ "tsx": "^4.20.3",
35
+ "typescript": "^5.8.3",
36
+ "@workspace/typescript-config": "0.0.0",
37
+ "@workspace/eslint-config": "0.0.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "axios": "^1.10.0",
44
+ "commander": "^14.0.0",
45
+ "dotenv": "^17.1.0",
46
+ "node-cron": "^4.2.0"
47
+ },
48
+ "scripts": {
49
+ "build": "tsc",
50
+ "dev": "tsx src/index.ts",
51
+ "start": "node dist/index.js",
52
+ "cli": "tsx src/cli.ts",
53
+ "lint": "eslint . --max-warnings 0",
54
+ "typecheck": "tsc --noEmit"
55
+ }
56
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander'
4
+ import * as cron from 'node-cron'
5
+ import { UsageCollector } from './collector.js'
6
+ import type { CollectorConfig } from './types.js'
7
+
8
+ program
9
+ .name('ccusage-collector')
10
+ .description('Collect and sync Claude Code usage statistics')
11
+ .version('0.1.0')
12
+
13
+ program
14
+ .option('-k, --api-key <key>', 'API key for authentication')
15
+ .option('-e, --endpoint <url>', 'API endpoint URL')
16
+ .option('-s, --schedule <cron>', `Cron schedule for periodic sync
17
+ Examples:
18
+ "0 */4 * * *" - Every 4 hours
19
+ "*/10 * * * *" - Every 10 minutes`)
20
+ .option('-r, --max-retries <number>', 'Maximum number of retry attempts', '3')
21
+ .option('-d, --retry-delay <ms>', 'Delay between retry attempts in milliseconds', '1000')
22
+ .option('--dry-run', 'Collect data but don\'t sync to server')
23
+ .action(async (options) => {
24
+ // Validate required options
25
+ if (!options.apiKey) {
26
+ console.error('Error: API key is required (--api-key)')
27
+ process.exit(1)
28
+ }
29
+
30
+ if (!options.endpoint) {
31
+ console.error('Error: API endpoint is required (--endpoint)')
32
+ process.exit(1)
33
+ }
34
+
35
+ const config: CollectorConfig = {
36
+ apiKey: options.apiKey,
37
+ endpoint: options.endpoint,
38
+ maxRetries: parseInt(options.maxRetries),
39
+ retryDelay: parseInt(options.retryDelay)
40
+ }
41
+
42
+ const collector = new UsageCollector(config)
43
+
44
+ if (options.dryRun) {
45
+ console.log('Dry run mode: collecting data only')
46
+ try {
47
+ const data = await collector.collectUsageData()
48
+ console.log('Collected data:', JSON.stringify(data, null, 2))
49
+ } catch (error) {
50
+ console.error('Failed to collect data:', error instanceof Error ? error.message : error)
51
+ process.exit(1)
52
+ }
53
+ return
54
+ }
55
+
56
+ if (options.schedule) {
57
+ console.log(`Starting scheduled sync with cron: ${options.schedule}`)
58
+
59
+ // Validate cron expression
60
+ if (!cron.validate(options.schedule)) {
61
+ console.error('Error: Invalid cron expression')
62
+ process.exit(1)
63
+ }
64
+
65
+ // Run once immediately
66
+ console.log('Running initial sync...')
67
+ await collector.run()
68
+
69
+ // Schedule periodic runs
70
+ cron.schedule(options.schedule, async () => {
71
+ console.log(`\n[${new Date().toISOString()}] Running scheduled sync...`)
72
+ await collector.run()
73
+ })
74
+
75
+ console.log('Scheduled sync is running. Press Ctrl+C to stop.')
76
+
77
+ // Keep the process running
78
+ process.on('SIGINT', () => {
79
+ console.log('\nStopping scheduled sync...')
80
+ process.exit(0)
81
+ })
82
+ } else {
83
+ // Run once
84
+ await collector.run()
85
+ }
86
+ })
87
+
88
+ program.parse()
@@ -0,0 +1,119 @@
1
+ import { exec } from 'child_process'
2
+ import { promisify } from 'util'
3
+ import axios from 'axios'
4
+ import type { UsageData, SyncResult, CollectorConfig } from './types.js'
5
+
6
+ const execAsync = promisify(exec)
7
+
8
+ export class UsageCollector {
9
+ private config: CollectorConfig
10
+
11
+ constructor(config: CollectorConfig) {
12
+ this.config = {
13
+ maxRetries: 3,
14
+ retryDelay: 1000,
15
+ ...config
16
+ }
17
+ }
18
+
19
+ async collectUsageData(): Promise<UsageData> {
20
+ try {
21
+ console.log('Collecting usage data...')
22
+ const { stdout } = await execAsync('npx ccusage daily --json')
23
+
24
+ const data: UsageData = JSON.parse(stdout)
25
+
26
+ if (!data.daily || !Array.isArray(data.daily)) {
27
+ throw new Error('Invalid usage data format')
28
+ }
29
+
30
+ console.log(`Collected ${data.daily.length} daily records`)
31
+ return data
32
+ } catch (error) {
33
+ if (error instanceof Error) {
34
+ throw new Error(`Failed to collect usage data: ${error.message}`)
35
+ }
36
+ throw new Error('Failed to collect usage data: Unknown error')
37
+ }
38
+ }
39
+
40
+ async syncData(data: UsageData): Promise<SyncResult> {
41
+ const { endpoint, apiKey, maxRetries = 3, retryDelay = 1000 } = this.config
42
+
43
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
44
+ try {
45
+ console.log(`Syncing data (attempt ${attempt}/${maxRetries})...`)
46
+
47
+ const response = await axios.post(endpoint, data, {
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ 'x-api-key': apiKey
51
+ },
52
+ timeout: 30000
53
+ })
54
+
55
+ const result: SyncResult = response.data
56
+
57
+ if (result.success) {
58
+ console.log(`Successfully synced ${result.processed} records`)
59
+
60
+ // Log any errors for individual records
61
+ const errors = result.results.filter(r => r.status === 'error')
62
+ if (errors.length > 0) {
63
+ console.warn(`${errors.length} records had errors:`)
64
+ errors.forEach(error => {
65
+ console.warn(` - ${error.date}: ${error.message}`)
66
+ })
67
+ }
68
+
69
+ return result
70
+ } else {
71
+ throw new Error('Sync failed: ' + JSON.stringify(result))
72
+ }
73
+ } catch (error) {
74
+ const isLastAttempt = attempt === maxRetries
75
+
76
+ if (axios.isAxiosError(error)) {
77
+ const status = error.response?.status
78
+ const message = error.response?.data?.error || error.message
79
+
80
+ console.error(`Sync failed (attempt ${attempt}/${maxRetries}): ${status} - ${message}`)
81
+
82
+ // Don't retry on authentication errors
83
+ if (status === 401 || status === 403) {
84
+ throw new Error(`Authentication failed: ${message}`)
85
+ }
86
+
87
+ if (isLastAttempt) {
88
+ throw new Error(`Sync failed after ${maxRetries} attempts: ${message}`)
89
+ }
90
+ } else {
91
+ console.error(`Sync failed (attempt ${attempt}/${maxRetries}):`, error)
92
+
93
+ if (isLastAttempt) {
94
+ throw error
95
+ }
96
+ }
97
+
98
+ // Wait before retrying
99
+ if (!isLastAttempt) {
100
+ console.log(`Retrying in ${retryDelay}ms...`)
101
+ await new Promise(resolve => setTimeout(resolve, retryDelay))
102
+ }
103
+ }
104
+ }
105
+
106
+ throw new Error('Sync failed after all retries')
107
+ }
108
+
109
+ async run(): Promise<void> {
110
+ try {
111
+ const data = await this.collectUsageData()
112
+ await this.syncData(data)
113
+ console.log('Usage data sync completed successfully')
114
+ } catch (error) {
115
+ console.error('Usage data sync failed:', error instanceof Error ? error.message : error)
116
+ process.exit(1)
117
+ }
118
+ }
119
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { UsageCollector } from './collector.js'
2
+ export type {
3
+ DailyUsageRecord,
4
+ UsageData,
5
+ SyncResult,
6
+ CollectorConfig
7
+ } from './types.js'
package/src/types.ts ADDED
@@ -0,0 +1,48 @@
1
+ export interface DailyUsageRecord {
2
+ date: string
3
+ inputTokens: number
4
+ outputTokens: number
5
+ cacheCreationTokens: number
6
+ cacheReadTokens: number
7
+ totalTokens: number
8
+ totalCost: number
9
+ modelsUsed: string[]
10
+ modelBreakdowns: Array<{
11
+ modelName: string
12
+ inputTokens: number
13
+ outputTokens: number
14
+ cacheCreationTokens: number
15
+ cacheReadTokens: number
16
+ cost: number
17
+ }>
18
+ }
19
+
20
+ export interface UsageData {
21
+ daily: DailyUsageRecord[]
22
+ totals: {
23
+ inputTokens: number
24
+ outputTokens: number
25
+ cacheCreationTokens: number
26
+ cacheReadTokens: number
27
+ totalCost: number
28
+ totalTokens: number
29
+ }
30
+ }
31
+
32
+ export interface SyncResult {
33
+ success: boolean
34
+ processed: number
35
+ results: Array<{
36
+ date: string
37
+ status: 'success' | 'error'
38
+ message?: string
39
+ }>
40
+ }
41
+
42
+ export interface CollectorConfig {
43
+ endpoint: string
44
+ apiKey: string
45
+ schedule?: string
46
+ maxRetries?: number
47
+ retryDelay?: number
48
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "@workspace/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "module": "ESNext",
10
+ "moduleResolution": "node",
11
+ "target": "ES2022",
12
+ "lib": ["ES2022"],
13
+ "allowSyntheticDefaultImports": true,
14
+ "esModuleInterop": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }