ai-heatmap 1.0.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/.github/workflows/deploy.yml +50 -0
- package/README.md +208 -0
- package/bin/cli.mjs +60 -0
- package/bin/init.mjs +120 -0
- package/bin/push.mjs +59 -0
- package/index.html +12 -0
- package/package.json +44 -0
- package/public/data.json +2236 -0
- package/public/heatmap.svg +410 -0
- package/scripts/generate-svg.mjs +169 -0
- package/scripts/generate.mjs +73 -0
- package/scripts/serve-svg.mjs +172 -0
- package/src/App.css +94 -0
- package/src/App.tsx +225 -0
- package/src/main.tsx +10 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +21 -0
- package/vite.config.ts +7 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Deploy AI Heatmap
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
paths:
|
|
8
|
+
- "public/data.json"
|
|
9
|
+
- "src/**"
|
|
10
|
+
- "index.html"
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
pages: write
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: pages
|
|
19
|
+
cancel-in-progress: false
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- uses: actions/setup-node@v4
|
|
28
|
+
with:
|
|
29
|
+
node-version: 20
|
|
30
|
+
|
|
31
|
+
- run: npm ci
|
|
32
|
+
|
|
33
|
+
- name: Generate static SVG from data.json
|
|
34
|
+
run: npm run generate:svg
|
|
35
|
+
|
|
36
|
+
- run: npm run build
|
|
37
|
+
|
|
38
|
+
- uses: actions/upload-pages-artifact@v3
|
|
39
|
+
with:
|
|
40
|
+
path: dist
|
|
41
|
+
|
|
42
|
+
deploy:
|
|
43
|
+
needs: build
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
environment:
|
|
46
|
+
name: github-pages
|
|
47
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
48
|
+
steps:
|
|
49
|
+
- id: deployment
|
|
50
|
+
uses: actions/deploy-pages@v4
|
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# AI Heatmap
|
|
2
|
+
|
|
3
|
+
GitHub-style heatmap for your AI usage costs. Powered by [ccusage](https://github.com/ryoppippi/ccusage) + [react-activity-calendar](https://github.com/grubersjoe/react-activity-calendar).
|
|
4
|
+
|
|
5
|
+
## Preview
|
|
6
|
+
|
|
7
|
+
<!-- Replace YOUR_VERCEL_DOMAIN with your actual Vercel deployment URL -->
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
### Variations
|
|
11
|
+
|
|
12
|
+
```markdown
|
|
13
|
+
<!-- Dark mode with full stats -->
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
<!-- Blue theme, heatmap + stats only -->
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
<!-- Heatmap only (clean embed) -->
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
<!-- Custom date range -->
|
|
23
|
+

|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Generate data from local ccusage logs
|
|
30
|
+
npx ai-heatmap generate
|
|
31
|
+
|
|
32
|
+
# Init a new GitHub Pages repo
|
|
33
|
+
npx ai-heatmap init my-ai-heatmap
|
|
34
|
+
|
|
35
|
+
# Push data to repo
|
|
36
|
+
npx ai-heatmap push --repo owner/my-ai-heatmap
|
|
37
|
+
|
|
38
|
+
# Generate + push in one step
|
|
39
|
+
npx ai-heatmap update --repo owner/my-ai-heatmap
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## SVG API (Vercel)
|
|
43
|
+
|
|
44
|
+
Deploy this repo to Vercel for a dynamic SVG endpoint. Embed it in any README:
|
|
45
|
+
|
|
46
|
+
```markdown
|
|
47
|
+

|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The SVG is generated on each request from `public/data.json`, so you can control the output with query parameters.
|
|
51
|
+
|
|
52
|
+
### SVG API Options
|
|
53
|
+
|
|
54
|
+
| Parameter | Default | Description |
|
|
55
|
+
|-----------|---------|-------------|
|
|
56
|
+
| `colorScheme` | `light` | Color scheme: `light`, `dark` |
|
|
57
|
+
| `theme` | same as `colorScheme` | Theme override: `light`, `dark`, `blue`, `orange`, `pink` |
|
|
58
|
+
| `blockSize` | `16` | Size of each day block in pixels |
|
|
59
|
+
| `blockMargin` | `4` | Gap between blocks in pixels |
|
|
60
|
+
| `blockRadius` | `3` | Border radius of blocks in pixels |
|
|
61
|
+
| `bg` | auto | Background color (e.g. `%23ffffff`). Auto: transparent (light) / `#0d1117` (dark) |
|
|
62
|
+
| `textColor` | auto | Text color. Auto: `#24292f` (light) / `#c9d1d9` (dark) |
|
|
63
|
+
| `start` | - | Filter start date (`YYYY-MM-DD`) |
|
|
64
|
+
| `end` | - | Filter end date (`YYYY-MM-DD`) |
|
|
65
|
+
| `stats` | `true` | Show stats section (daily avg, weekly avg, peak, active days) |
|
|
66
|
+
| `weekday` | `true` | Show average-by-weekday bar chart |
|
|
67
|
+
|
|
68
|
+
### SVG API Examples
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
# Default light theme with all sections
|
|
72
|
+
/api/heatmap
|
|
73
|
+
|
|
74
|
+
# Dark theme
|
|
75
|
+
/api/heatmap?colorScheme=dark
|
|
76
|
+
|
|
77
|
+
# Pink theme, heatmap only
|
|
78
|
+
/api/heatmap?theme=pink&stats=false&weekday=false
|
|
79
|
+
|
|
80
|
+
# Custom block size
|
|
81
|
+
/api/heatmap?blockSize=12&blockMargin=3&blockRadius=2
|
|
82
|
+
|
|
83
|
+
# Date range filter
|
|
84
|
+
/api/heatmap?start=2026-01-01&end=2026-02-18
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## GitHub Pages (Interactive)
|
|
88
|
+
|
|
89
|
+
The interactive version with tooltips is deployed via GitHub Pages. Tooltips show cost, tokens, cache hit rate, and per-model breakdown.
|
|
90
|
+
|
|
91
|
+
### GitHub Pages Options
|
|
92
|
+
|
|
93
|
+
All options are controlled via query string:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
https://owner.github.io/my-ai-heatmap/?colorScheme=dark&blockSize=14
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Parameter | Default | Description |
|
|
100
|
+
|-----------|---------|-------------|
|
|
101
|
+
| `colorScheme` | `light` | Color scheme: `light`, `dark` |
|
|
102
|
+
| `blockSize` | `12` | Size of each day block in pixels |
|
|
103
|
+
| `blockMargin` | `3` | Gap between blocks in pixels |
|
|
104
|
+
| `blockRadius` | `2` | Border radius of blocks in pixels |
|
|
105
|
+
| `fontSize` | `12` | Font size in pixels |
|
|
106
|
+
| `hideColorLegend` | `false` | Hide the color legend |
|
|
107
|
+
| `hideMonthLabels` | `false` | Hide month labels |
|
|
108
|
+
| `hideTotalCount` | `false` | Hide total count label |
|
|
109
|
+
| `showWeekdayLabels` | `true` | Show weekday labels (Mon, Wed, Fri) |
|
|
110
|
+
| `weekStart` | `0` | First day of week (0 = Sunday, 1 = Monday) |
|
|
111
|
+
| `start` | - | Filter start date (`YYYY-MM-DD`) |
|
|
112
|
+
| `end` | - | Filter end date (`YYYY-MM-DD`) |
|
|
113
|
+
|
|
114
|
+
### Available Themes (SVG API)
|
|
115
|
+
|
|
116
|
+
| Theme | Colors |
|
|
117
|
+
|-------|--------|
|
|
118
|
+
| `light` | `#ebedf0` `#c6e48b` `#7bc96f` `#239a3b` `#196127` |
|
|
119
|
+
| `dark` | `#161b22` `#0e4429` `#006d32` `#26a641` `#39d353` |
|
|
120
|
+
| `blue` | `#ebedf0` `#c0ddf9` `#73b3f3` `#3886e1` `#1b4f91` |
|
|
121
|
+
| `orange` | `#ebedf0` `#ffdf80` `#ffa742` `#e87d2f` `#ac5219` |
|
|
122
|
+
| `pink` | `#ebedf0` `#ffc0cb` `#ff69b4` `#ff1493` `#c71585` |
|
|
123
|
+
|
|
124
|
+
## Configuration
|
|
125
|
+
|
|
126
|
+
Customize the static SVG output by editing `heatmap.config.json` in the project root:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"colorScheme": "light",
|
|
131
|
+
"theme": "",
|
|
132
|
+
"blockSize": 16,
|
|
133
|
+
"blockMargin": 4,
|
|
134
|
+
"blockRadius": 3,
|
|
135
|
+
"bg": "",
|
|
136
|
+
"textColor": "",
|
|
137
|
+
"start": "",
|
|
138
|
+
"end": "",
|
|
139
|
+
"stats": true,
|
|
140
|
+
"weekday": true
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This config is used by `npm run generate:svg` to build `public/heatmap.svg`. Empty strings (`""`) use auto defaults.
|
|
145
|
+
|
|
146
|
+
For the Vercel dynamic API, use query parameters instead (they override config).
|
|
147
|
+
|
|
148
|
+
## Data Update
|
|
149
|
+
|
|
150
|
+
`ccusage` reads Claude Code usage logs from your **local machine**, so data must be generated locally and pushed to the repo.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Generate + push in one step
|
|
154
|
+
npx ai-heatmap update --repo owner/my-ai-heatmap
|
|
155
|
+
|
|
156
|
+
# Or manually
|
|
157
|
+
npx ai-heatmap generate
|
|
158
|
+
git add public/data.json && git commit -m "update data" && git push
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
For automated updates, use a local cron job or macOS LaunchAgent:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# crontab -e (runs daily at midnight)
|
|
165
|
+
0 0 * * * cd /path/to/ai-heatmap && npx ai-heatmap update --repo owner/my-ai-heatmap
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Deployment
|
|
169
|
+
|
|
170
|
+
### GitHub Pages
|
|
171
|
+
|
|
172
|
+
1. Enable GitHub Pages (Settings > Pages > Source: GitHub Actions)
|
|
173
|
+
2. Push `data.json` to `main` to trigger the first deploy
|
|
174
|
+
3. Manual deploy: Actions tab > "Deploy AI Heatmap" > "Run workflow"
|
|
175
|
+
|
|
176
|
+
### Vercel
|
|
177
|
+
|
|
178
|
+
1. Import this repo on [vercel.com](https://vercel.com)
|
|
179
|
+
2. Deploy (zero config — `vercel.json` included)
|
|
180
|
+
3. Use the deployed URL for dynamic SVG embeds
|
|
181
|
+
|
|
182
|
+
## Local Development
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm install
|
|
186
|
+
npm run generate # Generate data.json from ccusage
|
|
187
|
+
npm run dev # Vite dev server (interactive heatmap)
|
|
188
|
+
node scripts/serve-svg.mjs # Local SVG API on :3333
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Project Structure
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
ai-heatmap/
|
|
195
|
+
api/heatmap.ts # Vercel serverless SVG endpoint
|
|
196
|
+
bin/cli.mjs # CLI entrypoint
|
|
197
|
+
bin/init.mjs # Repo scaffolding
|
|
198
|
+
bin/push.mjs # Push data to GitHub
|
|
199
|
+
scripts/generate.mjs # ccusage -> data.json
|
|
200
|
+
scripts/generate-svg.mjs # data.json -> static heatmap.svg
|
|
201
|
+
scripts/serve-svg.mjs # Local SVG dev server
|
|
202
|
+
src/App.tsx # React interactive heatmap
|
|
203
|
+
public/data.json # Generated activity data
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { resolve, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const root = resolve(__dirname, "..");
|
|
8
|
+
|
|
9
|
+
const [command, ...args] = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
const HELP = `
|
|
12
|
+
ai-heatmap - AI usage cost heatmap
|
|
13
|
+
|
|
14
|
+
Commands:
|
|
15
|
+
init [repo-name] Create a new heatmap GitHub Pages repo
|
|
16
|
+
generate [options] Generate data.json from ccusage
|
|
17
|
+
push --repo <owner/repo> Push data.json to target repo
|
|
18
|
+
update --repo <owner/repo> generate + push combined
|
|
19
|
+
|
|
20
|
+
Generate options:
|
|
21
|
+
--since YYYYMMDD Start date
|
|
22
|
+
--until YYYYMMDD End date
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
npx ai-heatmap init my-ai-heatmap
|
|
26
|
+
npx ai-heatmap generate --since 20260101
|
|
27
|
+
npx ai-heatmap push --repo seunggabi/my-ai-heatmap
|
|
28
|
+
npx ai-heatmap update --repo seunggabi/my-ai-heatmap
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
switch (command) {
|
|
32
|
+
case "init": {
|
|
33
|
+
const script = resolve(__dirname, "init.mjs");
|
|
34
|
+
execSync(`node ${script} ${args.join(" ")}`, { stdio: "inherit" });
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case "generate": {
|
|
38
|
+
const script = resolve(root, "scripts/generate.mjs");
|
|
39
|
+
execSync(`node ${script} ${args.join(" ")}`, { stdio: "inherit" });
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case "push": {
|
|
43
|
+
const script = resolve(__dirname, "push.mjs");
|
|
44
|
+
execSync(`node ${script} ${args.join(" ")}`, { stdio: "inherit" });
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "update": {
|
|
48
|
+
const genScript = resolve(root, "scripts/generate.mjs");
|
|
49
|
+
const pushScript = resolve(__dirname, "push.mjs");
|
|
50
|
+
const genArgs = args.filter(
|
|
51
|
+
(a) => a.startsWith("--since") || a.startsWith("--until"),
|
|
52
|
+
);
|
|
53
|
+
const pushArgs = args.filter((a) => a.startsWith("--repo"));
|
|
54
|
+
execSync(`node ${genScript} ${genArgs.join(" ")}`, { stdio: "inherit" });
|
|
55
|
+
execSync(`node ${pushScript} ${pushArgs.join(" ")}`, { stdio: "inherit" });
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
default:
|
|
59
|
+
console.log(HELP);
|
|
60
|
+
}
|
package/bin/init.mjs
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { mkdirSync, writeFileSync, copyFileSync, existsSync } from "node:fs";
|
|
4
|
+
import { resolve, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const templateRoot = resolve(__dirname, "..");
|
|
9
|
+
const repoName = process.argv[2] || "ai-heatmap";
|
|
10
|
+
const targetDir = resolve(process.cwd(), repoName);
|
|
11
|
+
|
|
12
|
+
if (existsSync(targetDir)) {
|
|
13
|
+
console.error(`Error: ${targetDir} already exists`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log(`Creating ${repoName}...`);
|
|
18
|
+
mkdirSync(targetDir, { recursive: true });
|
|
19
|
+
|
|
20
|
+
// package.json
|
|
21
|
+
const pkg = {
|
|
22
|
+
name: repoName,
|
|
23
|
+
version: "1.0.0",
|
|
24
|
+
type: "module",
|
|
25
|
+
scripts: {
|
|
26
|
+
generate: "npx ai-heatmap generate",
|
|
27
|
+
dev: "vite",
|
|
28
|
+
build: "vite build",
|
|
29
|
+
preview: "vite preview",
|
|
30
|
+
update: `npx ai-heatmap update --repo OWNER/${repoName}`,
|
|
31
|
+
},
|
|
32
|
+
dependencies: {
|
|
33
|
+
react: "^19.2.4",
|
|
34
|
+
"react-activity-calendar": "^3.1.1",
|
|
35
|
+
"react-dom": "^19.2.4",
|
|
36
|
+
"react-tooltip": "^5.30.0",
|
|
37
|
+
},
|
|
38
|
+
devDependencies: {
|
|
39
|
+
"@types/react": "^19.2.14",
|
|
40
|
+
"@types/react-dom": "^19.2.3",
|
|
41
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
42
|
+
typescript: "^5.9.3",
|
|
43
|
+
vite: "^7.3.1",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
writeFileSync(
|
|
47
|
+
resolve(targetDir, "package.json"),
|
|
48
|
+
JSON.stringify(pkg, null, 2),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Copy template files
|
|
52
|
+
const filesToCopy = [
|
|
53
|
+
"index.html",
|
|
54
|
+
"vite.config.ts",
|
|
55
|
+
"tsconfig.json",
|
|
56
|
+
".gitignore",
|
|
57
|
+
];
|
|
58
|
+
for (const f of filesToCopy) {
|
|
59
|
+
copyFileSync(resolve(templateRoot, f), resolve(targetDir, f));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Copy src/
|
|
63
|
+
mkdirSync(resolve(targetDir, "src"), { recursive: true });
|
|
64
|
+
const srcFiles = ["main.tsx", "App.tsx", "App.css", "vite-env.d.ts"];
|
|
65
|
+
for (const f of srcFiles) {
|
|
66
|
+
copyFileSync(
|
|
67
|
+
resolve(templateRoot, "src", f),
|
|
68
|
+
resolve(targetDir, "src", f),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Create public/
|
|
73
|
+
mkdirSync(resolve(targetDir, "public"), { recursive: true });
|
|
74
|
+
|
|
75
|
+
// GitHub Actions workflow
|
|
76
|
+
const workflowDir = resolve(targetDir, ".github/workflows");
|
|
77
|
+
mkdirSync(workflowDir, { recursive: true });
|
|
78
|
+
copyFileSync(
|
|
79
|
+
resolve(templateRoot, ".github/workflows/deploy.yml"),
|
|
80
|
+
resolve(workflowDir, "deploy.yml"),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Git init
|
|
84
|
+
execSync("git init", { cwd: targetDir, stdio: "inherit" });
|
|
85
|
+
execSync("git add -A", { cwd: targetDir, stdio: "inherit" });
|
|
86
|
+
execSync('git commit -m "Initial commit: ai-heatmap"', {
|
|
87
|
+
cwd: targetDir,
|
|
88
|
+
stdio: "inherit",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Try to create GitHub repo
|
|
92
|
+
try {
|
|
93
|
+
execSync(`gh repo create ${repoName} --public --source=. --push`, {
|
|
94
|
+
cwd: targetDir,
|
|
95
|
+
stdio: "inherit",
|
|
96
|
+
});
|
|
97
|
+
console.log(`\nRepo created: https://github.com/$(gh api user -q .login)/${repoName}`);
|
|
98
|
+
|
|
99
|
+
// Enable GitHub Pages
|
|
100
|
+
try {
|
|
101
|
+
execSync(
|
|
102
|
+
`gh api repos/{owner}/{repo}/pages -X POST -f build_type=workflow 2>/dev/null || true`,
|
|
103
|
+
{ cwd: targetDir, stdio: "inherit" },
|
|
104
|
+
);
|
|
105
|
+
} catch {
|
|
106
|
+
// Pages may need manual setup
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
console.log("\nGitHub repo creation skipped (gh CLI not available or auth needed)");
|
|
110
|
+
console.log("Push manually: git remote add origin <url> && git push -u origin main");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`
|
|
114
|
+
Done! Next steps:
|
|
115
|
+
cd ${repoName}
|
|
116
|
+
npm install
|
|
117
|
+
npm run generate # Generate data from ccusage
|
|
118
|
+
npm run dev # Preview locally
|
|
119
|
+
git push # Deploy to GitHub Pages
|
|
120
|
+
`);
|
package/bin/push.mjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
+
import { resolve, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const root = resolve(__dirname, "..");
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const repoIdx = args.indexOf("--repo");
|
|
12
|
+
const repo = repoIdx !== -1 ? args[repoIdx + 1] : null;
|
|
13
|
+
|
|
14
|
+
if (!repo) {
|
|
15
|
+
console.error("Usage: ai-heatmap push --repo <owner/repo>");
|
|
16
|
+
console.error("Example: ai-heatmap push --repo seunggabi/my-ai-heatmap");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const dataPath = resolve(root, "public/data.json");
|
|
21
|
+
if (!existsSync(dataPath)) {
|
|
22
|
+
console.error("data.json not found. Run 'ai-heatmap generate' first.");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const content = readFileSync(dataPath, "utf-8");
|
|
27
|
+
const base64 = Buffer.from(content).toString("base64");
|
|
28
|
+
|
|
29
|
+
console.log(`Pushing data.json to ${repo}...`);
|
|
30
|
+
|
|
31
|
+
// Get current file SHA if it exists (needed for update)
|
|
32
|
+
let sha = "";
|
|
33
|
+
try {
|
|
34
|
+
sha = execSync(
|
|
35
|
+
`gh api repos/${repo}/contents/public/data.json --jq .sha 2>/dev/null`,
|
|
36
|
+
{ encoding: "utf-8" },
|
|
37
|
+
).trim();
|
|
38
|
+
} catch {
|
|
39
|
+
// File doesn't exist yet, that's fine
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const payload = {
|
|
43
|
+
message: `Update heatmap data (${new Date().toISOString().slice(0, 10)})`,
|
|
44
|
+
content: base64,
|
|
45
|
+
};
|
|
46
|
+
if (sha) payload.sha = sha;
|
|
47
|
+
|
|
48
|
+
const payloadStr = JSON.stringify(JSON.stringify(payload));
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
execSync(
|
|
52
|
+
`echo ${payloadStr} | gh api repos/${repo}/contents/public/data.json -X PUT --input -`,
|
|
53
|
+
{ stdio: ["pipe", "inherit", "inherit"] },
|
|
54
|
+
);
|
|
55
|
+
console.log(`Updated public/data.json in ${repo}`);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error("Push failed. Check gh auth and repo permissions.");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
package/index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>AI Heatmap</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-heatmap",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI usage cost heatmap powered by ccusage + react-activity-calendar",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-heatmap": "./bin/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"scripts/",
|
|
12
|
+
"src/",
|
|
13
|
+
"public/",
|
|
14
|
+
"index.html",
|
|
15
|
+
"vite.config.ts",
|
|
16
|
+
"tsconfig.json",
|
|
17
|
+
".github/"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"generate": "node scripts/generate.mjs",
|
|
21
|
+
"dev": "vite",
|
|
22
|
+
"build": "vite build",
|
|
23
|
+
"preview": "vite preview",
|
|
24
|
+
"generate:svg": "node scripts/generate-svg.mjs",
|
|
25
|
+
"deploy": "npm run generate && npm run generate:svg && npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"react": "^19.2.4",
|
|
32
|
+
"react-activity-calendar": "^3.1.1",
|
|
33
|
+
"react-dom": "^19.2.4",
|
|
34
|
+
"react-tooltip": "^5.30.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/react": "^19.2.14",
|
|
38
|
+
"@types/react-dom": "^19.2.3",
|
|
39
|
+
"@vercel/node": "^5.6.4",
|
|
40
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vite": "^7.3.1"
|
|
43
|
+
}
|
|
44
|
+
}
|