coding-usage-bar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/README.zh-CN.md +53 -0
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/coding-usage-bar-limit.png +0 -0
- package/assets/coding-usage-bar-ok.png +0 -0
- package/assets/coding-usage-bar-over.png +0 -0
- package/assets/coding-usage-bar-under.png +0 -0
- package/assets/provider-claude.png +0 -0
- package/assets/provider-codex.png +0 -0
- package/assets/provider-deepseek.png +0 -0
- package/assets/provider-glm-dark.png +0 -0
- package/assets/provider-glm.png +0 -0
- package/assets/provider-marks.png +0 -0
- package/assets/provider-minimax.png +0 -0
- package/dist/burn.d.ts +3 -0
- package/dist/burn.js +164 -0
- package/dist/card.d.ts +4 -0
- package/dist/card.js +79 -0
- package/dist/claude.d.ts +9 -0
- package/dist/claude.js +141 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +129 -0
- package/dist/codex.d.ts +6 -0
- package/dist/codex.js +128 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +64 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +16 -0
- package/dist/deepseek.d.ts +18 -0
- package/dist/deepseek.js +47 -0
- package/dist/doctor.d.ts +6 -0
- package/dist/doctor.js +225 -0
- package/dist/format.d.ts +9 -0
- package/dist/format.js +92 -0
- package/dist/fs-util.d.ts +6 -0
- package/dist/fs-util.js +39 -0
- package/dist/glm.d.ts +22 -0
- package/dist/glm.js +62 -0
- package/dist/install.d.ts +25 -0
- package/dist/install.js +432 -0
- package/dist/menubar.d.ts +23 -0
- package/dist/menubar.js +880 -0
- package/dist/minimax.d.ts +47 -0
- package/dist/minimax.js +100 -0
- package/dist/node-runtime.d.ts +1 -0
- package/dist/node-runtime.js +29 -0
- package/dist/notifier.d.ts +7 -0
- package/dist/notifier.js +121 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +45 -0
- package/dist/runtime.d.ts +23 -0
- package/dist/runtime.js +244 -0
- package/dist/star.d.ts +15 -0
- package/dist/star.js +117 -0
- package/dist/status.d.ts +14 -0
- package/dist/status.js +65 -0
- package/dist/store.d.ts +4 -0
- package/dist/store.js +29 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +1 -0
- package/dist/usage.d.ts +12 -0
- package/dist/usage.js +56 -0
- package/docs/assets/hero.png +0 -0
- package/fixtures/claude/latest.json +20 -0
- package/fixtures/claude/samples.jsonl +3 -0
- package/fixtures/codex/latest.json +20 -0
- package/fixtures/codex/samples.jsonl +3 -0
- package/fixtures/deepseek/latest.json +12 -0
- package/package.json +59 -0
package/dist/star.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readJsonFile, writeJsonAtomic } from "./fs-util.js";
|
|
3
|
+
const REPO = "hanzhangzzz/coding-usage-bar";
|
|
4
|
+
const PROMPT = "[coding-usage-bar] Enjoying Coding Usage Bar? Star it on GitHub? [y/N] ";
|
|
5
|
+
function defaultIsInteractive() {
|
|
6
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
7
|
+
}
|
|
8
|
+
export function isStarConsent(answer) {
|
|
9
|
+
const normalized = answer.trim().toLowerCase();
|
|
10
|
+
return normalized === "y" || normalized === "yes";
|
|
11
|
+
}
|
|
12
|
+
function defaultConfirmStar(prompt) {
|
|
13
|
+
if (!defaultIsInteractive()) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const answer = execFileSync("/bin/sh", [
|
|
18
|
+
"-c",
|
|
19
|
+
'printf "%s" "$1" > /dev/tty; IFS= read -r answer < /dev/tty; printf "%s" "$answer"',
|
|
20
|
+
"coding-usage-bar-star-prompt",
|
|
21
|
+
prompt,
|
|
22
|
+
], { encoding: "utf8" });
|
|
23
|
+
return isStarConsent(answer);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function ghStarArgs(repo) {
|
|
30
|
+
return [
|
|
31
|
+
"api",
|
|
32
|
+
"--method",
|
|
33
|
+
"PUT",
|
|
34
|
+
`/user/starred/${repo}`,
|
|
35
|
+
"--silent",
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
export function ghAuthStatusArgs() {
|
|
39
|
+
return ["auth", "status", "-h", "github.com"];
|
|
40
|
+
}
|
|
41
|
+
function commandErrorMessage(error) {
|
|
42
|
+
if (error && typeof error === "object") {
|
|
43
|
+
const stderr = error.stderr;
|
|
44
|
+
if (Buffer.isBuffer(stderr)) {
|
|
45
|
+
const message = stderr.toString("utf8").trim();
|
|
46
|
+
if (message) {
|
|
47
|
+
return message;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (typeof stderr === "string" && stderr.trim()) {
|
|
51
|
+
return stderr.trim();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return error instanceof Error ? error.message : String(error);
|
|
55
|
+
}
|
|
56
|
+
function defaultStarRepo(repo) {
|
|
57
|
+
execFileSync("gh", ghStarArgs(repo), { stdio: "pipe" });
|
|
58
|
+
}
|
|
59
|
+
function defaultCanStarWithGh() {
|
|
60
|
+
try {
|
|
61
|
+
execFileSync("gh", ghAuthStatusArgs(), { stdio: "ignore" });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function canStarWithGh(options) {
|
|
69
|
+
if (typeof options.canStarWithGh === "boolean") {
|
|
70
|
+
return options.canStarWithGh;
|
|
71
|
+
}
|
|
72
|
+
if (typeof options.canStarWithGh === "function") {
|
|
73
|
+
return options.canStarWithGh();
|
|
74
|
+
}
|
|
75
|
+
return defaultCanStarWithGh();
|
|
76
|
+
}
|
|
77
|
+
function hasPromptState(paths) {
|
|
78
|
+
const state = readJsonFile(paths.starPromptFile);
|
|
79
|
+
return state?.repo === REPO && (state.outcome === "starred" || state.outcome === "skipped");
|
|
80
|
+
}
|
|
81
|
+
export function maybePromptForStar(paths, options) {
|
|
82
|
+
const env = options.env ?? process.env;
|
|
83
|
+
if (options.dryRun || env.CI || options.isInteractive === false || hasPromptState(paths)) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
if (options.isInteractive === undefined && !defaultIsInteractive()) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
if (!canStarWithGh(options)) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const confirmStar = options.confirmStar ?? defaultConfirmStar;
|
|
93
|
+
const starRepo = options.starRepo ?? defaultStarRepo;
|
|
94
|
+
const now = options.now ?? (() => new Date());
|
|
95
|
+
const accepted = confirmStar(PROMPT);
|
|
96
|
+
const state = {
|
|
97
|
+
repo: REPO,
|
|
98
|
+
promptedAt: now().toISOString(),
|
|
99
|
+
response: accepted ? "accepted" : "declined",
|
|
100
|
+
outcome: accepted ? "starred" : "skipped",
|
|
101
|
+
};
|
|
102
|
+
if (!accepted) {
|
|
103
|
+
writeJsonAtomic(paths.starPromptFile, state);
|
|
104
|
+
return ["Skipped GitHub star prompt."];
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
starRepo(REPO);
|
|
108
|
+
writeJsonAtomic(paths.starPromptFile, state);
|
|
109
|
+
return ["Starred Coding Usage Bar on GitHub."];
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
state.outcome = "failed";
|
|
113
|
+
state.error = commandErrorMessage(error);
|
|
114
|
+
writeJsonAtomic(paths.starPromptFile, state);
|
|
115
|
+
return [`Could not star Coding Usage Bar with gh: ${state.error}`];
|
|
116
|
+
}
|
|
117
|
+
}
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BurnProfile, ProviderUsage, RuntimePaths, StatusIssue, StatusSnapshot } from "./types.js";
|
|
2
|
+
export declare function createStatusSnapshot(usages: ProviderUsage[], profile: BurnProfile, options?: {
|
|
3
|
+
paths?: RuntimePaths;
|
|
4
|
+
generatedAt?: Date;
|
|
5
|
+
fixtureSamples?: Map<string, ProviderUsage[]>;
|
|
6
|
+
issues?: StatusIssue[];
|
|
7
|
+
staleAfterSeconds?: number;
|
|
8
|
+
}): StatusSnapshot;
|
|
9
|
+
export declare function refreshStatusSnapshotFreshness(snapshot: StatusSnapshot, options?: {
|
|
10
|
+
now?: Date;
|
|
11
|
+
staleAfterSeconds?: number;
|
|
12
|
+
}): StatusSnapshot;
|
|
13
|
+
export declare function saveStatusSnapshot(snapshot: StatusSnapshot, paths?: RuntimePaths): void;
|
|
14
|
+
export declare function loadStatusSnapshot(paths?: RuntimePaths): StatusSnapshot | null;
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { analyzeUsage } from "./burn.js";
|
|
2
|
+
import { readJsonFile, writeJsonAtomic } from "./fs-util.js";
|
|
3
|
+
import { buildPaths } from "./paths.js";
|
|
4
|
+
import { loadSamples } from "./store.js";
|
|
5
|
+
const DEFAULT_STALE_AFTER_SECONDS = 10 * 60;
|
|
6
|
+
function buildProviderMeta(usage, generatedAt, staleAfterSeconds) {
|
|
7
|
+
const ageSeconds = Math.max(0, Math.round((generatedAt.getTime() - Date.parse(usage.observedAt)) / 1000));
|
|
8
|
+
return {
|
|
9
|
+
source: usage.source,
|
|
10
|
+
observedAt: usage.observedAt,
|
|
11
|
+
ageSeconds,
|
|
12
|
+
stale: ageSeconds > staleAfterSeconds,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function staleIssuesFor(providers) {
|
|
16
|
+
return providers
|
|
17
|
+
.filter((provider) => provider.meta.stale)
|
|
18
|
+
.map((provider) => ({
|
|
19
|
+
provider: provider.usage.provider,
|
|
20
|
+
severity: "warning",
|
|
21
|
+
code: "USAGE_STALE",
|
|
22
|
+
message: `${provider.usage.provider} usage is stale; latest observation is ${provider.meta.ageSeconds}s old.`,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
export function createStatusSnapshot(usages, profile, options = {}) {
|
|
26
|
+
const paths = options.paths ?? buildPaths();
|
|
27
|
+
const generatedAt = options.generatedAt ?? new Date();
|
|
28
|
+
const staleAfterSeconds = options.staleAfterSeconds ?? DEFAULT_STALE_AFTER_SECONDS;
|
|
29
|
+
const providers = usages.map((usage) => {
|
|
30
|
+
const fixtureSamples = options.fixtureSamples?.get(usage.provider);
|
|
31
|
+
const samples = fixtureSamples ?? loadSamples(usage.provider, paths);
|
|
32
|
+
return {
|
|
33
|
+
usage,
|
|
34
|
+
analysis: analyzeUsage(usage, samples.length > 0 ? samples : usages.filter((item) => item.provider === usage.provider), profile, generatedAt),
|
|
35
|
+
meta: buildProviderMeta(usage, generatedAt, staleAfterSeconds),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
const staleIssues = staleIssuesFor(providers);
|
|
39
|
+
return {
|
|
40
|
+
generatedAt: generatedAt.toISOString(),
|
|
41
|
+
profile,
|
|
42
|
+
providers,
|
|
43
|
+
issues: [...(options.issues ?? []), ...staleIssues],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function refreshStatusSnapshotFreshness(snapshot, options = {}) {
|
|
47
|
+
const now = options.now ?? new Date();
|
|
48
|
+
const staleAfterSeconds = options.staleAfterSeconds ?? DEFAULT_STALE_AFTER_SECONDS;
|
|
49
|
+
const providers = snapshot.providers.map((provider) => ({
|
|
50
|
+
...provider,
|
|
51
|
+
meta: buildProviderMeta(provider.usage, now, staleAfterSeconds),
|
|
52
|
+
}));
|
|
53
|
+
const nonStaleIssues = snapshot.issues.filter((issue) => issue.code !== "USAGE_STALE");
|
|
54
|
+
return {
|
|
55
|
+
...snapshot,
|
|
56
|
+
providers,
|
|
57
|
+
issues: [...nonStaleIssues, ...staleIssuesFor(providers)],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function saveStatusSnapshot(snapshot, paths = buildPaths()) {
|
|
61
|
+
writeJsonAtomic(paths.statusFile, snapshot);
|
|
62
|
+
}
|
|
63
|
+
export function loadStatusSnapshot(paths = buildPaths()) {
|
|
64
|
+
return readJsonFile(paths.statusFile);
|
|
65
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ProviderId, ProviderUsage, RuntimePaths } from "./types.js";
|
|
2
|
+
export declare function saveUsage(usage: ProviderUsage, paths?: RuntimePaths): void;
|
|
3
|
+
export declare function loadLatestUsage(provider: ProviderId, paths?: RuntimePaths): ProviderUsage | null;
|
|
4
|
+
export declare function loadSamples(provider: ProviderId, paths?: RuntimePaths): ProviderUsage[];
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { appendJsonLine, readJsonFile, writeJsonAtomic } from "./fs-util.js";
|
|
3
|
+
import { buildPaths, providerLatestPath, providerSamplesPath } from "./paths.js";
|
|
4
|
+
export function saveUsage(usage, paths = buildPaths()) {
|
|
5
|
+
writeJsonAtomic(providerLatestPath(paths, usage.provider), usage);
|
|
6
|
+
appendJsonLine(providerSamplesPath(paths, usage.provider), usage);
|
|
7
|
+
}
|
|
8
|
+
export function loadLatestUsage(provider, paths = buildPaths()) {
|
|
9
|
+
return readJsonFile(providerLatestPath(paths, provider));
|
|
10
|
+
}
|
|
11
|
+
export function loadSamples(provider, paths = buildPaths()) {
|
|
12
|
+
const file = providerSamplesPath(paths, provider);
|
|
13
|
+
if (!fs.existsSync(file)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return fs
|
|
17
|
+
.readFileSync(file, "utf8")
|
|
18
|
+
.split(/\r?\n/)
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.map((line) => {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(line);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
.filter((item) => item !== null);
|
|
29
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export type ProviderId = "claude" | "codex" | "glm" | "deepseek" | "minimax";
|
|
2
|
+
export type WindowName = "five_hour" | "seven_day";
|
|
3
|
+
export type BurnProfile = "low" | "high";
|
|
4
|
+
export type BurnState = "RAW" | "UNDER_BURN" | "ON_TRACK" | "OVER_BURN" | "LIMIT_RISK";
|
|
5
|
+
export interface UsageWindow {
|
|
6
|
+
name: WindowName;
|
|
7
|
+
windowMinutes: number;
|
|
8
|
+
usedPercent: number;
|
|
9
|
+
resetsAt: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ProviderUsage {
|
|
12
|
+
provider: ProviderId;
|
|
13
|
+
source: string;
|
|
14
|
+
observedAt: string;
|
|
15
|
+
planType?: string | null;
|
|
16
|
+
windows: UsageWindow[];
|
|
17
|
+
balance?: {
|
|
18
|
+
total: string;
|
|
19
|
+
currency: string;
|
|
20
|
+
isAvailable: boolean;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface BurnAnalysis {
|
|
24
|
+
provider: ProviderId;
|
|
25
|
+
state: BurnState;
|
|
26
|
+
profile: BurnProfile;
|
|
27
|
+
observedAt: string;
|
|
28
|
+
fiveHour?: UsageWindow;
|
|
29
|
+
sevenDay?: UsageWindow;
|
|
30
|
+
target?: {
|
|
31
|
+
minPercent: number;
|
|
32
|
+
maxPercent: number;
|
|
33
|
+
recommendedPercent: number;
|
|
34
|
+
conversionRate: number;
|
|
35
|
+
};
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
export interface DoctorCheck {
|
|
39
|
+
name: string;
|
|
40
|
+
ok: boolean;
|
|
41
|
+
message: string;
|
|
42
|
+
}
|
|
43
|
+
export interface GlmConfig {
|
|
44
|
+
baseUrl?: string;
|
|
45
|
+
apiKey?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface DeepseekConfig {
|
|
48
|
+
apiKey?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface MinimaxConfig {
|
|
51
|
+
region?: "cn" | "global";
|
|
52
|
+
apiKey?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface RuntimePaths {
|
|
55
|
+
homeDir: string;
|
|
56
|
+
stateDir: string;
|
|
57
|
+
configFile: string;
|
|
58
|
+
claudeDir: string;
|
|
59
|
+
codexDir: string;
|
|
60
|
+
glmDir: string;
|
|
61
|
+
deepseekDir: string;
|
|
62
|
+
minimaxDir: string;
|
|
63
|
+
notificationStateFile: string;
|
|
64
|
+
statusFile: string;
|
|
65
|
+
starPromptFile: string;
|
|
66
|
+
cliBinDir: string;
|
|
67
|
+
cliBinFile: string;
|
|
68
|
+
swiftBarPluginDir: string;
|
|
69
|
+
swiftBarPluginFile: string;
|
|
70
|
+
launchAgentFile: string;
|
|
71
|
+
claudeSettingsFile: string;
|
|
72
|
+
claudeStatusLineScript: string;
|
|
73
|
+
}
|
|
74
|
+
export interface ProviderStatus {
|
|
75
|
+
usage: ProviderUsage;
|
|
76
|
+
analysis: BurnAnalysis;
|
|
77
|
+
meta: {
|
|
78
|
+
source: string;
|
|
79
|
+
observedAt: string;
|
|
80
|
+
ageSeconds: number;
|
|
81
|
+
stale: boolean;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export interface StatusIssue {
|
|
85
|
+
provider?: ProviderId;
|
|
86
|
+
severity: "warning" | "error";
|
|
87
|
+
code: string;
|
|
88
|
+
message: string;
|
|
89
|
+
}
|
|
90
|
+
export interface StatusSnapshot {
|
|
91
|
+
generatedAt: string;
|
|
92
|
+
profile: BurnProfile;
|
|
93
|
+
providers: ProviderStatus[];
|
|
94
|
+
issues: StatusIssue[];
|
|
95
|
+
}
|
|
96
|
+
export interface BurnConfig {
|
|
97
|
+
providers: ProviderId[];
|
|
98
|
+
glm?: GlmConfig;
|
|
99
|
+
deepseek?: DeepseekConfig;
|
|
100
|
+
minimax?: MinimaxConfig;
|
|
101
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ProviderId, ProviderUsage, UsageWindow, WindowName } from "./types.js";
|
|
2
|
+
export declare function normalizeWindow(name: WindowName, raw: unknown, fallbackMinutes: number): UsageWindow | null;
|
|
3
|
+
export declare function findWindow(usage: ProviderUsage, name: WindowName): UsageWindow | undefined;
|
|
4
|
+
export declare function validateProviderUsage(usage: ProviderUsage): void;
|
|
5
|
+
export declare function makeProviderUsage(input: {
|
|
6
|
+
provider: ProviderId;
|
|
7
|
+
source: string;
|
|
8
|
+
observedAt?: string;
|
|
9
|
+
planType?: string | null;
|
|
10
|
+
fiveHour: UsageWindow;
|
|
11
|
+
sevenDay: UsageWindow;
|
|
12
|
+
}): ProviderUsage;
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
function asNumber(value) {
|
|
2
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
3
|
+
return value;
|
|
4
|
+
}
|
|
5
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
6
|
+
const parsed = Number(value);
|
|
7
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function asString(value) {
|
|
12
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
13
|
+
}
|
|
14
|
+
export function normalizeWindow(name, raw, fallbackMinutes) {
|
|
15
|
+
if (raw === null || typeof raw !== "object") {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const record = raw;
|
|
19
|
+
const usedPercent = asNumber(record.used_percent ?? record.used_percentage);
|
|
20
|
+
const windowMinutes = asNumber(record.window_minutes ?? record.windowMinutes) ?? fallbackMinutes;
|
|
21
|
+
const resetsAtRaw = record.resets_at ?? record.resetsAt;
|
|
22
|
+
const resetsAt = typeof resetsAtRaw === "number"
|
|
23
|
+
? new Date(resetsAtRaw * 1000).toISOString()
|
|
24
|
+
: asString(resetsAtRaw);
|
|
25
|
+
if (usedPercent === null || resetsAt === null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
name,
|
|
30
|
+
windowMinutes,
|
|
31
|
+
usedPercent,
|
|
32
|
+
resetsAt,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function findWindow(usage, name) {
|
|
36
|
+
return usage.windows.find((window) => window.name === name);
|
|
37
|
+
}
|
|
38
|
+
export function validateProviderUsage(usage) {
|
|
39
|
+
const fiveHour = findWindow(usage, "five_hour");
|
|
40
|
+
const sevenDay = findWindow(usage, "seven_day");
|
|
41
|
+
if (!fiveHour || fiveHour.windowMinutes !== 300) {
|
|
42
|
+
throw new Error(`${usage.provider}: missing 5h usage window`);
|
|
43
|
+
}
|
|
44
|
+
if (!sevenDay || sevenDay.windowMinutes !== 10080) {
|
|
45
|
+
throw new Error(`${usage.provider}: missing 7d usage window`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function makeProviderUsage(input) {
|
|
49
|
+
return {
|
|
50
|
+
provider: input.provider,
|
|
51
|
+
source: input.source,
|
|
52
|
+
observedAt: input.observedAt ?? new Date().toISOString(),
|
|
53
|
+
planType: input.planType ?? null,
|
|
54
|
+
windows: [input.fiveHour, input.sevenDay],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"provider": "claude",
|
|
3
|
+
"source": "fixture",
|
|
4
|
+
"observedAt": "2026-05-08T00:00:00.000Z",
|
|
5
|
+
"planType": null,
|
|
6
|
+
"windows": [
|
|
7
|
+
{
|
|
8
|
+
"name": "five_hour",
|
|
9
|
+
"windowMinutes": 300,
|
|
10
|
+
"usedPercent": 25,
|
|
11
|
+
"resetsAt": "2026-05-08T02:00:00.000Z"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "seven_day",
|
|
15
|
+
"windowMinutes": 10080,
|
|
16
|
+
"usedPercent": 35,
|
|
17
|
+
"resetsAt": "2026-05-12T00:00:00.000Z"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"provider":"claude","source":"fixture","observedAt":"2026-05-07T22:00:00.000Z","planType":null,"windows":[{"name":"five_hour","windowMinutes":300,"usedPercent":5,"resetsAt":"2026-05-08T02:00:00.000Z"},{"name":"seven_day","windowMinutes":10080,"usedPercent":33,"resetsAt":"2026-05-12T00:00:00.000Z"}]}
|
|
2
|
+
{"provider":"claude","source":"fixture","observedAt":"2026-05-07T23:00:00.000Z","planType":null,"windows":[{"name":"five_hour","windowMinutes":300,"usedPercent":15,"resetsAt":"2026-05-08T02:00:00.000Z"},{"name":"seven_day","windowMinutes":10080,"usedPercent":34,"resetsAt":"2026-05-12T00:00:00.000Z"}]}
|
|
3
|
+
{"provider":"claude","source":"fixture","observedAt":"2026-05-08T00:00:00.000Z","planType":null,"windows":[{"name":"five_hour","windowMinutes":300,"usedPercent":25,"resetsAt":"2026-05-08T02:00:00.000Z"},{"name":"seven_day","windowMinutes":10080,"usedPercent":35,"resetsAt":"2026-05-12T00:00:00.000Z"}]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"provider": "codex",
|
|
3
|
+
"source": "fixture",
|
|
4
|
+
"observedAt": "2026-05-08T00:00:00.000Z",
|
|
5
|
+
"planType": "pro",
|
|
6
|
+
"windows": [
|
|
7
|
+
{
|
|
8
|
+
"name": "five_hour",
|
|
9
|
+
"windowMinutes": 300,
|
|
10
|
+
"usedPercent": 92,
|
|
11
|
+
"resetsAt": "2026-05-08T00:45:00.000Z"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "seven_day",
|
|
15
|
+
"windowMinutes": 10080,
|
|
16
|
+
"usedPercent": 41,
|
|
17
|
+
"resetsAt": "2026-05-13T00:00:00.000Z"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"provider":"codex","source":"fixture","observedAt":"2026-05-07T22:00:00.000Z","planType":"pro","windows":[{"name":"five_hour","windowMinutes":300,"usedPercent":20,"resetsAt":"2026-05-08T00:45:00.000Z"},{"name":"seven_day","windowMinutes":10080,"usedPercent":37,"resetsAt":"2026-05-13T00:00:00.000Z"}]}
|
|
2
|
+
{"provider":"codex","source":"fixture","observedAt":"2026-05-07T23:00:00.000Z","planType":"pro","windows":[{"name":"five_hour","windowMinutes":300,"usedPercent":55,"resetsAt":"2026-05-08T00:45:00.000Z"},{"name":"seven_day","windowMinutes":10080,"usedPercent":39,"resetsAt":"2026-05-13T00:00:00.000Z"}]}
|
|
3
|
+
{"provider":"codex","source":"fixture","observedAt":"2026-05-08T00:00:00.000Z","planType":"pro","windows":[{"name":"five_hour","windowMinutes":300,"usedPercent":92,"resetsAt":"2026-05-08T00:45:00.000Z"},{"name":"seven_day","windowMinutes":10080,"usedPercent":41,"resetsAt":"2026-05-13T00:00:00.000Z"}]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coding-usage-bar",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "macOS menu bar usage monitor for Claude Code, Codex, GLM, DeepSeek, and MiniMax.",
|
|
5
|
+
"author": "hanzhangzzz",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/hanzhangzzz/coding-usage-bar.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/hanzhangzzz/coding-usage-bar#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/hanzhangzzz/coding-usage-bar/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude-code",
|
|
17
|
+
"codex",
|
|
18
|
+
"openai",
|
|
19
|
+
"anthropic",
|
|
20
|
+
"macos",
|
|
21
|
+
"menu-bar",
|
|
22
|
+
"ai-coding",
|
|
23
|
+
"coding-plan",
|
|
24
|
+
"usage-monitor",
|
|
25
|
+
"quota-monitor",
|
|
26
|
+
"rate-limit",
|
|
27
|
+
"swiftbar",
|
|
28
|
+
"burn-rate"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"bin": {
|
|
32
|
+
"coding-usage-bar": "./dist/cli.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"assets",
|
|
36
|
+
"docs/assets/hero.png",
|
|
37
|
+
"dist",
|
|
38
|
+
"fixtures",
|
|
39
|
+
"README.md",
|
|
40
|
+
"README.zh-CN.md",
|
|
41
|
+
"THIRD_PARTY_NOTICES.md"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc && chmod +x dist/cli.js",
|
|
45
|
+
"test": "npm run build && node --test tests/*.test.js",
|
|
46
|
+
"clean": "rm -rf dist",
|
|
47
|
+
"prepack": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=20"
|
|
51
|
+
},
|
|
52
|
+
"os": [
|
|
53
|
+
"darwin"
|
|
54
|
+
],
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^24.10.1",
|
|
57
|
+
"typescript": "^5.9.3"
|
|
58
|
+
}
|
|
59
|
+
}
|