@askalf/dario 4.8.75 → 4.8.76

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 CHANGED
@@ -200,9 +200,9 @@ The tool doesn't know. The backend doesn't know. dario is the seam.
200
200
 
201
201
  ## Overage guard
202
202
 
203
- A subscriber should never see a single `representative-claim: overage` response during normal operation. One means something is wrong — wire-shape drift, a classifier change, an account misconfig — and continuing to forward requests in the same shape bleeds real money (accounts with extra-usage enabled) or returns a wall of rejections (accounts without it). The first hit is the signal; the second through hundredth are damage.
203
+ A subscriber should never see a single response billed outside their subscription pool during normal operation. One means something is wrong — wire-shape drift, a classifier change, an account misconfig — and continuing to forward requests in the same shape bleeds real money (accounts with extra-usage enabled) or returns a wall of rejections (accounts without it). The first hit is the signal; the second through hundredth are damage.
204
204
 
205
- So the moment any upstream response carries `representative-claim: overage`, dario **halts the proxy**. Every subsequent request returns `503` with an Anthropic-shaped error body the client surfaces verbatim, until you run `dario resume`, press `R` on the TUI, or the cooldown clears (default 30 min). The halt is visible across the TUI's Status, Hits, and Analytics tabs, fires a best-effort native OS notification, and emits named SSE events.
205
+ So the moment any upstream response bills to something other than your subscription pool — `representative-claim: overage`, `api`, or a new credit/SDK bucket like the one the 2026-06-15 Agent-SDK split introduces — dario **halts the proxy**. The check is an allow-list, not a match on `overage`: anything that isn't a known subscription claim (`five_hour`/`seven_day` and their fallbacks) and isn't the `unknown` no-header sentinel trips it, so a credit-bucket claim dario has never seen still halts. Every subsequent request returns `503` with an Anthropic-shaped error body the client surfaces verbatim, until you run `dario resume`, press `R` on the TUI, or the cooldown clears (default 30 min). The halt is visible across the TUI's Status, Hits, and Analytics tabs, fires a best-effort native OS notification, and emits named SSE events. (In `--upstream-api-key` passthrough mode the guard is off — `api` billing is the point there, not a failure.)
206
206
 
207
207
  ```
208
208
  ┌─ dario ─────────────────────────────[ q quit · Tab next · ? help ]──┐
@@ -368,26 +368,27 @@ Follow [@ask_alf](https://x.com/ask_alf) for drift bulletins as they happen.
368
368
 
369
369
  MIT — see [LICENSE](LICENSE) and [DISCLAIMER.md](DISCLAIMER.md).
370
370
 
371
- ## Also by askalf
371
+ ## Own Your Stack
372
372
 
373
- | Project | What it does |
374
- |---|---|
375
- | [askalf platform](https://askalf.org) | Self-hosted AI workforce — agents that run real business + life work. Uses dario as its LLM substrate. *Early access at [askalf.org](https://askalf.org).* |
376
- | [hands](https://github.com/askalf/hands) | Cross-platform computer-use agent — your LLM on your mouse, keyboard, and screen. Routes through dario or any Anthropic-compat. |
377
- | [deepdive](https://github.com/askalf/deepdive) | Local research agent. One command, cited answer. Plan → search → headless fetch → extract → synthesize. |
378
- | [agent](https://github.com/askalf/agent) | Connect any device to an askalf fleet — runs the shell or Claude Code tasks the fleet dispatches. |
379
- | [browser-bridge](https://github.com/askalf/browser-bridge) | Stealth headless Chromium in a container, CDP on 9222. Playwright / Puppeteer / MCP-compatible. |
380
- | [claude-sync](https://github.com/askalf/claude-sync) | Sync Claude Code sessions across machines via a portable `.ccsync` file. |
381
- | [pgflex](https://github.com/askalf/pgflex) | One Postgres API, two modes — real PostgreSQL for production, PGlite (WASM) for dev. |
382
- | [redisflex](https://github.com/askalf/redisflex) | One Redis API, two modes — ioredis for production, in-process for dev. Includes a BullMQ-shaped in-memory queue. |
373
+ dario is the routing layer of **[Own Your Stack](https://github.com/askalf)** — open tools for owning your AI infrastructure instead of renting it by the token. One subscription. Your box. Your terms.
383
374
 
375
+ | Tool | |
376
+ |---|---|
377
+ | **[dario](https://github.com/askalf/dario)** | own your routing — your subscription, in any tool *(you are here)* |
378
+ | **[deepdive](https://github.com/askalf/deepdive)** | own your research — local agent, cited answers |
379
+ | **[hands](https://github.com/askalf/hands)** | own your computer-use — your LLM on your own mouse & keyboard |
380
+ | **[agent](https://github.com/askalf/agent)** | own your fleet — connect any device, dispatch real work |
381
+ | **[browser-bridge](https://github.com/askalf/browser-bridge)** | own your browser — stealth headless Chromium, your CDP endpoint |
382
+ | **[claude-sync](https://github.com/askalf/claude-sync)** | own your sessions — sync Claude Code across machines |
383
+ | **[askalf platform](https://askalf.org)** | own your operation — the self-hosted AI workforce, running on this stack |
384
384
 
385
385
  ---
386
386
 
387
- ## Built by Sprayberry Labs
387
+ ## Built by Thomas Sprayberry
388
388
 
389
- This is one of the open-source building blocks from **[Sprayberry Labs](https://sprayberrylabs.com)** an independent studio (Atlanta, GA) that ships bespoke software and **fixed-price code & security audits**, delivered with the AI workforce these tools are part of.
389
+ dario is part of **Own Your Stack** — the open toolkit behind **[Sprayberry Labs](https://sprayberrylabs.com)**, an independent studio (Atlanta, GA) that ships bespoke software with the autonomous AI workforce these tools are part of.
390
390
 
391
- Part of the [askalf](https://askalf.org) ecosystem — a self-hosted AI workforce platform, now in early access.
391
+ Built in the open, scars included. Follow the build → **[@ask_alf](https://x.com/ask_alf)** · **[sprayberrylabs.com/own-your-stack](https://sprayberrylabs.com/own-your-stack)**
392
392
 
393
- **Got a codebase that needs an expert read?** → **[Scan a repo — free mini-audit](https://sprayberrylabs.com)**, or see the **$1,500 fixed-price Audit** and build Sprints. · [sprayberrylabs.com](https://sprayberrylabs.com) · hello@sprayberrylabs.com
393
+ ---
394
+ Part of **[Own Your Stack](https://github.com/askalf)** — own your AI infrastructure instead of renting it. Built by Thomas Sprayberry.
@@ -55,6 +55,34 @@ export type BillingBucket = 'subscription' | 'subscription_fallback' | 'extra_us
55
55
  * billing bucket. Pure function; no state; safe to call from any context.
56
56
  */
57
57
  export declare function billingBucketFromClaim(claim: string | null | undefined): BillingBucket;
58
+ /**
59
+ * The `representative-claim` values that mean "billed against the subscription
60
+ * pool" — the place dario exists to keep traffic. `five_hour`/`seven_day` and
61
+ * their server-side `_fallback` variants are all subscription billing (see
62
+ * `billingBucketFromClaim` + discussion #1). Anything else is either a
63
+ * non-subscription billing classification or the `unknown` sentinel below.
64
+ */
65
+ export declare const SUBSCRIPTION_CLAIMS: ReadonlySet<string>;
66
+ /**
67
+ * The sentinel `claim` dario assigns when a response carried no rate-limit
68
+ * header at all (non-200s, stream aborts, early rejects — see `pool.ts`
69
+ * `parseRateLimits` / `EMPTY_SNAPSHOT`). It is NOT a billing classification,
70
+ * so the overage-guard must never halt on it.
71
+ */
72
+ export declare const NO_BILLING_CLAIM = "unknown";
73
+ /**
74
+ * True when a claim represents real *non-subscription* billing — the
75
+ * condition the overage-guard halts on (see `overage-guard.ts`, #288).
76
+ *
77
+ * Deliberately an allow-list, not `claim === 'overage'`: it halts on anything
78
+ * that is NOT a known subscription claim AND NOT the `unknown` sentinel. That
79
+ * catches `overage` and `api` as before, but ALSO any new credit/SDK bucket
80
+ * string Anthropic introduces — e.g. the 2026-06-15 Agent-SDK/headless split,
81
+ * whose credit-bucket claim dario has never observed (it keeps traffic in the
82
+ * pool) and so cannot hardcode. `unknown` is exempt: halting on it would halt
83
+ * the proxy on every transient non-200/stream-abort.
84
+ */
85
+ export declare function isNonSubscriptionBilling(claim: string | null | undefined): boolean;
58
86
  export declare class Analytics extends EventEmitter {
59
87
  private records;
60
88
  private maxRecords;
package/dist/analytics.js CHANGED
@@ -37,6 +37,43 @@ export function billingBucketFromClaim(claim) {
37
37
  return 'unknown';
38
38
  }
39
39
  }
40
+ /**
41
+ * The `representative-claim` values that mean "billed against the subscription
42
+ * pool" — the place dario exists to keep traffic. `five_hour`/`seven_day` and
43
+ * their server-side `_fallback` variants are all subscription billing (see
44
+ * `billingBucketFromClaim` + discussion #1). Anything else is either a
45
+ * non-subscription billing classification or the `unknown` sentinel below.
46
+ */
47
+ export const SUBSCRIPTION_CLAIMS = new Set([
48
+ 'five_hour',
49
+ 'seven_day',
50
+ 'five_hour_fallback',
51
+ 'seven_day_fallback',
52
+ ]);
53
+ /**
54
+ * The sentinel `claim` dario assigns when a response carried no rate-limit
55
+ * header at all (non-200s, stream aborts, early rejects — see `pool.ts`
56
+ * `parseRateLimits` / `EMPTY_SNAPSHOT`). It is NOT a billing classification,
57
+ * so the overage-guard must never halt on it.
58
+ */
59
+ export const NO_BILLING_CLAIM = 'unknown';
60
+ /**
61
+ * True when a claim represents real *non-subscription* billing — the
62
+ * condition the overage-guard halts on (see `overage-guard.ts`, #288).
63
+ *
64
+ * Deliberately an allow-list, not `claim === 'overage'`: it halts on anything
65
+ * that is NOT a known subscription claim AND NOT the `unknown` sentinel. That
66
+ * catches `overage` and `api` as before, but ALSO any new credit/SDK bucket
67
+ * string Anthropic introduces — e.g. the 2026-06-15 Agent-SDK/headless split,
68
+ * whose credit-bucket claim dario has never observed (it keeps traffic in the
69
+ * pool) and so cannot hardcode. `unknown` is exempt: halting on it would halt
70
+ * the proxy on every transient non-200/stream-abort.
71
+ */
72
+ export function isNonSubscriptionBilling(claim) {
73
+ if (!claim || claim === NO_BILLING_CLAIM)
74
+ return false;
75
+ return !SUBSCRIPTION_CLAIMS.has(claim);
76
+ }
40
77
  // Anthropic pricing (per 1M tokens, USD). Not authoritative — used for
41
78
  // rough burn-rate display in the /analytics summary.
42
79
  const PRICING = {
@@ -1,18 +1,25 @@
1
1
  /**
2
- * Overage-guard — halt the proxy on the first `representative-claim: overage`
3
- * response to prevent silent API-rate bleed.
2
+ * Overage-guard — halt the proxy the first time a response bills to anything
3
+ * other than the subscription pool, to prevent silent API-rate bleed.
4
4
  *
5
- * Subscribers should never see a single overage hit during normal
5
+ * Subscribers should never see a single non-subscription hit during normal
6
6
  * operation. One means something is wrong (wire-shape drift, classifier
7
7
  * change, account misconfig, billing-flip after a CC release) and
8
8
  * continuing to forward requests bleeds against per-token billing.
9
9
  *
10
10
  * The guard subscribes to the Analytics record stream — every completed
11
11
  * request emits a record carrying its `claim` (raw representative-claim
12
- * value). When `claim === 'overage'` lands, the guard transitions to a
13
- * halted state and emits a `'halt'` event. The HTTP request path checks
14
- * `isHalted()` on every incoming request and returns 503 with an
15
- * Anthropic-shaped error body when halted.
12
+ * value). When a non-subscription claim lands `overage`, `api`, or any
13
+ * new credit/SDK-bucket string (the 2026-06-15 Agent-SDK split) the guard
14
+ * transitions to a halted state and emits a `'halt'` event. The detection is
15
+ * an allow-list (`isNonSubscriptionBilling`): it fires on anything that is
16
+ * not a known subscription claim and not the `unknown` sentinel (which means
17
+ * "no rate-limit header" — a transient non-200/abort, not a billing flip).
18
+ * The HTTP request path checks `isHalted()` on every incoming request and
19
+ * returns 503 with an Anthropic-shaped error body when halted.
20
+ *
21
+ * The name predates the broadening (it caught only `overage` through v4.8.x,
22
+ * #288); the issue lineage + `dario_overage_guard` error type are kept stable.
16
23
  *
17
24
  * Resume paths:
18
25
  * - explicit: `dario resume` CLI → POST /admin/resume → `clear('manual')`
@@ -26,7 +33,7 @@
26
33
  * See dario#288.
27
34
  */
28
35
  import { EventEmitter } from 'node:events';
29
- import type { Analytics, RequestRecord } from './analytics.js';
36
+ import { type Analytics, type RequestRecord } from './analytics.js';
30
37
  export interface HaltState {
31
38
  since: number;
32
39
  cooldownUntil: number;
@@ -58,9 +65,11 @@ export declare class OverageGuard extends EventEmitter {
58
65
  private analyticsListener;
59
66
  constructor(opts: OverageGuardOptions);
60
67
  /**
61
- * Subscribe to an Analytics instance. Every record emitted with
62
- * `claim === 'overage'` triggers halt (when behavior === 'halt') or a
63
- * warn-only event (when behavior === 'warn').
68
+ * Subscribe to an Analytics instance. Every record whose claim is a
69
+ * non-subscription billing classification (`isNonSubscriptionBilling`
70
+ * `overage`, `api`, or a new credit/SDK bucket) triggers halt (when
71
+ * behavior === 'halt') or a warn-only event (when behavior === 'warn').
72
+ * Subscription claims and the `unknown` sentinel are ignored.
64
73
  *
65
74
  * Idempotent — calling attach() a second time replaces the listener
66
75
  * rather than stacking; useful for tests.
@@ -1,18 +1,25 @@
1
1
  /**
2
- * Overage-guard — halt the proxy on the first `representative-claim: overage`
3
- * response to prevent silent API-rate bleed.
2
+ * Overage-guard — halt the proxy the first time a response bills to anything
3
+ * other than the subscription pool, to prevent silent API-rate bleed.
4
4
  *
5
- * Subscribers should never see a single overage hit during normal
5
+ * Subscribers should never see a single non-subscription hit during normal
6
6
  * operation. One means something is wrong (wire-shape drift, classifier
7
7
  * change, account misconfig, billing-flip after a CC release) and
8
8
  * continuing to forward requests bleeds against per-token billing.
9
9
  *
10
10
  * The guard subscribes to the Analytics record stream — every completed
11
11
  * request emits a record carrying its `claim` (raw representative-claim
12
- * value). When `claim === 'overage'` lands, the guard transitions to a
13
- * halted state and emits a `'halt'` event. The HTTP request path checks
14
- * `isHalted()` on every incoming request and returns 503 with an
15
- * Anthropic-shaped error body when halted.
12
+ * value). When a non-subscription claim lands `overage`, `api`, or any
13
+ * new credit/SDK-bucket string (the 2026-06-15 Agent-SDK split) the guard
14
+ * transitions to a halted state and emits a `'halt'` event. The detection is
15
+ * an allow-list (`isNonSubscriptionBilling`): it fires on anything that is
16
+ * not a known subscription claim and not the `unknown` sentinel (which means
17
+ * "no rate-limit header" — a transient non-200/abort, not a billing flip).
18
+ * The HTTP request path checks `isHalted()` on every incoming request and
19
+ * returns 503 with an Anthropic-shaped error body when halted.
20
+ *
21
+ * The name predates the broadening (it caught only `overage` through v4.8.x,
22
+ * #288); the issue lineage + `dario_overage_guard` error type are kept stable.
16
23
  *
17
24
  * Resume paths:
18
25
  * - explicit: `dario resume` CLI → POST /admin/resume → `clear('manual')`
@@ -26,6 +33,7 @@
26
33
  * See dario#288.
27
34
  */
28
35
  import { EventEmitter } from 'node:events';
36
+ import { isNonSubscriptionBilling } from './analytics.js';
29
37
  export class OverageGuard extends EventEmitter {
30
38
  opts;
31
39
  halted = null;
@@ -39,9 +47,11 @@ export class OverageGuard extends EventEmitter {
39
47
  this.opts = opts;
40
48
  }
41
49
  /**
42
- * Subscribe to an Analytics instance. Every record emitted with
43
- * `claim === 'overage'` triggers halt (when behavior === 'halt') or a
44
- * warn-only event (when behavior === 'warn').
50
+ * Subscribe to an Analytics instance. Every record whose claim is a
51
+ * non-subscription billing classification (`isNonSubscriptionBilling`
52
+ * `overage`, `api`, or a new credit/SDK bucket) triggers halt (when
53
+ * behavior === 'halt') or a warn-only event (when behavior === 'warn').
54
+ * Subscription claims and the `unknown` sentinel are ignored.
45
55
  *
46
56
  * Idempotent — calling attach() a second time replaces the listener
47
57
  * rather than stacking; useful for tests.
@@ -57,7 +67,7 @@ export class OverageGuard extends EventEmitter {
57
67
  return;
58
68
  }
59
69
  this.analyticsListener = (r) => {
60
- if (r.claim === 'overage') {
70
+ if (isNonSubscriptionBilling(r.claim)) {
61
71
  this.onOverageDetected(r);
62
72
  }
63
73
  };
@@ -113,7 +123,7 @@ export class OverageGuard extends EventEmitter {
113
123
  if (this.opts.notifyOs && this.opts.notifier) {
114
124
  try {
115
125
  const title = this.opts.behavior === 'halt' ? 'dario halted' : 'dario warning';
116
- const msg = `Request classified as 'overage' (per-token billing)${this.opts.behavior === 'halt' ? '. Proxy halted. Run `dario resume` to continue.' : ''}`;
126
+ const msg = `Request classified as '${r.claim}' (non-subscription billing)${this.opts.behavior === 'halt' ? '. Proxy halted. Run `dario resume` to continue.' : ''}`;
117
127
  this.opts.notifier(title, msg);
118
128
  }
119
129
  catch {
@@ -180,10 +190,10 @@ export function buildHaltErrorBody(state) {
180
190
  error: {
181
191
  type: 'dario_overage_guard',
182
192
  message: `dario halted to prevent API-rate bleed. A request was classified ` +
183
- `as 'overage' (per-token billing) instead of your subscription pool. ` +
184
- `To resume: run \`dario resume\` in another terminal, or wait until ` +
185
- `${isoCooldown} for the cooldown to auto-clear. ` +
186
- `Details: github.com/askalf/dario/issues/288`,
193
+ `as '${state.request.claim}' (non-subscription billing) instead of ` +
194
+ `your subscription pool. To resume: run \`dario resume\` in another ` +
195
+ `terminal, or wait until ${isoCooldown} for the cooldown to ` +
196
+ `auto-clear. Details: github.com/askalf/dario/issues/288`,
187
197
  },
188
198
  };
189
199
  }
package/dist/proxy.d.ts CHANGED
@@ -321,11 +321,13 @@ interface ProxyOptions {
321
321
  */
322
322
  upstreamApiKey?: string;
323
323
  /**
324
- * Overage-guard — halt the proxy on the first response carrying
325
- * `representative-claim: overage`. Subscribers should never see a
326
- * single overage hit during normal operation; one means something
327
- * is wrong (wire-shape drift, classifier change, account misconfig)
328
- * and continuing to forward bleeds against per-token billing.
324
+ * Overage-guard — halt the proxy on the first response that bills to
325
+ * anything other than the subscription pool (`overage`, `api`, or a new
326
+ * credit/SDK bucket the 2026-06-15 split). Subscribers should never see
327
+ * a single non-subscription hit during normal operation; one means
328
+ * something is wrong (wire-shape drift, classifier change, account
329
+ * misconfig) and continuing to forward bleeds against per-token billing.
330
+ * Auto-disabled in upstream-API-key passthrough mode (see proxy setup).
329
331
  *
330
332
  * Default: enabled, halt behavior, 30-min cooldown, OS-notify on.
331
333
  * See dario#288.
package/dist/proxy.js CHANGED
@@ -44,7 +44,19 @@ function computeBuildTag(userMessage, version) {
44
44
  const chars = [4, 7, 20].map(i => userMessage[i] || '0').join('');
45
45
  return createHash('sha256').update(`${BILLING_SEED}${chars}${version}`).digest('hex').slice(0, 3);
46
46
  }
47
- // Per-request cch: random 5-char hex value each request (Claude Code does the same).
47
+ // Per-request cch. NOTE: real Claude Code does NOT randomise this it's a
48
+ // deterministic xxHash64 integrity hash over the serialized request body,
49
+ // written in place over a `cch=00000` placeholder inside the billing-tag
50
+ // system block (reverse-engineered on cc_version=2.1.37; see dario#528).
51
+ //
52
+ // We emit a random 5-hex value on purpose. The published seed
53
+ // (0x6E52736AC806831E) + scope do NOT reproduce the cch on current CC
54
+ // (falsified against a live 2.1.177 capture), and the seed/scope appear to
55
+ // rotate between releases. A deterministic-but-wrong value is no better than
56
+ // random against a server that recomputes — and a cleaner tell — whereas a
57
+ // random value keeps the right shape (5 lowercase hex) and varies per request
58
+ // like the real one. Revisit only if Anthropic starts rejecting on cch, which
59
+ // would require re-RE'ing the current binary for the live seed + pre-image.
48
60
  function computeCch() {
49
61
  return randomBytes(3).toString('hex').slice(0, 5);
50
62
  }
@@ -807,8 +819,15 @@ export async function startProxy(opts = {}) {
807
819
  // defaults (enabled=true, behavior='halt', cooldown=30min, notifyOs=true)
808
820
  // so an opts-less proxy still gets protection. The notifier is wired
809
821
  // separately below once notify.ts is loaded.
822
+ //
823
+ // Disabled in upstream-API-key passthrough mode: there the operator
824
+ // explicitly opted into the per-token API pool, so responses legitimately
825
+ // bill as `api`. The guard now halts on ANY non-subscription claim (incl.
826
+ // `api`), so leaving it on would false-halt the whole passthrough path
827
+ // (e.g. the self-hosted compat workflow). The subscription-protection
828
+ // premise doesn't apply when there's no subscription to protect.
810
829
  const overageGuard = new OverageGuard({
811
- enabled: opts.overageGuardEnabled ?? true,
830
+ enabled: (opts.overageGuardEnabled ?? true) && !upstreamApiKey,
812
831
  behavior: opts.overageGuardBehavior ?? 'halt',
813
832
  cooldownMs: opts.overageGuardCooldownMs ?? 30 * 60 * 1000,
814
833
  notifyOs: opts.overageGuardNotifyOs ?? true,
@@ -820,10 +839,10 @@ export async function startProxy(opts = {}) {
820
839
  // a TUI attached. -v / --verbose is not required — this is loud by
821
840
  // design.
822
841
  overageGuard.on('halt', (state) => {
823
- console.error(`[dario] OVERAGE-GUARD HALTED: ${state.request.model} on account=${state.request.account} returned representative-claim=overage at ${new Date(state.request.timestamp).toISOString()}. Returning 503 to new requests until \`dario resume\` or cooldown expires (${new Date(state.cooldownUntil).toISOString()}). See dario#288.`);
842
+ console.error(`[dario] OVERAGE-GUARD HALTED: ${state.request.model} on account=${state.request.account} returned representative-claim=${state.request.claim} at ${new Date(state.request.timestamp).toISOString()}. Returning 503 to new requests until \`dario resume\` or cooldown expires (${new Date(state.cooldownUntil).toISOString()}). See dario#288.`);
824
843
  });
825
844
  overageGuard.on('warn', (state) => {
826
- console.error(`[dario] OVERAGE-GUARD WARN: ${state.request.model} on account=${state.request.account} returned representative-claim=overage at ${new Date(state.request.timestamp).toISOString()}. Behavior=warn — proxy continuing to forward; investigate before bill bleeds. See dario#288.`);
845
+ console.error(`[dario] OVERAGE-GUARD WARN: ${state.request.model} on account=${state.request.account} returned representative-claim=${state.request.claim} at ${new Date(state.request.timestamp).toISOString()}. Behavior=warn — proxy continuing to forward; investigate before bill bleeds. See dario#288.`);
827
846
  });
828
847
  overageGuard.on('resume', (info) => {
829
848
  console.error(`[dario] overage-guard resumed (${info.reason}). Normal request handling restored.`);
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import { fg, dim, brand, inverse, BOX, pad, truncate } from '../render.js';
27
27
  import { renderKvRow } from '../layout.js';
28
- import { billingBucketFromClaim } from '../../analytics.js';
28
+ import { billingBucketFromClaim, isNonSubscriptionBilling } from '../../analytics.js';
29
29
  const MAX_BUFFER = 5000;
30
30
  export const HitsTab = {
31
31
  id: 'hits',
@@ -143,7 +143,7 @@ export const HitsTab = {
143
143
  if (state.halt) {
144
144
  const since = formatTimestamp(state.halt.since);
145
145
  const cooldown = formatRemaining(state.halt.cooldownUntil - Date.now());
146
- const line1 = ` ${fg('red', '⚠ HALTED')} overage detected at ${since} on ${state.halt.request.model} (account=${state.halt.request.account})`;
146
+ const line1 = ` ${fg('red', '⚠ HALTED')} ${state.halt.request.claim} detected at ${since} on ${state.halt.request.model} (account=${state.halt.request.account})`;
147
147
  const line2 = ` ${dim('→ New /v1/messages requests return 503 until')} ${fg('cyan', 'R')} ${dim('here, or')} ${fg('cyan', 'dario resume')}${dim(' from any shell. Auto-resume in')} ${cooldown}${dim('.')}`;
148
148
  lines.push(line1);
149
149
  lines.push(line2);
@@ -158,9 +158,12 @@ export const HitsTab = {
158
158
  pad('st', colStatus)));
159
159
  for (let i = startIdx; i < endIdx; i++) {
160
160
  const r = newestFirst[i];
161
- const isOverage = r.claim === 'overage';
161
+ // Flag any non-subscription billing red — the same condition the
162
+ // overage-guard halts on (overage, api, or a credit/SDK bucket), not
163
+ // just literal `overage`. See isNonSubscriptionBilling (#288).
164
+ const isNonSub = isNonSubscriptionBilling(r.claim);
162
165
  const marker = i === state.selectedIdx ? fg('cyan', '▎')
163
- : isOverage ? fg('red', '!')
166
+ : isNonSub ? fg('red', '!')
164
167
  : ' ';
165
168
  const row = marker + ' ' +
166
169
  pad(formatTime(r.timestamp), colTime) +
@@ -169,12 +172,12 @@ export const HitsTab = {
169
172
  pad(formatTokens(r.outputTokens), colOut) +
170
173
  pad(formatLatency(r.latencyMs), colLat) +
171
174
  pad(formatStatus(r.status), colStatus);
172
- // Overage rows render in red even when unselected; selection still
173
- // wins via the inverse() wrapper so the user can drill into one.
175
+ // Non-subscription rows render in red even when unselected; selection
176
+ // still wins via the inverse() wrapper so the user can drill into one.
174
177
  let styled;
175
178
  if (i === state.selectedIdx)
176
179
  styled = inverse(truncate(row, w - 2));
177
- else if (isOverage)
180
+ else if (isNonSub)
178
181
  styled = fg('red', truncate(row, w - 2));
179
182
  else
180
183
  styled = truncate(row, w - 2);
@@ -115,7 +115,7 @@ export const StatusTab = {
115
115
  const remainingMs = Math.max(0, s.cooldownUntil - Date.now());
116
116
  const remaining = formatDuration(remainingMs);
117
117
  // Red banner header — this is the loud surface when halted
118
- lines.push(' ' + fg('red', '⚠ HALTED') + ' ' + dim(`overage detected ${formatAgo(s.since)} ago`));
118
+ lines.push(' ' + fg('red', '⚠ HALTED') + ' ' + dim(`${s.request.claim} detected ${formatAgo(s.since)} ago`));
119
119
  lines.push(' ' + renderKvRow('Request', `${s.request.model} ${dim('account=' + s.request.account)}`, w - 4));
120
120
  lines.push(' ' + renderKvRow('Cause', `representative-claim = ${fg('red', s.request.claim)}`, w - 4));
121
121
  lines.push(' ' + renderKvRow('Auto-resume in', remaining === '0s' ? fg('yellow', 'now (cooldown elapsed)') : remaining, w - 4));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "4.8.75",
3
+ "version": "4.8.76",
4
4
  "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
5
5
  "type": "module",
6
6
  "bin": {