@kill-switch/agent-guard 0.1.5 → 0.1.6
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 +11 -5
- package/dist/claude-code.d.ts +22 -0
- package/dist/claude-code.js +48 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/report.d.ts +25 -13
- package/dist/report.js +62 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -136,18 +136,24 @@ your alert channels:
|
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
`status` shows it; the hook injects it into the session even when only the hook is running
|
|
139
|
-
(it reads the snapshot the proxy persisted).
|
|
140
|
-
|
|
139
|
+
(it reads the snapshot the proxy persisted).
|
|
140
|
+
|
|
141
|
+
**Without the proxy, real percentages are unknowable** — Claude Code fetches them from Anthropic
|
|
142
|
+
and never writes them to disk, and local cost does *not* map to Anthropic's internal rate-limit
|
|
143
|
+
units (so we don't fake a "% of limit"). Instead, hook-only mode shows what's honestly knowable:
|
|
144
|
+
your **auto-detected plan tier** (read from `~/.claude.json`) plus **absolute rolling cost** —
|
|
145
|
+
and points you at the proxy for the real numbers. Your tier needs no flag; `--plan` only
|
|
146
|
+
overrides the auto-detection.
|
|
141
147
|
|
|
142
148
|
```sh
|
|
143
|
-
ks guard config --plan max5 # auto | pro | max5 | max20
|
|
149
|
+
ks guard config --plan max5 # auto (detect) | pro | max5 | max20
|
|
144
150
|
```
|
|
145
151
|
|
|
146
|
-
Tune the thresholds (0–1 utilization) if the
|
|
152
|
+
Tune the thresholds (0–1 utilization) if the proxy's pacing is too eager:
|
|
147
153
|
|
|
148
154
|
| Setting | Meaning | Default |
|
|
149
155
|
|---|---|---|
|
|
150
|
-
| `--plan` (`AGENT_GUARD_PLAN`) | `auto` (
|
|
156
|
+
| `--plan` (`AGENT_GUARD_PLAN`) | `auto` (detect from ~/.claude.json) or pin a tier | `auto` |
|
|
151
157
|
| `--weekly-soft` / `--weekly-danger` | weekly warn / danger utilization | 0.6 / 0.85 |
|
|
152
158
|
| `--5h-soft` / `--5h-danger` | 5-hour warn / danger utilization | 0.7 / 0.9 |
|
|
153
159
|
| `--burn-ratio` | pace multiplier that triggers a warning | 1.5 |
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read what Claude Code persists locally about the account.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code stores the user's plan tier in `~/.claude.json` under
|
|
5
|
+
* `oauthAccount` — so we can auto-detect it instead of making the user pass
|
|
6
|
+
* `--plan`. Note: Claude Code does NOT persist the live `/usage` rate-limit
|
|
7
|
+
* percentages anywhere readable (they're fetched from Anthropic and rendered in
|
|
8
|
+
* the TUI only) — the proxy's `unified-*` response headers remain the single
|
|
9
|
+
* source of truth for real utilization. This module is best-effort and never
|
|
10
|
+
* throws; a missing/unreadable file just means "unknown".
|
|
11
|
+
*/
|
|
12
|
+
/** The subscription tiers agent-guard knows about (a subset of LimitsConfig.plan). */
|
|
13
|
+
export type SubscriptionTier = "pro" | "max5" | "max20";
|
|
14
|
+
/** Map a Claude Code rate-limit-tier string (e.g. "default_claude_max_20x") to our tier. */
|
|
15
|
+
export declare function mapRateLimitTier(raw: string | null | undefined): SubscriptionTier | null;
|
|
16
|
+
/**
|
|
17
|
+
* Best-effort read of the user's plan tier from `~/.claude.json`. Prefers the
|
|
18
|
+
* per-user tier over the org default when present. Returns null if absent.
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectPlanTier(claudeJsonPath?: string): SubscriptionTier | null;
|
|
21
|
+
/** Human label for a tier. */
|
|
22
|
+
export declare function tierLabel(tier: SubscriptionTier): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read what Claude Code persists locally about the account.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code stores the user's plan tier in `~/.claude.json` under
|
|
5
|
+
* `oauthAccount` — so we can auto-detect it instead of making the user pass
|
|
6
|
+
* `--plan`. Note: Claude Code does NOT persist the live `/usage` rate-limit
|
|
7
|
+
* percentages anywhere readable (they're fetched from Anthropic and rendered in
|
|
8
|
+
* the TUI only) — the proxy's `unified-*` response headers remain the single
|
|
9
|
+
* source of truth for real utilization. This module is best-effort and never
|
|
10
|
+
* throws; a missing/unreadable file just means "unknown".
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
/** Map a Claude Code rate-limit-tier string (e.g. "default_claude_max_20x") to our tier. */
|
|
16
|
+
export function mapRateLimitTier(raw) {
|
|
17
|
+
if (!raw)
|
|
18
|
+
return null;
|
|
19
|
+
const s = raw.toLowerCase();
|
|
20
|
+
if (/max[_-]?20x/.test(s))
|
|
21
|
+
return "max20";
|
|
22
|
+
if (/max[_-]?5x/.test(s))
|
|
23
|
+
return "max5";
|
|
24
|
+
if (s.includes("pro"))
|
|
25
|
+
return "pro";
|
|
26
|
+
// Anything else (free, team, enterprise, unknown) → no estimate-tier mapping.
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Best-effort read of the user's plan tier from `~/.claude.json`. Prefers the
|
|
31
|
+
* per-user tier over the org default when present. Returns null if absent.
|
|
32
|
+
*/
|
|
33
|
+
export function detectPlanTier(claudeJsonPath = join(homedir(), ".claude.json")) {
|
|
34
|
+
try {
|
|
35
|
+
const j = JSON.parse(readFileSync(claudeJsonPath, "utf8"));
|
|
36
|
+
const acct = j.oauthAccount;
|
|
37
|
+
if (!acct)
|
|
38
|
+
return null;
|
|
39
|
+
return mapRateLimitTier(acct.userRateLimitTier) ?? mapRateLimitTier(acct.organizationRateLimitTier);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Human label for a tier. */
|
|
46
|
+
export function tierLabel(tier) {
|
|
47
|
+
return tier === "max20" ? "Max 20x" : tier === "max5" ? "Max 5x" : "Pro";
|
|
48
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,8 +16,8 @@ export { loadConfig, DEFAULT_BUDGET, DEFAULT_LIMITS, guardDir, ensureGuardDir, c
|
|
|
16
16
|
export { dispatchAlert, type AlertEvent, type AlertLevel } from "./alert.js";
|
|
17
17
|
export { startProxy, resolveUpstream, type ProxyOptions } from "./proxy.js";
|
|
18
18
|
export { runHook } from "./hook.js";
|
|
19
|
-
export { buildStatusReport, formatLimitsLines, type StatusReport, type LimitsReport } from "./report.js";
|
|
19
|
+
export { buildStatusReport, buildLimitsReport, formatLimitsLines, type StatusReport, type LimitsReport } from "./report.js";
|
|
20
|
+
export { detectPlanTier, mapRateLimitTier, tierLabel, type SubscriptionTier } from "./claude-code.js";
|
|
20
21
|
export { parseUnifiedHeaders, parseUtilization, parseReset, recordHeaders, loadLimitsState, saveLimitsState, emptyLimitsState, limitNotifyKey, unifiedHeaderDump, logUnifiedHeaders, WINDOW_MS, type LimitSnapshot, type WindowState, type LimitsState, type LimitWindow, type HeaderGetter, } from "./limits.js";
|
|
21
22
|
export { assessWindow, assessSnapshot, worstLevel, type PacingAssessment, type PacingLevel, type PacingThresholds, } from "./pacing.js";
|
|
22
|
-
export { estimateSnapshot, isEstimated, TIER_BUDGETS, type PlanTier, type TierBudget, } from "./estimate.js";
|
|
23
23
|
export { installHook, setBudget, setLimits, resetLedger, type InstallOptions, type InstallResult, type BudgetPatch, type LimitsPatch, } from "./ops.js";
|
package/dist/index.js
CHANGED
|
@@ -16,8 +16,8 @@ export { loadConfig, DEFAULT_BUDGET, DEFAULT_LIMITS, guardDir, ensureGuardDir, c
|
|
|
16
16
|
export { dispatchAlert } from "./alert.js";
|
|
17
17
|
export { startProxy, resolveUpstream } from "./proxy.js";
|
|
18
18
|
export { runHook } from "./hook.js";
|
|
19
|
-
export { buildStatusReport, formatLimitsLines } from "./report.js";
|
|
19
|
+
export { buildStatusReport, buildLimitsReport, formatLimitsLines } from "./report.js";
|
|
20
|
+
export { detectPlanTier, mapRateLimitTier, tierLabel } from "./claude-code.js";
|
|
20
21
|
export { parseUnifiedHeaders, parseUtilization, parseReset, recordHeaders, loadLimitsState, saveLimitsState, emptyLimitsState, limitNotifyKey, unifiedHeaderDump, logUnifiedHeaders, WINDOW_MS, } from "./limits.js";
|
|
21
22
|
export { assessWindow, assessSnapshot, worstLevel, } from "./pacing.js";
|
|
22
|
-
export { estimateSnapshot, isEstimated, TIER_BUDGETS, } from "./estimate.js";
|
|
23
23
|
export { installHook, setBudget, setLimits, resetLedger, } from "./ops.js";
|
package/dist/report.d.ts
CHANGED
|
@@ -4,24 +4,39 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Two halves:
|
|
6
6
|
* - the dollar budget (session + daily-rolling), always present; and
|
|
7
|
-
* - the subscription rate-limit standing
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* - the subscription rate-limit standing. Real 5-hour/weekly utilization is
|
|
8
|
+
* only knowable from Anthropic's `unified-*` headers, which the proxy
|
|
9
|
+
* captures — so when we have a fresh snapshot we show real percentages, and
|
|
10
|
+
* otherwise we show what's honestly knowable locally: the auto-detected plan
|
|
11
|
+
* tier plus absolute rolling cost (NOT a fabricated "% of limit" — local cost
|
|
12
|
+
* does not map to Anthropic's internal rate-limit units). Alert-only.
|
|
10
13
|
*/
|
|
11
|
-
import { type
|
|
14
|
+
import { type GuardConfig } from "./config.js";
|
|
15
|
+
import { type SessionRecord, type Ledger } from "./ledger.js";
|
|
12
16
|
import { type Budget, type VerdictLevel } from "./budget.js";
|
|
13
17
|
import { type PacingAssessment, type PacingLevel } from "./pacing.js";
|
|
14
|
-
import type
|
|
15
|
-
import type { Ledger } from "./ledger.js";
|
|
18
|
+
import { type SubscriptionTier } from "./claude-code.js";
|
|
16
19
|
export interface LimitsReport {
|
|
17
|
-
/**
|
|
18
|
-
source: "headers" | "
|
|
20
|
+
/** "headers" = real proxy data; "none" = no live data (show tier + cost only). */
|
|
21
|
+
source: "headers" | "none";
|
|
22
|
+
/** Configured plan ("auto" | tier). */
|
|
19
23
|
plan: string;
|
|
24
|
+
/** Resolved subscription tier (from config or auto-detected from ~/.claude.json). */
|
|
25
|
+
tier: SubscriptionTier | null;
|
|
26
|
+
/** True when the tier came from ~/.claude.json rather than an explicit --plan. */
|
|
27
|
+
tierDetected: boolean;
|
|
20
28
|
subscriptionDetected: boolean;
|
|
21
|
-
/** Epoch ms the snapshot was observed
|
|
29
|
+
/** Epoch ms the headers snapshot was observed, or null. */
|
|
22
30
|
observedAt: number | null;
|
|
31
|
+
/** Real per-window pacing (only when source === "headers"). */
|
|
23
32
|
windows: PacingAssessment[];
|
|
24
33
|
level: PacingLevel;
|
|
34
|
+
/** Absolute rolling cost (API-equivalent USD) — honest local signal, not a limit %. */
|
|
35
|
+
cost: {
|
|
36
|
+
fiveHourUSD: number;
|
|
37
|
+
dailyUSD: number;
|
|
38
|
+
weeklyUSD: number;
|
|
39
|
+
};
|
|
25
40
|
}
|
|
26
41
|
export interface StatusReport {
|
|
27
42
|
budget: Budget;
|
|
@@ -29,12 +44,10 @@ export interface StatusReport {
|
|
|
29
44
|
verdict: VerdictLevel;
|
|
30
45
|
reasons: string[];
|
|
31
46
|
paused: boolean;
|
|
32
|
-
/** Epoch ms the pause auto-expires, or null (indefinite / not paused). */
|
|
33
47
|
pauseUntil: number | null;
|
|
34
48
|
sessions: Array<{
|
|
35
49
|
id: string;
|
|
36
50
|
} & SessionRecord>;
|
|
37
|
-
/** Subscription rate-limit pacing — present whenever we have data to show. */
|
|
38
51
|
limits: LimitsReport;
|
|
39
52
|
}
|
|
40
53
|
/**
|
|
@@ -45,8 +58,7 @@ export interface StatusReport {
|
|
|
45
58
|
export declare function buildLimitsReport(cfg: GuardConfig, ledger: Ledger, now: number): LimitsReport;
|
|
46
59
|
/**
|
|
47
60
|
* Render the subscription rate-limit section as plain text lines (no color), so
|
|
48
|
-
* both the `agent-guard` and `ks guard` status views stay identical.
|
|
49
|
-
* empty array when there's nothing useful to show.
|
|
61
|
+
* both the `agent-guard` and `ks guard` status views stay identical.
|
|
50
62
|
*/
|
|
51
63
|
export declare function formatLimitsLines(limits: LimitsReport, now?: number): string[];
|
|
52
64
|
/** Build the current status report from the on-disk config + ledger. */
|
package/dist/report.js
CHANGED
|
@@ -4,18 +4,30 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Two halves:
|
|
6
6
|
* - the dollar budget (session + daily-rolling), always present; and
|
|
7
|
-
* - the subscription rate-limit standing
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* - the subscription rate-limit standing. Real 5-hour/weekly utilization is
|
|
8
|
+
* only knowable from Anthropic's `unified-*` headers, which the proxy
|
|
9
|
+
* captures — so when we have a fresh snapshot we show real percentages, and
|
|
10
|
+
* otherwise we show what's honestly knowable locally: the auto-detected plan
|
|
11
|
+
* tier plus absolute rolling cost (NOT a fabricated "% of limit" — local cost
|
|
12
|
+
* does not map to Anthropic's internal rate-limit units). Alert-only.
|
|
10
13
|
*/
|
|
11
|
-
import { loadConfig } from "./config.js";
|
|
12
|
-
import { isPaused, pauseExpiry } from "./config.js";
|
|
14
|
+
import { loadConfig, isPaused, pauseExpiry } from "./config.js";
|
|
13
15
|
import { loadLedger, rollingDailyCost } from "./ledger.js";
|
|
14
16
|
import { evaluate } from "./budget.js";
|
|
17
|
+
import { fmtUSD } from "./cost.js";
|
|
15
18
|
import { loadLimitsState, WINDOW_MS } from "./limits.js";
|
|
16
19
|
import { assessSnapshot, worstLevel } from "./pacing.js";
|
|
17
|
-
import {
|
|
20
|
+
import { detectPlanTier, tierLabel } from "./claude-code.js";
|
|
18
21
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
22
|
+
/** Sum metered cost across sessions whose last activity is within `windowMs`. */
|
|
23
|
+
function costInWindow(ledger, now, windowMs) {
|
|
24
|
+
let total = 0;
|
|
25
|
+
for (const s of Object.values(ledger.sessions)) {
|
|
26
|
+
if (now - s.lastAt < windowMs)
|
|
27
|
+
total += s.costUSD || 0;
|
|
28
|
+
}
|
|
29
|
+
return total;
|
|
30
|
+
}
|
|
19
31
|
/**
|
|
20
32
|
* Compute the subscription rate-limit section. Exported so the Claude Code hook
|
|
21
33
|
* can reuse its already-loaded cfg + ledger instead of paying for a second
|
|
@@ -23,48 +35,52 @@ const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
23
35
|
*/
|
|
24
36
|
export function buildLimitsReport(cfg, ledger, now) {
|
|
25
37
|
const state = loadLimitsState();
|
|
26
|
-
const thresholds = cfg.limits;
|
|
27
38
|
const plan = cfg.limits.plan;
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
// Resolve the effective tier: an explicit --plan wins; otherwise auto-detect
|
|
40
|
+
// from ~/.claude.json (oauthAccount.organizationRateLimitTier).
|
|
41
|
+
let tier = plan === "pro" || plan === "max5" || plan === "max20" ? plan : null;
|
|
42
|
+
let tierDetected = false;
|
|
43
|
+
if (!tier) {
|
|
44
|
+
const detected = detectPlanTier();
|
|
45
|
+
if (detected) {
|
|
46
|
+
tier = detected;
|
|
47
|
+
tierDetected = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const cost = {
|
|
51
|
+
fiveHourUSD: costInWindow(ledger, now, WINDOW_MS["5h"]),
|
|
52
|
+
dailyUSD: costInWindow(ledger, now, DAY_MS),
|
|
53
|
+
weeklyUSD: costInWindow(ledger, now, WINDOW_MS.weekly),
|
|
54
|
+
};
|
|
55
|
+
// Real data: a fresh snapshot, with any already-reset window dropped (stale).
|
|
34
56
|
const snap = state.snapshot;
|
|
35
57
|
if (snap && now - snap.observedAt < WINDOW_MS.weekly) {
|
|
36
|
-
const windows = assessSnapshot(snap,
|
|
58
|
+
const windows = assessSnapshot(snap, cfg.limits, now).filter((w) => !(w.resetAt != null && w.resetAt <= now));
|
|
37
59
|
if (windows.length) {
|
|
38
60
|
return {
|
|
39
61
|
source: "headers",
|
|
40
62
|
plan,
|
|
63
|
+
tier,
|
|
64
|
+
tierDetected,
|
|
41
65
|
subscriptionDetected: state.subscriptionDetected,
|
|
42
66
|
observedAt: snap.observedAt,
|
|
43
67
|
windows,
|
|
44
68
|
level: worstLevel(windows),
|
|
69
|
+
cost,
|
|
45
70
|
};
|
|
46
71
|
}
|
|
47
72
|
}
|
|
48
|
-
//
|
|
49
|
-
if (plan === "pro" || plan === "max5" || plan === "max20") {
|
|
50
|
-
const snap = estimateSnapshot(ledger, plan, now);
|
|
51
|
-
const windows = assessSnapshot(snap, thresholds, now);
|
|
52
|
-
return {
|
|
53
|
-
source: "estimated",
|
|
54
|
-
plan,
|
|
55
|
-
subscriptionDetected: state.subscriptionDetected,
|
|
56
|
-
observedAt: snap.observedAt,
|
|
57
|
-
windows,
|
|
58
|
-
level: worstLevel(windows),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
73
|
+
// No live data — honest local signal only (tier + absolute cost, never a fake %).
|
|
61
74
|
return {
|
|
62
75
|
source: "none",
|
|
63
76
|
plan,
|
|
77
|
+
tier,
|
|
78
|
+
tierDetected,
|
|
64
79
|
subscriptionDetected: state.subscriptionDetected,
|
|
65
80
|
observedAt: null,
|
|
66
81
|
windows: [],
|
|
67
82
|
level: "ok",
|
|
83
|
+
cost,
|
|
68
84
|
};
|
|
69
85
|
}
|
|
70
86
|
function bar(frac) {
|
|
@@ -84,30 +100,28 @@ function ageString(observedAt, now) {
|
|
|
84
100
|
}
|
|
85
101
|
/**
|
|
86
102
|
* Render the subscription rate-limit section as plain text lines (no color), so
|
|
87
|
-
* both the `agent-guard` and `ks guard` status views stay identical.
|
|
88
|
-
* empty array when there's nothing useful to show.
|
|
103
|
+
* both the `agent-guard` and `ks guard` status views stay identical.
|
|
89
104
|
*/
|
|
90
105
|
export function formatLimitsLines(limits, now = Date.now()) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
];
|
|
99
|
-
}
|
|
100
|
-
return [];
|
|
101
|
-
}
|
|
102
|
-
const icon = limits.level === "danger" ? "🟥" : limits.level === "warn" ? "🟡" : "🟢";
|
|
103
|
-
const tag = limits.source === "estimated" ? " (estimated — run the proxy for exact)" : "";
|
|
104
|
-
const lines = [`${icon} Claude Code plan limits${tag} · observed ${limits.observedAt ? ageString(limits.observedAt, now) : "—"}`];
|
|
105
|
-
for (const w of limits.windows) {
|
|
106
|
-
// w.message already leads with "<window> limit NN% used, …", so the bar
|
|
107
|
-
// carries the visual and the message carries the numbers + pacing.
|
|
108
|
-
lines.push(` ${bar(w.utilization)} ${w.message}`);
|
|
106
|
+
// Real proxy data → real percentages.
|
|
107
|
+
if (limits.source === "headers") {
|
|
108
|
+
const icon = limits.level === "danger" ? "🟥" : limits.level === "warn" ? "🟡" : "🟢";
|
|
109
|
+
const lines = [`${icon} Claude Code plan limits · observed ${limits.observedAt ? ageString(limits.observedAt, now) : "—"}`];
|
|
110
|
+
for (const w of limits.windows)
|
|
111
|
+
lines.push(` ${bar(w.utilization)} ${w.message}`);
|
|
112
|
+
return lines;
|
|
109
113
|
}
|
|
110
|
-
|
|
114
|
+
// No live data: show what's honestly knowable — the tier and absolute cost,
|
|
115
|
+
// and steer to the proxy for the real percentages. Never a fabricated % bar.
|
|
116
|
+
const planStr = limits.tier
|
|
117
|
+
? `Claude Code plan: ${tierLabel(limits.tier)}${limits.tierDetected ? " (detected)" : ""}`
|
|
118
|
+
: "Claude Code plan: unknown";
|
|
119
|
+
const c = limits.cost;
|
|
120
|
+
return [
|
|
121
|
+
`📊 ${planStr} — real 5-hour/weekly limit % needs the proxy`,
|
|
122
|
+
` local rolling cost (API-equivalent, NOT a limit %): 5h ${fmtUSD(c.fiveHourUSD)} · 24h ${fmtUSD(c.dailyUSD)} · 7d ${fmtUSD(c.weeklyUSD)}`,
|
|
123
|
+
` for exact limits: \`ks guard proxy\` → ANTHROPIC_BASE_URL=http://localhost:8787 claude`,
|
|
124
|
+
];
|
|
111
125
|
}
|
|
112
126
|
/** Build the current status report from the on-disk config + ledger. */
|
|
113
127
|
export function buildStatusReport(now = Date.now()) {
|
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.6",
|
|
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": {
|