@kill-switch/agent-guard 0.1.6 → 0.1.7
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 +27 -2
- package/dist/claude-usage.d.ts +50 -0
- package/dist/claude-usage.js +131 -0
- package/dist/cli.js +37 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/limits.d.ts +10 -0
- package/dist/limits.js +1 -0
- package/dist/report.d.ts +3 -0
- package/dist/report.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -115,8 +115,33 @@ scarce resource isn't dollars — it's your plan's rate-limit quota, in two roll
|
|
|
115
115
|
- a **5-hour** window (burst protection), and
|
|
116
116
|
- a **weekly** (7-day) window — the real lockout risk, "resets a couple times a month".
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
### Easiest: `agent-guard usage`
|
|
119
|
+
|
|
120
|
+
Pull your **real** limits straight from Anthropic — the same data `/usage` shows — no proxy, no
|
|
121
|
+
workflow change:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
agent-guard usage # → 5-hour, weekly, and per-model (Sonnet/Opus) weekly utilization + resets
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
It reads your Claude Code OAuth token from the OS credential store (macOS Keychain
|
|
128
|
+
`Claude Code-credentials`, or `~/.claude/.credentials.json` on Linux — used only as a Bearer
|
|
129
|
+
header, **never logged or stored**) and GETs the `/api/oauth/usage` endpoint. `status`
|
|
130
|
+
auto-refreshes this (throttled to 120s). The endpoint is **undocumented**, so every call fails
|
|
131
|
+
soft — if it's unavailable, agent-guard falls back to the proxy or "unknown".
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
🟢 Claude Code plan limits · observed just now
|
|
135
|
+
[██░░░░░░░░░░░░░░░░░░] 5-hour limit 12% used, resets 9:19 AM
|
|
136
|
+
[███░░░░░░░░░░░░░░░░░] weekly limit 17% used, resets Tue 7:59 PM
|
|
137
|
+
[░░░░░░░░░░░░░░░░░░░░] weekly · Sonnet 1%
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Alternative: the proxy
|
|
141
|
+
|
|
142
|
+
Anthropic also reports your standing on every response via `anthropic-ratelimit-unified-*`
|
|
143
|
+
headers (5h + weekly only — no per-model). Run Claude Code **through the proxy** and agent-guard
|
|
144
|
+
reads them in-flight:
|
|
120
145
|
|
|
121
146
|
```sh
|
|
122
147
|
agent-guard proxy # meters Anthropic + reads limit headers
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real subscription usage from Anthropic's OAuth usage endpoint.
|
|
3
|
+
*
|
|
4
|
+
* This is the authoritative, structured source for Claude Code Pro/Max limits —
|
|
5
|
+
* the same data the `/usage` command shows — without a proxy, a screen-scrape, or
|
|
6
|
+
* a (hopeless) local estimate. It's the endpoint the polished community status-
|
|
7
|
+
* line tools settled on. One caveat: it's **undocumented**, so Anthropic could
|
|
8
|
+
* change or gate it; every call here fails soft (returns null) and the caller
|
|
9
|
+
* falls back to the proxy / "unknown".
|
|
10
|
+
*
|
|
11
|
+
* Token handling: the OAuth access token is read from the OS credential store
|
|
12
|
+
* (macOS Keychain `Claude Code-credentials`, or `~/.claude/.credentials.json` on
|
|
13
|
+
* Linux), used only as a Bearer header on the GET, and **never logged or
|
|
14
|
+
* persisted** by us.
|
|
15
|
+
*/
|
|
16
|
+
import { type LimitSnapshot } from "./limits.js";
|
|
17
|
+
/**
|
|
18
|
+
* Best-effort read of the Claude Code OAuth access token from the OS credential
|
|
19
|
+
* store. Returns null (never throws, never logs the token) if unavailable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readOAuthToken(): string | null;
|
|
22
|
+
interface UsageWindow {
|
|
23
|
+
utilization?: number | null;
|
|
24
|
+
resets_at?: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface UsageResponse {
|
|
27
|
+
five_hour?: UsageWindow | null;
|
|
28
|
+
seven_day?: UsageWindow | null;
|
|
29
|
+
seven_day_sonnet?: UsageWindow | null;
|
|
30
|
+
seven_day_opus?: UsageWindow | null;
|
|
31
|
+
extra_usage?: {
|
|
32
|
+
is_enabled?: boolean;
|
|
33
|
+
monthly_limit?: number;
|
|
34
|
+
used_credits?: number;
|
|
35
|
+
} | null;
|
|
36
|
+
}
|
|
37
|
+
/** GET the usage endpoint with the given token. Returns null on any failure. */
|
|
38
|
+
export declare function fetchUsage(token: string, timeoutMs?: number): Promise<UsageResponse | null>;
|
|
39
|
+
/** Map the OAuth usage response into a {@link LimitSnapshot}. */
|
|
40
|
+
export declare function usageToSnapshot(u: UsageResponse, now: number): LimitSnapshot;
|
|
41
|
+
/**
|
|
42
|
+
* Fetch real usage and persist it as the live snapshot, throttled. Returns the
|
|
43
|
+
* fresh snapshot on a successful fetch, or null if we skipped (throttled) or
|
|
44
|
+
* couldn't fetch (no token / endpoint down — caller falls back gracefully).
|
|
45
|
+
*/
|
|
46
|
+
export declare function refreshUsage(now: number, opts?: {
|
|
47
|
+
force?: boolean;
|
|
48
|
+
throttleMs?: number;
|
|
49
|
+
}): Promise<LimitSnapshot | null>;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real subscription usage from Anthropic's OAuth usage endpoint.
|
|
3
|
+
*
|
|
4
|
+
* This is the authoritative, structured source for Claude Code Pro/Max limits —
|
|
5
|
+
* the same data the `/usage` command shows — without a proxy, a screen-scrape, or
|
|
6
|
+
* a (hopeless) local estimate. It's the endpoint the polished community status-
|
|
7
|
+
* line tools settled on. One caveat: it's **undocumented**, so Anthropic could
|
|
8
|
+
* change or gate it; every call here fails soft (returns null) and the caller
|
|
9
|
+
* falls back to the proxy / "unknown".
|
|
10
|
+
*
|
|
11
|
+
* Token handling: the OAuth access token is read from the OS credential store
|
|
12
|
+
* (macOS Keychain `Claude Code-credentials`, or `~/.claude/.credentials.json` on
|
|
13
|
+
* Linux), used only as a Bearer header on the GET, and **never logged or
|
|
14
|
+
* persisted** by us.
|
|
15
|
+
*/
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { homedir, platform } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { loadLimitsState, saveLimitsState, parseReset, } from "./limits.js";
|
|
21
|
+
const USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
22
|
+
const OAUTH_BETA = "oauth-2025-04-20";
|
|
23
|
+
const DEFAULT_THROTTLE_MS = 120_000; // don't hammer the endpoint
|
|
24
|
+
/**
|
|
25
|
+
* Best-effort read of the Claude Code OAuth access token from the OS credential
|
|
26
|
+
* store. Returns null (never throws, never logs the token) if unavailable.
|
|
27
|
+
*/
|
|
28
|
+
export function readOAuthToken() {
|
|
29
|
+
try {
|
|
30
|
+
let raw;
|
|
31
|
+
if (platform() === "darwin") {
|
|
32
|
+
raw = execFileSync("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
timeout: 4000,
|
|
35
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
raw = readFileSync(join(homedir(), ".claude", ".credentials.json"), "utf8");
|
|
40
|
+
}
|
|
41
|
+
const j = JSON.parse(raw);
|
|
42
|
+
return j?.claudeAiOauth?.accessToken ?? j?.accessToken ?? null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** GET the usage endpoint with the given token. Returns null on any failure. */
|
|
49
|
+
export async function fetchUsage(token, timeoutMs = 8000) {
|
|
50
|
+
try {
|
|
51
|
+
const ctrl = new AbortController();
|
|
52
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
53
|
+
let res;
|
|
54
|
+
try {
|
|
55
|
+
res = await fetch(USAGE_URL, {
|
|
56
|
+
headers: {
|
|
57
|
+
authorization: `Bearer ${token}`,
|
|
58
|
+
"anthropic-beta": OAUTH_BETA,
|
|
59
|
+
"content-type": "application/json",
|
|
60
|
+
},
|
|
61
|
+
signal: ctrl.signal,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
clearTimeout(t);
|
|
66
|
+
}
|
|
67
|
+
if (!res.ok)
|
|
68
|
+
return null;
|
|
69
|
+
return (await res.json());
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convert one endpoint window to a {@link WindowState}. The endpoint's
|
|
77
|
+
* `utilization` is an integer **percent** (0–100) — including small values like
|
|
78
|
+
* 1 (= 1%), so we always divide by 100 (NOT the header parser's ambiguous
|
|
79
|
+
* >1.5 heuristic, which would read 1 as 100%).
|
|
80
|
+
*/
|
|
81
|
+
function toWindow(u, now) {
|
|
82
|
+
if (!u || typeof u.utilization !== "number")
|
|
83
|
+
return null;
|
|
84
|
+
return {
|
|
85
|
+
utilization: Math.max(0, Math.min(1, u.utilization / 100)),
|
|
86
|
+
resetAt: parseReset(u.resets_at ?? null, now),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Map the OAuth usage response into a {@link LimitSnapshot}. */
|
|
90
|
+
export function usageToSnapshot(u, now) {
|
|
91
|
+
const extras = [];
|
|
92
|
+
const addExtra = (label, w) => {
|
|
93
|
+
const s = toWindow(w, now);
|
|
94
|
+
if (s)
|
|
95
|
+
extras.push({ label, utilization: s.utilization, resetAt: s.resetAt });
|
|
96
|
+
};
|
|
97
|
+
addExtra("weekly · Sonnet", u.seven_day_sonnet);
|
|
98
|
+
addExtra("weekly · Opus", u.seven_day_opus);
|
|
99
|
+
return {
|
|
100
|
+
fiveHour: toWindow(u.five_hour, now),
|
|
101
|
+
weekly: toWindow(u.seven_day, now),
|
|
102
|
+
status: "oauth-usage",
|
|
103
|
+
observedAt: now,
|
|
104
|
+
extras: extras.length ? extras : undefined,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Fetch real usage and persist it as the live snapshot, throttled. Returns the
|
|
109
|
+
* fresh snapshot on a successful fetch, or null if we skipped (throttled) or
|
|
110
|
+
* couldn't fetch (no token / endpoint down — caller falls back gracefully).
|
|
111
|
+
*/
|
|
112
|
+
export async function refreshUsage(now, opts = {}) {
|
|
113
|
+
const throttle = opts.throttleMs ?? DEFAULT_THROTTLE_MS;
|
|
114
|
+
const state = loadLimitsState();
|
|
115
|
+
if (!opts.force && state.lastFetchAt && now - state.lastFetchAt < throttle && state.snapshot) {
|
|
116
|
+
return null; // recent enough — use the cached snapshot
|
|
117
|
+
}
|
|
118
|
+
const token = readOAuthToken();
|
|
119
|
+
if (!token)
|
|
120
|
+
return null;
|
|
121
|
+
const usage = await fetchUsage(token);
|
|
122
|
+
if (!usage) {
|
|
123
|
+
// mark the attempt so we don't retry every call when the endpoint is down
|
|
124
|
+
saveLimitsState({ ...loadLimitsState(), lastFetchAt: now });
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const snapshot = usageToSnapshot(usage, now);
|
|
128
|
+
const fresh = loadLimitsState();
|
|
129
|
+
saveLimitsState({ ...fresh, subscriptionDetected: true, snapshot, lastFetchAt: now });
|
|
130
|
+
return snapshot;
|
|
131
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import { evaluate } from "./budget.js";
|
|
|
19
19
|
import { fmtUSD } from "./cost.js";
|
|
20
20
|
import { installHook, setBudget, setLimits, resetLedger } from "./ops.js";
|
|
21
21
|
import { buildStatusReport, formatLimitsLines } from "./report.js";
|
|
22
|
+
import { refreshUsage } from "./claude-usage.js";
|
|
22
23
|
const program = new Command();
|
|
23
24
|
program
|
|
24
25
|
.name("agent-guard")
|
|
@@ -59,9 +60,15 @@ program
|
|
|
59
60
|
// ── status ───────────────────────────────────────────────────────────────────
|
|
60
61
|
program
|
|
61
62
|
.command("status")
|
|
62
|
-
.description("Show current session + daily spend
|
|
63
|
+
.description("Show current session + daily spend, and real Claude Code plan limits")
|
|
63
64
|
.option("--json", "Output as JSON")
|
|
64
|
-
.action((opts) => {
|
|
65
|
+
.action(async (opts) => {
|
|
66
|
+
try {
|
|
67
|
+
await refreshUsage(Date.now()); // throttled real-limits pull; never fails status
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* offline / no token */
|
|
71
|
+
}
|
|
65
72
|
const cfg = loadConfig();
|
|
66
73
|
const ledger = loadLedger();
|
|
67
74
|
const now = Date.now();
|
|
@@ -119,6 +126,34 @@ program
|
|
|
119
126
|
console.log(line);
|
|
120
127
|
}
|
|
121
128
|
});
|
|
129
|
+
// ── usage (real Claude Code plan limits) ─────────────────────────────────────
|
|
130
|
+
program
|
|
131
|
+
.command("usage")
|
|
132
|
+
.description("Fetch your REAL Claude Code plan limits (5h + weekly + per-model) from Anthropic")
|
|
133
|
+
.option("--json", "Output as JSON")
|
|
134
|
+
.action(async (opts) => {
|
|
135
|
+
let snap;
|
|
136
|
+
try {
|
|
137
|
+
snap = await refreshUsage(Date.now(), { force: true });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
snap = null;
|
|
141
|
+
}
|
|
142
|
+
const report = buildStatusReport();
|
|
143
|
+
if (opts.json) {
|
|
144
|
+
console.log(JSON.stringify({ fetched: !!snap, limits: report.limits }, null, 2));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!snap && report.limits.source !== "headers") {
|
|
148
|
+
console.log("Couldn't fetch usage — need a logged-in Claude Code (token in the macOS Keychain or ~/.claude/.credentials.json).");
|
|
149
|
+
console.log("The /api/oauth/usage endpoint is undocumented and may be unavailable.");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
console.log("");
|
|
153
|
+
for (const line of formatLimitsLines(report.limits))
|
|
154
|
+
console.log(line);
|
|
155
|
+
console.log("");
|
|
156
|
+
});
|
|
122
157
|
// ── pause / resume (escape hatch) ────────────────────────────────────────────
|
|
123
158
|
program
|
|
124
159
|
.command("pause")
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { startProxy, resolveUpstream, type ProxyOptions } from "./proxy.js";
|
|
|
18
18
|
export { runHook } from "./hook.js";
|
|
19
19
|
export { buildStatusReport, buildLimitsReport, formatLimitsLines, type StatusReport, type LimitsReport } from "./report.js";
|
|
20
20
|
export { detectPlanTier, mapRateLimitTier, tierLabel, type SubscriptionTier } from "./claude-code.js";
|
|
21
|
-
export {
|
|
21
|
+
export { readOAuthToken, fetchUsage, usageToSnapshot, refreshUsage, type UsageResponse } from "./claude-usage.js";
|
|
22
|
+
export { parseUnifiedHeaders, parseUtilization, parseReset, recordHeaders, loadLimitsState, saveLimitsState, emptyLimitsState, limitNotifyKey, unifiedHeaderDump, logUnifiedHeaders, WINDOW_MS, type LimitSnapshot, type WindowState, type ExtraWindow, type LimitsState, type LimitWindow, type HeaderGetter, } from "./limits.js";
|
|
22
23
|
export { assessWindow, assessSnapshot, worstLevel, type PacingAssessment, type PacingLevel, type PacingThresholds, } from "./pacing.js";
|
|
23
24
|
export { installHook, setBudget, setLimits, resetLedger, type InstallOptions, type InstallResult, type BudgetPatch, type LimitsPatch, } from "./ops.js";
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ export { startProxy, resolveUpstream } from "./proxy.js";
|
|
|
18
18
|
export { runHook } from "./hook.js";
|
|
19
19
|
export { buildStatusReport, buildLimitsReport, formatLimitsLines } from "./report.js";
|
|
20
20
|
export { detectPlanTier, mapRateLimitTier, tierLabel } from "./claude-code.js";
|
|
21
|
+
export { readOAuthToken, fetchUsage, usageToSnapshot, refreshUsage } from "./claude-usage.js";
|
|
21
22
|
export { parseUnifiedHeaders, parseUtilization, parseReset, recordHeaders, loadLimitsState, saveLimitsState, emptyLimitsState, limitNotifyKey, unifiedHeaderDump, logUnifiedHeaders, WINDOW_MS, } from "./limits.js";
|
|
22
23
|
export { assessWindow, assessSnapshot, worstLevel, } from "./pacing.js";
|
|
23
24
|
export { installHook, setBudget, setLimits, resetLedger, } from "./ops.js";
|
package/dist/limits.d.ts
CHANGED
|
@@ -32,6 +32,12 @@ export interface WindowState {
|
|
|
32
32
|
/** Raw per-window status string from Anthropic (e.g. "allowed" / "warning"), if any. */
|
|
33
33
|
status?: string;
|
|
34
34
|
}
|
|
35
|
+
/** A labelled extra window (e.g. per-model weekly) — display-only, no pacing. */
|
|
36
|
+
export interface ExtraWindow {
|
|
37
|
+
label: string;
|
|
38
|
+
utilization: number;
|
|
39
|
+
resetAt: number | null;
|
|
40
|
+
}
|
|
35
41
|
/** A point-in-time read of the account's subscription rate-limit standing. */
|
|
36
42
|
export interface LimitSnapshot {
|
|
37
43
|
fiveHour: WindowState | null;
|
|
@@ -40,6 +46,8 @@ export interface LimitSnapshot {
|
|
|
40
46
|
status: string | null;
|
|
41
47
|
/** Epoch ms when this snapshot was observed. */
|
|
42
48
|
observedAt: number;
|
|
49
|
+
/** Extra windows from the OAuth usage endpoint (per-model weekly, etc.) — display only. */
|
|
50
|
+
extras?: ExtraWindow[];
|
|
43
51
|
}
|
|
44
52
|
/** Persisted global state (account-wide, not per-session). */
|
|
45
53
|
export interface LimitsState {
|
|
@@ -51,6 +59,8 @@ export interface LimitsState {
|
|
|
51
59
|
notified: Record<string, boolean>;
|
|
52
60
|
/** Epoch ms we first logged the raw unified-* headers (write-once diagnostic). */
|
|
53
61
|
headersLoggedAt?: number;
|
|
62
|
+
/** Epoch ms of the last OAuth usage-endpoint fetch (for throttling). */
|
|
63
|
+
lastFetchAt?: number;
|
|
54
64
|
}
|
|
55
65
|
/** Nominal window durations, used for pacing math when a reset time is unknown. */
|
|
56
66
|
export declare const WINDOW_MS: Record<LimitWindow, number>;
|
package/dist/limits.js
CHANGED
package/dist/report.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { type GuardConfig } from "./config.js";
|
|
15
15
|
import { type SessionRecord, type Ledger } from "./ledger.js";
|
|
16
16
|
import { type Budget, type VerdictLevel } from "./budget.js";
|
|
17
|
+
import { type ExtraWindow } from "./limits.js";
|
|
17
18
|
import { type PacingAssessment, type PacingLevel } from "./pacing.js";
|
|
18
19
|
import { type SubscriptionTier } from "./claude-code.js";
|
|
19
20
|
export interface LimitsReport {
|
|
@@ -30,6 +31,8 @@ export interface LimitsReport {
|
|
|
30
31
|
observedAt: number | null;
|
|
31
32
|
/** Real per-window pacing (only when source === "headers"). */
|
|
32
33
|
windows: PacingAssessment[];
|
|
34
|
+
/** Extra display-only windows (per-model weekly) from the OAuth usage endpoint. */
|
|
35
|
+
extras: ExtraWindow[];
|
|
33
36
|
level: PacingLevel;
|
|
34
37
|
/** Absolute rolling cost (API-equivalent USD) — honest local signal, not a limit %. */
|
|
35
38
|
cost: {
|
package/dist/report.js
CHANGED
|
@@ -65,6 +65,7 @@ export function buildLimitsReport(cfg, ledger, now) {
|
|
|
65
65
|
subscriptionDetected: state.subscriptionDetected,
|
|
66
66
|
observedAt: snap.observedAt,
|
|
67
67
|
windows,
|
|
68
|
+
extras: snap.extras ?? [],
|
|
68
69
|
level: worstLevel(windows),
|
|
69
70
|
cost,
|
|
70
71
|
};
|
|
@@ -79,6 +80,7 @@ export function buildLimitsReport(cfg, ledger, now) {
|
|
|
79
80
|
subscriptionDetected: state.subscriptionDetected,
|
|
80
81
|
observedAt: null,
|
|
81
82
|
windows: [],
|
|
83
|
+
extras: [],
|
|
82
84
|
level: "ok",
|
|
83
85
|
cost,
|
|
84
86
|
};
|
|
@@ -109,6 +111,10 @@ export function formatLimitsLines(limits, now = Date.now()) {
|
|
|
109
111
|
const lines = [`${icon} Claude Code plan limits · observed ${limits.observedAt ? ageString(limits.observedAt, now) : "—"}`];
|
|
110
112
|
for (const w of limits.windows)
|
|
111
113
|
lines.push(` ${bar(w.utilization)} ${w.message}`);
|
|
114
|
+
for (const e of limits.extras) {
|
|
115
|
+
const pct = `${Math.round(e.utilization * 100)}%`.padStart(4);
|
|
116
|
+
lines.push(` ${bar(e.utilization)} ${e.label} ${pct}`);
|
|
117
|
+
}
|
|
112
118
|
return lines;
|
|
113
119
|
}
|
|
114
120
|
// No live data: show what's honestly knowable — the tier and absolute cost,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kill-switch/agent-guard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Kill Switch for coding agents — stop runaway Claude Code / Cursor / Aider sessions from racking up an LLM bill. Native hook + token-metering proxy with per-session and daily-rolling budgets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|