vibestats 1.3.14 → 1.4.1
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 +15 -3
- package/dist/index.js +389 -68
- package/dist/web/404.html +1 -0
- package/dist/web/_next/static/chunks/231.de8a9ee791ed9eef.js +1 -0
- package/dist/web/_next/static/chunks/498-74a3a367cb8442f0.js +1 -0
- package/dist/web/_next/static/chunks/562.a5731acd5543100e.js +1 -0
- package/dist/web/_next/static/chunks/816-8960921093d57453.js +1 -0
- package/dist/web/_next/static/chunks/832-93b990b7ceeaa2c5.js +1 -0
- package/dist/web/_next/static/chunks/971-0ab6a23b2af361a6.js +1 -0
- package/dist/web/_next/static/chunks/app/_not-found/page-91f35a114767b957.js +1 -0
- package/dist/web/_next/static/chunks/app/activity/[slug]/page-bd4706aecd453508.js +1 -0
- package/dist/web/_next/static/chunks/app/activity/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/changelog/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/compare/[slug]/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/compare/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/dashboard/activity/page-e84049538e03061e.js +1 -0
- package/dist/web/_next/static/chunks/app/dashboard/layout-bd4706aecd453508.js +1 -0
- package/dist/web/_next/static/chunks/app/dashboard/page-4834f263664e8663.js +1 -0
- package/dist/web/_next/static/chunks/app/dashboard/usage/page-b42b457b8fd3865c.js +1 -0
- package/dist/web/_next/static/chunks/app/dashboard/wrapped/page-268dced4ee2726f7.js +1 -0
- package/dist/web/_next/static/chunks/app/docs/commands/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/docs/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/features/[slug]/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/features/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/guides/[slug]/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/guides/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/images/[...slug]/route-bd4706aecd453508.js +1 -0
- package/dist/web/_next/static/chunks/app/layout-6ebd9b17af55ec02.js +1 -0
- package/dist/web/_next/static/chunks/app/page-dfb7bbdb5999dc75.js +1 -0
- package/dist/web/_next/static/chunks/app/robots.txt/route-bd4706aecd453508.js +1 -0
- package/dist/web/_next/static/chunks/app/s/[slug]/route-bd4706aecd453508.js +1 -0
- package/dist/web/_next/static/chunks/app/sitemap.xml/route-bd4706aecd453508.js +1 -0
- package/dist/web/_next/static/chunks/app/usage/[slug]/page-bd4706aecd453508.js +1 -0
- package/dist/web/_next/static/chunks/app/use-cases/[slug]/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/use-cases/page-5d0587c1d5910e68.js +1 -0
- package/dist/web/_next/static/chunks/app/wrapped/[slug]/page-bd4706aecd453508.js +1 -0
- package/dist/web/_next/static/chunks/app/wrapped/page-66b00de7736097db.js +1 -0
- package/dist/web/_next/static/chunks/c476d598-9099ed8b975ae1d6.js +1 -0
- package/dist/web/_next/static/chunks/framework-1c6a486f6592f084.js +1 -0
- package/dist/web/_next/static/chunks/main-app-6619364ab1f13fb7.js +1 -0
- package/dist/web/_next/static/chunks/main-f6fa273a9100cc16.js +1 -0
- package/dist/web/_next/static/chunks/pages/_app-55552e79b4ca5b96.js +1 -0
- package/dist/web/_next/static/chunks/pages/_error-da3c1b00689f457b.js +1 -0
- package/dist/web/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/web/_next/static/chunks/webpack-175b2f5685d7557b.js +1 -0
- package/dist/web/_next/static/css/335de1248158d380.css +3 -0
- package/dist/web/_next/static/gPirvBMhpRSdtR0tsMj2H/_buildManifest.js +1 -0
- package/dist/web/_next/static/gPirvBMhpRSdtR0tsMj2H/_ssgManifest.js +1 -0
- package/dist/web/activity.html +1 -0
- package/dist/web/activity.txt +19 -0
- package/dist/web/changelog.html +1 -0
- package/dist/web/changelog.txt +60 -0
- package/dist/web/compare/claude-code-vs-codex-cli-tracking.html +1 -0
- package/dist/web/compare/claude-code-vs-codex-cli-tracking.txt +25 -0
- package/dist/web/compare/daily-vs-monthly-ai-usage-reports.html +1 -0
- package/dist/web/compare/daily-vs-monthly-ai-usage-reports.txt +24 -0
- package/dist/web/compare/local-vs-cloud-ai-analytics.html +1 -0
- package/dist/web/compare/local-vs-cloud-ai-analytics.txt +25 -0
- package/dist/web/compare/manual-tracking-vs-vibestats.html +1 -0
- package/dist/web/compare/manual-tracking-vs-vibestats.txt +24 -0
- package/dist/web/compare/querystring-shares-vs-stored-share-pages.html +1 -0
- package/dist/web/compare/querystring-shares-vs-stored-share-pages.txt +25 -0
- package/dist/web/compare/session-breakdown-vs-model-breakdown.html +1 -0
- package/dist/web/compare/session-breakdown-vs-model-breakdown.txt +25 -0
- package/dist/web/compare/single-source-vs-combined-tracking.html +1 -0
- package/dist/web/compare/single-source-vs-combined-tracking.txt +25 -0
- package/dist/web/compare/terminal-table-vs-json-output.html +1 -0
- package/dist/web/compare/terminal-table-vs-json-output.txt +24 -0
- package/dist/web/compare/token-volume-vs-cost-estimation.html +1 -0
- package/dist/web/compare/token-volume-vs-cost-estimation.txt +24 -0
- package/dist/web/compare/wrapped-vs-activity-heatmap.html +1 -0
- package/dist/web/compare/wrapped-vs-activity-heatmap.txt +25 -0
- package/dist/web/compare.html +1 -0
- package/dist/web/compare.txt +32 -0
- package/dist/web/dashboard/activity.html +1 -0
- package/dist/web/dashboard/activity.txt +20 -0
- package/dist/web/dashboard/usage.html +1 -0
- package/dist/web/dashboard/usage.txt +20 -0
- package/dist/web/dashboard/wrapped.html +1 -0
- package/dist/web/dashboard/wrapped.txt +20 -0
- package/dist/web/dashboard.html +1 -0
- package/dist/web/dashboard.txt +20 -0
- package/dist/web/docs/commands.html +1 -0
- package/dist/web/docs/commands.txt +33 -0
- package/dist/web/docs.html +1 -0
- package/dist/web/docs.txt +25 -0
- package/dist/web/features/ai-coding-activity-heatmap.html +1 -0
- package/dist/web/features/ai-coding-activity-heatmap.txt +25 -0
- package/dist/web/features/ai-coding-wrapped.html +1 -0
- package/dist/web/features/ai-coding-wrapped.txt +24 -0
- package/dist/web/features/claude-code-stats.html +1 -0
- package/dist/web/features/claude-code-stats.txt +24 -0
- package/dist/web/features/claude-diagnostics.html +1 -0
- package/dist/web/features/claude-diagnostics.txt +24 -0
- package/dist/web/features/codex-cli-stats.html +1 -0
- package/dist/web/features/codex-cli-stats.txt +24 -0
- package/dist/web/features/combined-ai-coding-stats.html +1 -0
- package/dist/web/features/combined-ai-coding-stats.txt +25 -0
- package/dist/web/features/json-and-automation-exports.html +1 -0
- package/dist/web/features/json-and-automation-exports.txt +25 -0
- package/dist/web/features/model-breakdown.html +1 -0
- package/dist/web/features/model-breakdown.txt +24 -0
- package/dist/web/features/privacy-first-analytics.html +1 -0
- package/dist/web/features/privacy-first-analytics.txt +25 -0
- package/dist/web/features/session-breakdown.html +1 -0
- package/dist/web/features/session-breakdown.txt +24 -0
- package/dist/web/features/share-pages.html +1 -0
- package/dist/web/features/share-pages.txt +24 -0
- package/dist/web/features/token-and-cost-tracking.html +1 -0
- package/dist/web/features/token-and-cost-tracking.txt +24 -0
- package/dist/web/features.html +1 -0
- package/dist/web/features.txt +33 -0
- package/dist/web/guides/how-to-build-ai-activity-heatmap.html +1 -0
- package/dist/web/guides/how-to-build-ai-activity-heatmap.txt +24 -0
- package/dist/web/guides/how-to-compare-model-usage.html +1 -0
- package/dist/web/guides/how-to-compare-model-usage.txt +24 -0
- package/dist/web/guides/how-to-create-ai-coding-wrapped.html +1 -0
- package/dist/web/guides/how-to-create-ai-coding-wrapped.txt +24 -0
- package/dist/web/guides/how-to-measure-ai-token-costs.html +1 -0
- package/dist/web/guides/how-to-measure-ai-token-costs.txt +24 -0
- package/dist/web/guides/how-to-read-claude-diagnostics.html +1 -0
- package/dist/web/guides/how-to-read-claude-diagnostics.txt +24 -0
- package/dist/web/guides/how-to-share-ai-coding-stats.html +1 -0
- package/dist/web/guides/how-to-share-ai-coding-stats.txt +24 -0
- package/dist/web/guides/how-to-track-claude-code-usage.html +1 -0
- package/dist/web/guides/how-to-track-claude-code-usage.txt +24 -0
- package/dist/web/guides/how-to-track-codex-cli-usage.html +1 -0
- package/dist/web/guides/how-to-track-codex-cli-usage.txt +24 -0
- package/dist/web/guides.html +1 -0
- package/dist/web/guides.txt +27 -0
- package/dist/web/index.html +1 -0
- package/dist/web/index.txt +23 -0
- package/dist/web/robots.txt +7 -0
- package/dist/web/sitemap.xml +279 -0
- package/dist/web/use-cases/ai-agencies.html +1 -0
- package/dist/web/use-cases/ai-agencies.txt +23 -0
- package/dist/web/use-cases/consultants.html +1 -0
- package/dist/web/use-cases/consultants.txt +23 -0
- package/dist/web/use-cases/engineering-managers.html +1 -0
- package/dist/web/use-cases/engineering-managers.txt +24 -0
- package/dist/web/use-cases/freelancers.html +1 -0
- package/dist/web/use-cases/freelancers.txt +23 -0
- package/dist/web/use-cases/indie-hackers.html +1 -0
- package/dist/web/use-cases/indie-hackers.txt +23 -0
- package/dist/web/use-cases/monthly-reporting.html +1 -0
- package/dist/web/use-cases/monthly-reporting.txt +23 -0
- package/dist/web/use-cases/open-source-maintainers.html +1 -0
- package/dist/web/use-cases/open-source-maintainers.txt +24 -0
- package/dist/web/use-cases/personal-retrospectives.html +1 -0
- package/dist/web/use-cases/personal-retrospectives.txt +24 -0
- package/dist/web/use-cases/researchers.html +1 -0
- package/dist/web/use-cases/researchers.txt +23 -0
- package/dist/web/use-cases/weekly-reviews.html +1 -0
- package/dist/web/use-cases/weekly-reviews.txt +23 -0
- package/dist/web/use-cases.html +1 -0
- package/dist/web/use-cases.txt +31 -0
- package/dist/web/wrapped.html +1 -0
- package/dist/web/wrapped.txt +20 -0
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ AI coding stats CLI for **Claude Code** and **OpenAI Codex**. Track your usage a
|
|
|
8
8
|
npm install -g vibestats
|
|
9
9
|
# or run directly
|
|
10
10
|
npx vibestats
|
|
11
|
+
npx vibestats codex
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
## Usage
|
|
@@ -15,6 +16,9 @@ npx vibestats
|
|
|
15
16
|
```bash
|
|
16
17
|
# Usage stats (default)
|
|
17
18
|
vibestats # Daily usage table
|
|
19
|
+
vibestats claude # Claude-compatible usage
|
|
20
|
+
vibestats codex # Codex CLI usage
|
|
21
|
+
vibestats all # Combined local sources
|
|
18
22
|
vibestats --monthly # Monthly aggregation
|
|
19
23
|
vibestats --model # Aggregate by model
|
|
20
24
|
vibestats --total # Show only totals
|
|
@@ -65,10 +69,15 @@ vibestats --claude-limits
|
|
|
65
69
|
|
|
66
70
|
### Data Source
|
|
67
71
|
|
|
68
|
-
|
|
|
69
|
-
|
|
72
|
+
| Command or flag | Description |
|
|
73
|
+
|-----------------|-------------|
|
|
74
|
+
| `vibestats claude` | Claude-compatible local usage |
|
|
75
|
+
| `vibestats codex` | OpenAI Codex only |
|
|
76
|
+
| `vibestats all` | All supported local sources combined |
|
|
70
77
|
| `--codex` | OpenAI Codex only |
|
|
71
78
|
| `--combined` | Claude + Codex combined |
|
|
79
|
+
| `vibestats usage codex --total` | Codex total usage table |
|
|
80
|
+
| `vibestats limits codex` | Codex local limit windows |
|
|
72
81
|
|
|
73
82
|
### Config
|
|
74
83
|
|
|
@@ -99,7 +108,7 @@ Creates `~/.vibestats.json`:
|
|
|
99
108
|
|
|
100
109
|
| Source | Location |
|
|
101
110
|
|--------|----------|
|
|
102
|
-
| Claude Code
|
|
111
|
+
| Claude-compatible | Claude Code, OpenCode, and Factory Droid local usage |
|
|
103
112
|
| OpenAI Codex | `~/.codex/sessions/*.jsonl` |
|
|
104
113
|
|
|
105
114
|
Additional Claude diagnostic files:
|
|
@@ -125,10 +134,13 @@ Additional Claude diagnostic files:
|
|
|
125
134
|
|
|
126
135
|
Visit [vibestats.wolfai.dev](https://vibestats.wolfai.dev) to view shares in the browser.
|
|
127
136
|
|
|
137
|
+
- [vibestats.wolfai.dev/docs](https://vibestats.wolfai.dev/docs)
|
|
128
138
|
- [vibestats.wolfai.dev/wrapped](https://vibestats.wolfai.dev/wrapped)
|
|
129
139
|
- [vibestats.wolfai.dev/activity](https://vibestats.wolfai.dev/activity)
|
|
130
140
|
- [vibestats.wolfai.dev/changelog](https://vibestats.wolfai.dev/changelog)
|
|
131
141
|
|
|
142
|
+
The hosted web app runs from the VPS deployment defined by `docker-compose.vps.yml` and `packages/web/Dockerfile`, behind Traefik at `vibestats.wolfai.dev`.
|
|
143
|
+
|
|
132
144
|
## License
|
|
133
145
|
|
|
134
146
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -1036,7 +1036,7 @@ async function writeUsageCacheFile(cache) {
|
|
|
1036
1036
|
await fs.rename(tmpPath, cachePath);
|
|
1037
1037
|
}
|
|
1038
1038
|
function sleep(ms) {
|
|
1039
|
-
return new Promise((
|
|
1039
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1040
1040
|
}
|
|
1041
1041
|
async function withUsageCacheLock(task) {
|
|
1042
1042
|
const lockPath = `${getUsageCachePath()}.lock`;
|
|
@@ -2373,9 +2373,9 @@ function aggregateBySession(entries) {
|
|
|
2373
2373
|
}
|
|
2374
2374
|
return Array.from(sessionMap.entries()).filter(([, data]) => data.totalTokens > 0).map(([sessionId, data]) => {
|
|
2375
2375
|
const date = data.firstTimestamp ? toLocalDateString3(data.firstTimestamp) : "unknown";
|
|
2376
|
-
const
|
|
2376
|
+
const shortId2 = data.displaySessionId.slice(0, 12);
|
|
2377
2377
|
return {
|
|
2378
|
-
key: `${date} ${
|
|
2378
|
+
key: `${date} ${shortId2}`,
|
|
2379
2379
|
inputTokens: data.inputTokens,
|
|
2380
2380
|
outputTokens: data.outputTokens,
|
|
2381
2381
|
cacheWriteTokens: data.cacheWriteTokens,
|
|
@@ -3666,6 +3666,359 @@ async function inspectClaudeUsage(claudeDir = getClaudeDir2()) {
|
|
|
3666
3666
|
};
|
|
3667
3667
|
}
|
|
3668
3668
|
|
|
3669
|
+
// src/shared/args.ts
|
|
3670
|
+
var modelFamilyArgs = {
|
|
3671
|
+
claude: {
|
|
3672
|
+
type: "boolean",
|
|
3673
|
+
description: "Show only Claude family stats across local usage sources",
|
|
3674
|
+
default: false
|
|
3675
|
+
},
|
|
3676
|
+
kimi: {
|
|
3677
|
+
type: "boolean",
|
|
3678
|
+
description: "Show only Kimi family stats across local usage sources",
|
|
3679
|
+
default: false
|
|
3680
|
+
},
|
|
3681
|
+
minimax: {
|
|
3682
|
+
type: "boolean",
|
|
3683
|
+
description: "Show only MiniMax family stats across local usage sources",
|
|
3684
|
+
default: false
|
|
3685
|
+
}
|
|
3686
|
+
};
|
|
3687
|
+
function getSelectedModelFamilies(args) {
|
|
3688
|
+
const families = [];
|
|
3689
|
+
if (args.claude === true) families.push("claude");
|
|
3690
|
+
if (args.kimi === true) families.push("kimi");
|
|
3691
|
+
if (args.minimax === true) families.push("minimax");
|
|
3692
|
+
return families;
|
|
3693
|
+
}
|
|
3694
|
+
var cacheArgs = {
|
|
3695
|
+
"no-cache": {
|
|
3696
|
+
type: "boolean",
|
|
3697
|
+
description: "Bypass the persistent local usage cache for this run",
|
|
3698
|
+
default: false
|
|
3699
|
+
},
|
|
3700
|
+
"refresh-cache": {
|
|
3701
|
+
type: "boolean",
|
|
3702
|
+
description: "Rebuild the persistent local usage cache before showing stats",
|
|
3703
|
+
default: false
|
|
3704
|
+
}
|
|
3705
|
+
};
|
|
3706
|
+
function getUsageCacheOptions(args) {
|
|
3707
|
+
return {
|
|
3708
|
+
useCache: args["no-cache"] !== true,
|
|
3709
|
+
refreshCache: args["refresh-cache"] === true
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
// src/dashboard/assets-path.ts
|
|
3714
|
+
import { fileURLToPath } from "url";
|
|
3715
|
+
import { dirname as dirname2, resolve, join as join5 } from "path";
|
|
3716
|
+
import { existsSync } from "fs";
|
|
3717
|
+
function resolveDashboardAssets(importMetaUrl) {
|
|
3718
|
+
const here = dirname2(fileURLToPath(importMetaUrl));
|
|
3719
|
+
const candidates = [
|
|
3720
|
+
// Published tarball (tsup single-file bundle): here === .../dist, assets at .../dist/web
|
|
3721
|
+
resolve(here, "web"),
|
|
3722
|
+
// Same tarball layout but caller is nested one deeper (e.g. dist/commands/...)
|
|
3723
|
+
resolve(here, "..", "web"),
|
|
3724
|
+
// Monorepo dev with tsx from packages/cli/src/dashboard/...
|
|
3725
|
+
resolve(here, "..", "..", "..", "web", ".next-static"),
|
|
3726
|
+
// Monorepo dev with tsx from packages/cli/src/commands/...
|
|
3727
|
+
resolve(here, "..", "..", "web", ".next-static")
|
|
3728
|
+
];
|
|
3729
|
+
for (const candidate of candidates) {
|
|
3730
|
+
if (existsSync(join5(candidate, "index.html"))) return candidate;
|
|
3731
|
+
}
|
|
3732
|
+
throw new Error(
|
|
3733
|
+
`dashboard assets not found. Tried:
|
|
3734
|
+
` + candidates.map((c) => ` - ${c}`).join("\n") + `
|
|
3735
|
+
Run \`pnpm --filter web build:static\` first.`
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3739
|
+
// src/dashboard/payloads.ts
|
|
3740
|
+
import { randomBytes } from "crypto";
|
|
3741
|
+
var MAX_INLINE_BYTES = 6e3;
|
|
3742
|
+
var STUB_BASE = "http://local";
|
|
3743
|
+
function extractEncodedQuery(urlWithQuery) {
|
|
3744
|
+
const url = new URL(urlWithQuery);
|
|
3745
|
+
const query = url.search.replace(/^\?/, "");
|
|
3746
|
+
return query || null;
|
|
3747
|
+
}
|
|
3748
|
+
function shortId() {
|
|
3749
|
+
return randomBytes(6).toString("hex");
|
|
3750
|
+
}
|
|
3751
|
+
function inlineOrFallback(paramKey, payload, inlineQuery, out) {
|
|
3752
|
+
if (!payload || !inlineQuery) return;
|
|
3753
|
+
if (Buffer.byteLength(inlineQuery, "utf-8") <= MAX_INLINE_BYTES) {
|
|
3754
|
+
out.hubParams.set(paramKey, inlineQuery);
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
const id = shortId();
|
|
3758
|
+
out.jsonStore.set(id, JSON.stringify(payload));
|
|
3759
|
+
out.hubParams.set(paramKey, `json:${id}`);
|
|
3760
|
+
}
|
|
3761
|
+
function buildDashboardPayloads(input) {
|
|
3762
|
+
const out = {
|
|
3763
|
+
hubParams: new URLSearchParams(),
|
|
3764
|
+
jsonStore: /* @__PURE__ */ new Map()
|
|
3765
|
+
};
|
|
3766
|
+
if (input.usage) {
|
|
3767
|
+
const full = encodeUsageToUrl(input.usage, STUB_BASE);
|
|
3768
|
+
inlineOrFallback("u", input.usage, extractEncodedQuery(full), out);
|
|
3769
|
+
}
|
|
3770
|
+
if (input.wrapped) {
|
|
3771
|
+
const full = encodeStatsToUrl(input.wrapped, STUB_BASE);
|
|
3772
|
+
inlineOrFallback("w", input.wrapped, extractEncodedQuery(full), out);
|
|
3773
|
+
}
|
|
3774
|
+
if (input.activity) {
|
|
3775
|
+
const full = encodeActivityToUrl(input.activity, STUB_BASE);
|
|
3776
|
+
inlineOrFallback("a", input.activity, extractEncodedQuery(full), out);
|
|
3777
|
+
}
|
|
3778
|
+
return out;
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
// src/dashboard/open-browser.ts
|
|
3782
|
+
import { spawn } from "child_process";
|
|
3783
|
+
import { platform } from "os";
|
|
3784
|
+
function openInBrowser(url) {
|
|
3785
|
+
const os = platform();
|
|
3786
|
+
const cmd = os === "darwin" ? "open" : os === "win32" ? "cmd" : "xdg-open";
|
|
3787
|
+
const args = os === "win32" ? ["/c", "start", "", url] : [url];
|
|
3788
|
+
try {
|
|
3789
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
3790
|
+
child.on("error", () => {
|
|
3791
|
+
});
|
|
3792
|
+
child.unref();
|
|
3793
|
+
} catch {
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
|
|
3797
|
+
// src/dashboard/server.ts
|
|
3798
|
+
import { createServer as createServer2 } from "http";
|
|
3799
|
+
import { readFile } from "fs/promises";
|
|
3800
|
+
import { existsSync as existsSync2, realpathSync, statSync } from "fs";
|
|
3801
|
+
import { extname, join as join6, normalize, resolve as resolve2, sep } from "path";
|
|
3802
|
+
|
|
3803
|
+
// src/dashboard/port.ts
|
|
3804
|
+
import { createServer, createConnection } from "net";
|
|
3805
|
+
async function findFreePort(start, end) {
|
|
3806
|
+
for (let port = start; port <= end; port++) {
|
|
3807
|
+
if (await isPortFree(port)) return port;
|
|
3808
|
+
}
|
|
3809
|
+
throw new Error(`no free port available in range ${start}-${end}`);
|
|
3810
|
+
}
|
|
3811
|
+
function isPortFree(port) {
|
|
3812
|
+
return isConnectable(port).then((connectable) => {
|
|
3813
|
+
if (connectable) return false;
|
|
3814
|
+
return isBindable(port);
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
function isConnectable(port) {
|
|
3818
|
+
return new Promise((resolve3) => {
|
|
3819
|
+
const socket = createConnection({ port, host: "127.0.0.1" });
|
|
3820
|
+
const done = (result) => {
|
|
3821
|
+
socket.removeAllListeners();
|
|
3822
|
+
socket.destroy();
|
|
3823
|
+
resolve3(result);
|
|
3824
|
+
};
|
|
3825
|
+
socket.once("connect", () => done(true));
|
|
3826
|
+
socket.once("error", () => done(false));
|
|
3827
|
+
const timer = setTimeout(() => done(false), 250);
|
|
3828
|
+
timer.unref();
|
|
3829
|
+
});
|
|
3830
|
+
}
|
|
3831
|
+
function isBindable(port) {
|
|
3832
|
+
return new Promise((resolve3) => {
|
|
3833
|
+
const server = createServer();
|
|
3834
|
+
const onError = () => {
|
|
3835
|
+
server.removeListener("listening", onListening);
|
|
3836
|
+
resolve3(false);
|
|
3837
|
+
};
|
|
3838
|
+
const onListening = () => {
|
|
3839
|
+
server.removeListener("error", onError);
|
|
3840
|
+
server.close(() => {
|
|
3841
|
+
resolve3(true);
|
|
3842
|
+
});
|
|
3843
|
+
};
|
|
3844
|
+
server.on("error", onError);
|
|
3845
|
+
server.on("listening", onListening);
|
|
3846
|
+
server.listen(port, "127.0.0.1");
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
// src/dashboard/server.ts
|
|
3851
|
+
var MIME = {
|
|
3852
|
+
".html": "text/html; charset=utf-8",
|
|
3853
|
+
".js": "application/javascript; charset=utf-8",
|
|
3854
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
3855
|
+
".css": "text/css; charset=utf-8",
|
|
3856
|
+
".json": "application/json; charset=utf-8",
|
|
3857
|
+
".svg": "image/svg+xml",
|
|
3858
|
+
".png": "image/png",
|
|
3859
|
+
".jpg": "image/jpeg",
|
|
3860
|
+
".jpeg": "image/jpeg",
|
|
3861
|
+
".webp": "image/webp",
|
|
3862
|
+
".ico": "image/x-icon",
|
|
3863
|
+
".woff": "font/woff",
|
|
3864
|
+
".woff2": "font/woff2",
|
|
3865
|
+
".txt": "text/plain; charset=utf-8",
|
|
3866
|
+
".map": "application/json; charset=utf-8"
|
|
3867
|
+
};
|
|
3868
|
+
async function startDashboardServer(options) {
|
|
3869
|
+
const root = realpathSync(resolve2(options.assetsDir));
|
|
3870
|
+
const preferred = options.preferredPort ?? 8080;
|
|
3871
|
+
const port = await findFreePort(preferred, preferred + 20);
|
|
3872
|
+
const server = createServer2(async (req, res) => {
|
|
3873
|
+
try {
|
|
3874
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
|
|
3875
|
+
let pathname = url.pathname;
|
|
3876
|
+
const dataMatch = /^\/data\/([A-Za-z0-9_-]+)\.json$/.exec(pathname);
|
|
3877
|
+
if (dataMatch) {
|
|
3878
|
+
const body2 = options.jsonStore.get(dataMatch[1]);
|
|
3879
|
+
if (!body2) {
|
|
3880
|
+
res.statusCode = 404;
|
|
3881
|
+
res.end("not found");
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3884
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
3885
|
+
res.setHeader("Cache-Control", "no-store");
|
|
3886
|
+
res.end(body2);
|
|
3887
|
+
return;
|
|
3888
|
+
}
|
|
3889
|
+
if (pathname === "/") pathname = "/index.html";
|
|
3890
|
+
if (pathname !== "/index.html" && pathname.endsWith("/")) {
|
|
3891
|
+
pathname = pathname.slice(0, -1);
|
|
3892
|
+
}
|
|
3893
|
+
const safePath = normalize(join6(root, pathname));
|
|
3894
|
+
if (!safePath.startsWith(root + sep) && safePath !== root) {
|
|
3895
|
+
res.statusCode = 400;
|
|
3896
|
+
res.end("bad request");
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
let filePath = safePath;
|
|
3900
|
+
const sibling = filePath + ".html";
|
|
3901
|
+
if (existsSync2(filePath) && statSync(filePath).isFile()) {
|
|
3902
|
+
} else if (existsSync2(sibling) && statSync(sibling).isFile()) {
|
|
3903
|
+
filePath = sibling;
|
|
3904
|
+
} else if (existsSync2(filePath) && statSync(filePath).isDirectory()) {
|
|
3905
|
+
filePath = join6(filePath, "index.html");
|
|
3906
|
+
}
|
|
3907
|
+
let realFilePath;
|
|
3908
|
+
try {
|
|
3909
|
+
realFilePath = realpathSync(filePath);
|
|
3910
|
+
} catch {
|
|
3911
|
+
res.statusCode = 404;
|
|
3912
|
+
res.end("not found");
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
if (!realFilePath.startsWith(root + sep) && realFilePath !== root) {
|
|
3916
|
+
res.statusCode = 400;
|
|
3917
|
+
res.end("bad request");
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
if (!existsSync2(realFilePath) || !statSync(realFilePath).isFile()) {
|
|
3921
|
+
res.statusCode = 404;
|
|
3922
|
+
res.end("not found");
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
const ext = extname(realFilePath).toLowerCase();
|
|
3926
|
+
const mime = MIME[ext] || "application/octet-stream";
|
|
3927
|
+
const body = await readFile(realFilePath);
|
|
3928
|
+
if (pathname.startsWith("/_next/static/")) {
|
|
3929
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
3930
|
+
} else {
|
|
3931
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
3932
|
+
}
|
|
3933
|
+
res.setHeader("Content-Type", mime);
|
|
3934
|
+
res.setHeader("Content-Length", String(body.length));
|
|
3935
|
+
res.end(body);
|
|
3936
|
+
} catch (err) {
|
|
3937
|
+
console.error("[dashboard] server error:", err);
|
|
3938
|
+
res.statusCode = 500;
|
|
3939
|
+
res.end("internal error");
|
|
3940
|
+
}
|
|
3941
|
+
});
|
|
3942
|
+
await new Promise((resolveFn, rejectFn) => {
|
|
3943
|
+
const onError = (err) => rejectFn(err);
|
|
3944
|
+
server.once("error", onError);
|
|
3945
|
+
server.listen(port, "127.0.0.1", () => {
|
|
3946
|
+
server.removeListener("error", onError);
|
|
3947
|
+
resolveFn();
|
|
3948
|
+
});
|
|
3949
|
+
});
|
|
3950
|
+
return {
|
|
3951
|
+
server,
|
|
3952
|
+
port,
|
|
3953
|
+
async close() {
|
|
3954
|
+
await Promise.race([
|
|
3955
|
+
new Promise(
|
|
3956
|
+
(resolveFn, rejectFn) => server.close((err) => err ? rejectFn(err) : resolveFn())
|
|
3957
|
+
),
|
|
3958
|
+
new Promise((resolveFn) => {
|
|
3959
|
+
const t = setTimeout(resolveFn, 2e3);
|
|
3960
|
+
t.unref();
|
|
3961
|
+
})
|
|
3962
|
+
]);
|
|
3963
|
+
}
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
// src/commands/dashboard.ts
|
|
3968
|
+
async function runDashboard(args, config) {
|
|
3969
|
+
void config;
|
|
3970
|
+
const families = getSelectedModelFamilies(args);
|
|
3971
|
+
const scopeLabel = formatModelFamilyLabel(families);
|
|
3972
|
+
const cacheOptions = getUsageCacheOptions(args);
|
|
3973
|
+
const codexOnly = args.codex;
|
|
3974
|
+
const combined = args.combined;
|
|
3975
|
+
const [usage, activity, data] = await Promise.all([
|
|
3976
|
+
loadUsageStats({
|
|
3977
|
+
aggregation: "daily",
|
|
3978
|
+
codexOnly,
|
|
3979
|
+
combined,
|
|
3980
|
+
families,
|
|
3981
|
+
scopeLabel,
|
|
3982
|
+
...cacheOptions
|
|
3983
|
+
}),
|
|
3984
|
+
loadActivityStats({ codexOnly, combined, families, scopeLabel, ...cacheOptions }),
|
|
3985
|
+
loadData({ codexOnly, combined, families, scopeLabel, ...cacheOptions })
|
|
3986
|
+
]);
|
|
3987
|
+
validateData(data, { codexOnly, combined, families, scopeLabel, ...cacheOptions });
|
|
3988
|
+
let wrapped = null;
|
|
3989
|
+
if (combined) {
|
|
3990
|
+
const c = data.claude ? computeWrappedStats(data.claude) : null;
|
|
3991
|
+
const x = data.codex ? computeCodexWrappedStats(data.codex) : null;
|
|
3992
|
+
wrapped = combineWrappedStats(c, x);
|
|
3993
|
+
} else if (codexOnly) {
|
|
3994
|
+
wrapped = data.codex ? computeCodexWrappedStats(data.codex) : null;
|
|
3995
|
+
} else {
|
|
3996
|
+
wrapped = data.claude ? computeWrappedStats(data.claude) : null;
|
|
3997
|
+
}
|
|
3998
|
+
const activityPayload = activity ? buildActivityArtifact(activity, "tokens", 365).payload : null;
|
|
3999
|
+
const { hubParams, jsonStore } = buildDashboardPayloads({
|
|
4000
|
+
usage: usage ?? null,
|
|
4001
|
+
wrapped,
|
|
4002
|
+
activity: activityPayload
|
|
4003
|
+
});
|
|
4004
|
+
const assetsDir = resolveDashboardAssets(import.meta.url);
|
|
4005
|
+
const server = await startDashboardServer({ assetsDir, jsonStore });
|
|
4006
|
+
const url = `http://127.0.0.1:${server.port}/dashboard/?${hubParams.toString()}`;
|
|
4007
|
+
const orange = "\x1B[38;5;208m";
|
|
4008
|
+
const reset = "\x1B[0m";
|
|
4009
|
+
console.log(`${orange}vibestats dashboard${reset} \u2192 ${url}`);
|
|
4010
|
+
console.log("Press Ctrl+C to stop.");
|
|
4011
|
+
openInBrowser(url);
|
|
4012
|
+
await new Promise((resolveFn) => {
|
|
4013
|
+
const shutdown = async () => {
|
|
4014
|
+
await server.close();
|
|
4015
|
+
resolveFn();
|
|
4016
|
+
};
|
|
4017
|
+
process.once("SIGINT", shutdown);
|
|
4018
|
+
process.once("SIGTERM", shutdown);
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
|
|
3669
4022
|
// src/cli-intent.ts
|
|
3670
4023
|
var COMMANDS = /* @__PURE__ */ new Set(["usage", "limits", "limit", "pace"]);
|
|
3671
4024
|
var SCOPES = /* @__PURE__ */ new Set(["claude", "codex", "all", "combined"]);
|
|
@@ -3856,8 +4209,8 @@ function applyCliIntent(args, intent) {
|
|
|
3856
4209
|
// src/limits/claude.ts
|
|
3857
4210
|
import { execFile as execFile2 } from "child_process";
|
|
3858
4211
|
import { randomUUID } from "crypto";
|
|
3859
|
-
import { dirname as
|
|
3860
|
-
import { fileURLToPath } from "url";
|
|
4212
|
+
import { dirname as dirname3 } from "path";
|
|
4213
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3861
4214
|
|
|
3862
4215
|
// src/limits/pace.ts
|
|
3863
4216
|
var PACE_EPSILON_PERCENT = 1;
|
|
@@ -4207,7 +4560,7 @@ function isClaudePromptReady(text) {
|
|
|
4207
4560
|
return /Claude Code v\d+\.\d+\.\d+/.test(normalized) && (normalized.includes("\u276F") || normalized.includes(">"));
|
|
4208
4561
|
}
|
|
4209
4562
|
function execFileCommand(command, args, options = {}) {
|
|
4210
|
-
return new Promise((
|
|
4563
|
+
return new Promise((resolve3, reject) => {
|
|
4211
4564
|
execFile2(
|
|
4212
4565
|
command,
|
|
4213
4566
|
[...args],
|
|
@@ -4221,18 +4574,18 @@ function execFileCommand(command, args, options = {}) {
|
|
|
4221
4574
|
reject(error);
|
|
4222
4575
|
return;
|
|
4223
4576
|
}
|
|
4224
|
-
|
|
4577
|
+
resolve3({ stdout, stderr });
|
|
4225
4578
|
}
|
|
4226
4579
|
);
|
|
4227
4580
|
});
|
|
4228
4581
|
}
|
|
4229
4582
|
function sleep2(ms) {
|
|
4230
|
-
return new Promise((
|
|
4583
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
4231
4584
|
}
|
|
4232
4585
|
function resolveDefaultClaudeUsageCwd() {
|
|
4233
4586
|
const envCwd = process.env.VIBESTATS_CLAUDE_USAGE_CWD ?? process.env.VIBESTATS_USAGE_CWD;
|
|
4234
4587
|
if (envCwd?.trim()) return envCwd;
|
|
4235
|
-
return
|
|
4588
|
+
return dirname3(fileURLToPath2(import.meta.url));
|
|
4236
4589
|
}
|
|
4237
4590
|
async function captureUsageWithTmux(options) {
|
|
4238
4591
|
const sessionName = `${TMUX_SESSION_PREFIX}-${randomUUID().replaceAll("-", "").slice(0, 16)}`;
|
|
@@ -4329,7 +4682,7 @@ async function fetchClaudeLimits(options = {}) {
|
|
|
4329
4682
|
}
|
|
4330
4683
|
|
|
4331
4684
|
// src/limits/codex.ts
|
|
4332
|
-
import { spawn } from "child_process";
|
|
4685
|
+
import { spawn as spawn2 } from "child_process";
|
|
4333
4686
|
function clampPercent3(value) {
|
|
4334
4687
|
const numeric = typeof value === "number" ? value : Number(value);
|
|
4335
4688
|
if (!Number.isFinite(numeric)) return 0;
|
|
@@ -4462,8 +4815,8 @@ var JsonRpcClient = class {
|
|
|
4462
4815
|
method,
|
|
4463
4816
|
...params === void 0 ? {} : { params }
|
|
4464
4817
|
};
|
|
4465
|
-
const promise = new Promise((
|
|
4466
|
-
this.pending.set(id, { resolve, reject });
|
|
4818
|
+
const promise = new Promise((resolve3, reject) => {
|
|
4819
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
4467
4820
|
});
|
|
4468
4821
|
try {
|
|
4469
4822
|
this.child.stdin.write(`${JSON.stringify(message)}
|
|
@@ -4543,7 +4896,7 @@ function scriptArgsForCodexStatus(command) {
|
|
|
4543
4896
|
async function fetchCodexStatusFallback(options = {}) {
|
|
4544
4897
|
const command = options.command || "codex";
|
|
4545
4898
|
const timeoutMs = options.timeoutMs ?? 8e3;
|
|
4546
|
-
const child =
|
|
4899
|
+
const child = spawn2("script", scriptArgsForCodexStatus(command), {
|
|
4547
4900
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4548
4901
|
env: process.env
|
|
4549
4902
|
});
|
|
@@ -4556,19 +4909,19 @@ async function fetchCodexStatusFallback(options = {}) {
|
|
|
4556
4909
|
child.stderr.on("data", (chunk) => {
|
|
4557
4910
|
output += chunk;
|
|
4558
4911
|
});
|
|
4559
|
-
await new Promise((
|
|
4912
|
+
await new Promise((resolve3) => {
|
|
4560
4913
|
const timer = setTimeout(() => {
|
|
4561
4914
|
child.kill("SIGTERM");
|
|
4562
|
-
|
|
4915
|
+
resolve3();
|
|
4563
4916
|
}, timeoutMs);
|
|
4564
4917
|
timer.unref();
|
|
4565
4918
|
child.on("error", () => {
|
|
4566
4919
|
clearTimeout(timer);
|
|
4567
|
-
|
|
4920
|
+
resolve3();
|
|
4568
4921
|
});
|
|
4569
4922
|
child.on("exit", () => {
|
|
4570
4923
|
clearTimeout(timer);
|
|
4571
|
-
|
|
4924
|
+
resolve3();
|
|
4572
4925
|
});
|
|
4573
4926
|
});
|
|
4574
4927
|
const windows = parseCodexStatusText(output);
|
|
@@ -4591,7 +4944,7 @@ async function fetchCodexStatusFallback(options = {}) {
|
|
|
4591
4944
|
async function fetchCodexLimits(options = {}) {
|
|
4592
4945
|
const command = options.command || "codex";
|
|
4593
4946
|
const timeoutMs = options.timeoutMs ?? 8e3;
|
|
4594
|
-
const child =
|
|
4947
|
+
const child = spawn2(command, ["-s", "read-only", "-a", "untrusted", "app-server"], {
|
|
4595
4948
|
stdio: ["pipe", "pipe", "pipe"],
|
|
4596
4949
|
env: process.env
|
|
4597
4950
|
});
|
|
@@ -4756,10 +5109,10 @@ function displayUsageLimits(limits, options = {}) {
|
|
|
4756
5109
|
}
|
|
4757
5110
|
|
|
4758
5111
|
// src/config.ts
|
|
4759
|
-
import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
5112
|
+
import { readFileSync, existsSync as existsSync3, writeFileSync } from "fs";
|
|
4760
5113
|
import { homedir as homedir5 } from "os";
|
|
4761
|
-
import { join as
|
|
4762
|
-
var CONFIG_PATH =
|
|
5114
|
+
import { join as join7 } from "path";
|
|
5115
|
+
var CONFIG_PATH = join7(homedir5(), ".vibestats.json");
|
|
4763
5116
|
var DEFAULT_CONFIG = {
|
|
4764
5117
|
baseUrl: "https://vibestats.wolfai.dev",
|
|
4765
5118
|
outputFormat: "normal",
|
|
@@ -4769,7 +5122,7 @@ var DEFAULT_CONFIG = {
|
|
|
4769
5122
|
hideCost: false
|
|
4770
5123
|
};
|
|
4771
5124
|
function loadConfig() {
|
|
4772
|
-
if (!
|
|
5125
|
+
if (!existsSync3(CONFIG_PATH)) {
|
|
4773
5126
|
return DEFAULT_CONFIG;
|
|
4774
5127
|
}
|
|
4775
5128
|
try {
|
|
@@ -4792,7 +5145,7 @@ function mergeConfig(defaults, user) {
|
|
|
4792
5145
|
};
|
|
4793
5146
|
}
|
|
4794
5147
|
function initConfig() {
|
|
4795
|
-
if (
|
|
5148
|
+
if (existsSync3(CONFIG_PATH)) {
|
|
4796
5149
|
console.log(`Config file already exists at ${CONFIG_PATH}`);
|
|
4797
5150
|
return;
|
|
4798
5151
|
}
|
|
@@ -4822,50 +5175,6 @@ function resolveOptions(cliArgs, config) {
|
|
|
4822
5175
|
};
|
|
4823
5176
|
}
|
|
4824
5177
|
|
|
4825
|
-
// src/shared/args.ts
|
|
4826
|
-
var modelFamilyArgs = {
|
|
4827
|
-
claude: {
|
|
4828
|
-
type: "boolean",
|
|
4829
|
-
description: "Show only Claude family stats across local usage sources",
|
|
4830
|
-
default: false
|
|
4831
|
-
},
|
|
4832
|
-
kimi: {
|
|
4833
|
-
type: "boolean",
|
|
4834
|
-
description: "Show only Kimi family stats across local usage sources",
|
|
4835
|
-
default: false
|
|
4836
|
-
},
|
|
4837
|
-
minimax: {
|
|
4838
|
-
type: "boolean",
|
|
4839
|
-
description: "Show only MiniMax family stats across local usage sources",
|
|
4840
|
-
default: false
|
|
4841
|
-
}
|
|
4842
|
-
};
|
|
4843
|
-
function getSelectedModelFamilies(args) {
|
|
4844
|
-
const families = [];
|
|
4845
|
-
if (args.claude === true) families.push("claude");
|
|
4846
|
-
if (args.kimi === true) families.push("kimi");
|
|
4847
|
-
if (args.minimax === true) families.push("minimax");
|
|
4848
|
-
return families;
|
|
4849
|
-
}
|
|
4850
|
-
var cacheArgs = {
|
|
4851
|
-
"no-cache": {
|
|
4852
|
-
type: "boolean",
|
|
4853
|
-
description: "Bypass the persistent local usage cache for this run",
|
|
4854
|
-
default: false
|
|
4855
|
-
},
|
|
4856
|
-
"refresh-cache": {
|
|
4857
|
-
type: "boolean",
|
|
4858
|
-
description: "Rebuild the persistent local usage cache before showing stats",
|
|
4859
|
-
default: false
|
|
4860
|
-
}
|
|
4861
|
-
};
|
|
4862
|
-
function getUsageCacheOptions(args) {
|
|
4863
|
-
return {
|
|
4864
|
-
useCache: args["no-cache"] !== true,
|
|
4865
|
-
refreshCache: args["refresh-cache"] === true
|
|
4866
|
-
};
|
|
4867
|
-
}
|
|
4868
|
-
|
|
4869
5178
|
// src/index.ts
|
|
4870
5179
|
function printCommandHelp(error) {
|
|
4871
5180
|
console.error(`Error: ${error}`);
|
|
@@ -4874,6 +5183,9 @@ function printCommandHelp(error) {
|
|
|
4874
5183
|
console.error(" vibestats limits all");
|
|
4875
5184
|
console.error(" vibestats limits claude");
|
|
4876
5185
|
console.error(" vibestats limits codex");
|
|
5186
|
+
console.error(" vibestats claude");
|
|
5187
|
+
console.error(" vibestats codex");
|
|
5188
|
+
console.error(" vibestats all");
|
|
4877
5189
|
console.error(" vibestats usage all");
|
|
4878
5190
|
console.error(" vibestats usage --combined --daily");
|
|
4879
5191
|
console.error(" vibestats usage codex --total");
|
|
@@ -5023,7 +5335,7 @@ async function publishArtifactWithFallback(artifact, baseUrl, fallbackUrl, prefe
|
|
|
5023
5335
|
var main = defineCommand({
|
|
5024
5336
|
meta: {
|
|
5025
5337
|
name: "vibestats",
|
|
5026
|
-
version: "1.
|
|
5338
|
+
version: "1.4.1",
|
|
5027
5339
|
description: "AI coding stats - usage tracking and annual wrapped for Claude Code & Codex"
|
|
5028
5340
|
},
|
|
5029
5341
|
args: {
|
|
@@ -5160,6 +5472,11 @@ var main = defineCommand({
|
|
|
5160
5472
|
description: "Show current config location and values",
|
|
5161
5473
|
default: false
|
|
5162
5474
|
},
|
|
5475
|
+
dashboard: {
|
|
5476
|
+
type: "boolean",
|
|
5477
|
+
description: "Open a local web dashboard with usage, activity, and wrapped views",
|
|
5478
|
+
default: false
|
|
5479
|
+
},
|
|
5163
5480
|
"claude-system": {
|
|
5164
5481
|
type: "boolean",
|
|
5165
5482
|
description: "Inspect ~/.claude.json account and app state",
|
|
@@ -5200,6 +5517,10 @@ var main = defineCommand({
|
|
|
5200
5517
|
await runLiveLimits(normalizedArgs, config);
|
|
5201
5518
|
return;
|
|
5202
5519
|
}
|
|
5520
|
+
if (normalizedArgs.dashboard) {
|
|
5521
|
+
await runDashboard(normalizedArgs, config);
|
|
5522
|
+
return;
|
|
5523
|
+
}
|
|
5203
5524
|
if (normalizedArgs.activity) {
|
|
5204
5525
|
await runActivity(normalizedArgs, config);
|
|
5205
5526
|
} else if (normalizedArgs.wrapped) {
|