dsh-budget 0.1.2 → 0.3.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/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-08-26
9
+
10
+ ### Added
11
+
12
+ - Persist daily and monthly budget buckets across restarts.
13
+
14
+ ## [0.2.0] - 2026-08-23
15
+
16
+ ### Changed
17
+
18
+ - The Settings budget tab now honors `config.refreshIntervalMs` for its polling interval and `config.warnRatio` for its bar tone (both were previously hardcoded), and renders a per-day usage curve over `config.historyDays`, which is now actually consumed (it previously declared a limit no code read).
19
+
8
20
  ## [0.1.2] - 2026-08-22
9
21
 
10
22
  - Compatibility release for DeepSeek Harness `0.1.1-rc.2`: all `@deepseek-ai/dsh-*` devDependencies moved to rc.2 (peers unchanged at `>=0.1.0-rc.8 <0.2.0`); `dshWorkshop.compatibility.dshVersions` set to `0.1.1-rc.2`; the READMEs, AGENTS notes, and CI `compat`/`typecheck` pins synced to rc.2. No source changes required — the session/llm/typert/commands/client seams used here are unchanged in rc.2.
package/README.es.md CHANGED
@@ -35,7 +35,7 @@
35
35
  - **Medición agregada** — tokens (entrada sin caché / salida / lectura de caché / escritura de caché), costo USD estimado y huella de carbono por modelo, sesión y día, con una tabla integrada de USD por 1M tokens fusionada con tu `config.prices`.
36
36
  - **Gobernanza de presupuesto** — límites de sesión/diarios/mensuales; alerta de umbral `warnRatio` (webhook POST + indicador de notificación de escritorio) y tres políticas al superar el límite: `alert` (solo notificar), `block` (cortocircuitar nuevas solicitudes de modelo hasta que el usuario levante el bloqueo), `degrade` (bloqueo con guía correctiva que nombra el modelo más barato de tu mapa `degradation`).
37
37
  - **Carbono y latencia** — puente token→carbono (tokens × kWh/token × PUE × intensidad de la red regional, portado de AI-Carbon-Footprint-Calculator) y percentiles de latencia por modelo.
38
- - **Superficies** — pestaña Budget en Settings (barras de uso, desglose por modelo, alertas, editores de límites, botones de desbloqueo) y el comando `/budget` (`/budget`, `/budget models`, `/budget unblock <scope>`).
38
+ - **Superficies** — pestaña Budget en Settings (barras de uso, curva de uso por día, desglose por modelo, alertas, editores de límites, botones de desbloqueo) y el comando `/budget` (`/budget`, `/budget models`, `/budget unblock <scope>`).
39
39
 
40
40
  ## Inicio rápido
41
41
 
@@ -84,6 +84,7 @@ Todos los ajustes son campos `Config` de Schemastery (modificables desde cordis.
84
84
  | `currency` | `{code: USD, rate: 1.0, decimals: 2}` | Moneda de visualización (los costos se calculan en USD) |
85
85
  | `outputLanguage` | `en` | Idioma de salida de `/budget`: `en` / `zh` |
86
86
  | `historyDays` | `30` | Días de historial de uso diario en la instantánea |
87
+ | `persistence.enabled` / `intervalMs` | `true` / `10000` | Persistencia duradera diaria/mensual entre reinicios (dominio de almacenamiento); degrada a en-memoria si falta el dominio |
87
88
 
88
89
  ## Herramientas y superficies
89
90
 
@@ -92,7 +93,7 @@ Todos los ajustes son campos `Config` de Schemastery (modificables desde cordis.
92
93
  | `/budget` | Comando | Resumen por ámbito (uso, ratio, carbono, estado bloqueado) |
93
94
  | `/budget models` | Comando | Desglose por modelo con percentiles de latencia |
94
95
  | `/budget unblock <scope>` | Comando | Levantar un ámbito bloqueado (`session` / `daily` / `monthly`) |
95
- | Settings → Plugins → Budget | Pestaña de Settings | Barras de uso, desglose, alertas, editores de límites, desbloqueo |
96
+ | Settings → Plugins → Budget | Pestaña de Settings | Barras de uso, curva de uso por día, desglose, alertas, editores de límites, desbloqueo |
96
97
  | `budget/status`, `budget/setSettings`, `budget/unblock` | Typert Remote | Canal del cliente (consumido por la pestaña) |
97
98
 
98
99
  ## Permisos y datos
@@ -120,7 +121,7 @@ Todos los ajustes son campos `Config` de Schemastery (modificables desde cordis.
120
121
  pnpm install # node ^22.19 || >=24
121
122
  pnpm run typecheck # tsc: src + tests contra el checkout local del harness
122
123
  pnpm run typecheck:ci # tsc contra los tipos publicados 0.1.1-rc.2 (sin paths)
123
- pnpm test # vitest: 45 tests
124
+ pnpm test # vitest
124
125
  pnpm run build # declaraciones tsc + bundles tsdown (lib/)
125
126
  pnpm run verify:self-contained # las especificaciones de dependencias resuelven desde el registry
126
127
  pnpm run verify:artifacts # cara ESM + manifiesto typert + bundle de cliente
package/README.hi.md CHANGED
@@ -35,7 +35,7 @@
35
35
  - **समग्र मीटरिंग** — टोकन (बिना-कैश इनपुट / आउटपुट / कैश-रीड / कैश-राइट), अनुमानित USD लागत और कार्बन पदचिह्न प्रति मॉडल, सत्र और दिन; बिल्ट-इन USD-प्रति-1M तालिका आपके `config.prices` से मर्ज होती है।
36
36
  - **बजट प्रशासन** — सत्र/दैनिक/मासिक सीमाएँ; `warnRatio` थ्रेशोल्ड अलर्ट (वेबहुक POST + डेस्कटॉप-सूचना फ़्लैग) और सीमा पार होने पर तीन नीतियाँ: `alert` (केवल सूचित), `block` (उपयोगकर्ता द्वारा अनब्लॉक तक नए मॉडल अनुरोध रोकना), `degrade` (आपके `degradation` मैप के सस्ते मॉडल का नाम लेकर सुधारात्मक मार्गदर्शन सहित ब्लॉक)।
37
37
  - **कार्बन और लेटेंसी** — टोकन→कार्बन पुल (टोकन × kWh/टोकन × PUE × क्षेत्रीय ग्रिड तीव्रता, AI-Carbon-Footprint-Calculator से पोर्टेड) और प्रति-मॉडल लेटेंसी प्रतिशतक।
38
- - **सतहें** — Settings का Budget टैब (उपयोग बार, मॉडल विवरण, अलर्ट, सीमा संपादक, अनब्लॉक बटन) और `/budget` कमांड (`/budget`, `/budget models`, `/budget unblock <scope>`)।
38
+ - **सतहें** — Settings का Budget टैब (उपयोग बार, दैनिक उपयोग वक्र, मॉडल विवरण, अलर्ट, सीमा संपादक, अनब्लॉक बटन) और `/budget` कमांड (`/budget`, `/budget models`, `/budget unblock <scope>`)।
39
39
 
40
40
  ## त्वरित शुरुआत
41
41
 
@@ -84,6 +84,7 @@ dsh --profile web --dump-config | grep -A2 'id: budget'
84
84
  | `currency` | `{code: USD, rate: 1.0, decimals: 2}` | प्रदर्शन मुद्रा (लागत USD में गणित) |
85
85
  | `outputLanguage` | `en` | `/budget` आउटपुट भाषा: `en` / `zh` |
86
86
  | `historyDays` | `30` | स्नैपशॉट में रखे प्रति-दिन उपयोग इतिहास के दिन |
87
+ | `persistence.enabled` / `intervalMs` | `true` / `10000` | पुनरारंभ के बीच टिकाऊ दैनिक/मासिक संग्रहण (स्टोरेज डोमेन); डोमेन अनुपस्थित होने पर इन-मेमोरी में पतन |
87
88
 
88
89
  ## टूल और सतहें
89
90
 
@@ -92,7 +93,7 @@ dsh --profile web --dump-config | grep -A2 'id: budget'
92
93
  | `/budget` | कमांड | प्रति-स्कोप अवलोकन (उपयोग, अनुपात, कार्बन, ब्लॉक स्थिति) |
93
94
  | `/budget models` | कमांड | लेटेंसी प्रतिशतक सहित प्रति-मॉडल विवरण |
94
95
  | `/budget unblock <scope>` | कमांड | ब्लॉक किए गए स्कोप को हटाना (`session` / `daily` / `monthly`) |
95
- | Settings → Plugins → Budget | Settings टैब | उपयोग बार, मॉडल विवरण, अलर्ट, सीमा संपादक, अनब्लॉक बटन |
96
+ | Settings → Plugins → Budget | Settings टैब | उपयोग बार, दैनिक उपयोग वक्र, मॉडल विवरण, अलर्ट, सीमा संपादक, अनब्लॉक बटन |
96
97
  | `budget/status`, `budget/setSettings`, `budget/unblock` | Typert Remote | क्लाइंट चैनल (टैब इन्हें उपभोग करता है) |
97
98
 
98
99
  ## अनुमतियाँ और डेटा
@@ -120,7 +121,7 @@ dsh --profile web --dump-config | grep -A2 'id: budget'
120
121
  pnpm install # node ^22.19 || >=24
121
122
  pnpm run typecheck # tsc: src + tests स्थानीय हार्नेस चेकआउट के विरुद्ध
122
123
  pnpm run typecheck:ci # tsc प्रकाशित 0.1.1-rc.2 प्रकारों के विरुद्ध (बिना paths)
123
- pnpm test # vitest: 45 टेस्ट
124
+ pnpm test # vitest
124
125
  pnpm run build # tsc घोषणाएँ + tsdown बंडल (lib/)
125
126
  pnpm run verify:self-contained # निर्भरता स्पेक registry से हल होती हैं
126
127
  pnpm run verify:artifacts # ESM फ़ेस + typert मैनिफ़ेस्ट + क्लाइंट बंडल
package/README.md CHANGED
@@ -36,7 +36,7 @@
36
36
  - **Aggregated metering** — tokens (uncached input / output / cache read / cache write), estimated USD cost, and carbon footprint per model, session, and day, priced through a built-in USD-per-1M table merged with your `config.prices`.
37
37
  - **Budget governance** — session/daily/monthly caps; a warn-ratio threshold alert (webhook POST + desktop-notification flag) and three over-limit policies: `alert` (notify only), `block` (short-circuit new model requests until the user lifts the block), `degrade` (block with corrective guidance naming the cheaper model from your `degradation` map).
38
38
  - **Carbon & latency** — token→carbon bridge (tokens × kWh/token × PUE × regional grid intensity, ported from AI-Carbon-Footprint-Calculator) and per-model latency percentiles.
39
- - **Surfaces** — the Settings budget tab (usage bars, model breakdown, alerts, cap editors, unblock buttons) and the `/budget` command (`/budget`, `/budget models`, `/budget unblock <scope>`).
39
+ - **Surfaces** — the Settings budget tab (usage bars, per-day usage curve, model breakdown, alerts, cap editors, unblock buttons) and the `/budget` command (`/budget`, `/budget models`, `/budget unblock <scope>`).
40
40
 
41
41
  ## Quick start
42
42
 
@@ -85,6 +85,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). `cord
85
85
  | `currency` | `{code: USD, rate: 1.0, decimals: 2}` | Display currency (costs are computed in USD) |
86
86
  | `outputLanguage` | `en` | `/budget` output language: `en` / `zh` |
87
87
  | `historyDays` | `30` | Per-day usage history kept in the panel snapshot |
88
+ | `persistence.enabled` / `intervalMs` | `true` / `10000` | Durable day/month persistence across restarts (storage domain); degrades to in-memory when the domain is absent |
88
89
 
89
90
  ## Tools & surfaces
90
91
 
@@ -93,7 +94,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). `cord
93
94
  | `/budget` | Command | Per-scope overview (usage, ratio, carbon, blocked state) |
94
95
  | `/budget models` | Command | Per-model breakdown with latency percentiles |
95
96
  | `/budget unblock <scope>` | Command | Lift a blocked scope (`session` / `daily` / `monthly`) |
96
- | Settings → Plugins → Budget | Settings tab | Usage bars, model breakdown, alerts, cap editors, unblock buttons |
97
+ | Settings → Plugins → Budget | Settings tab | Usage bars, per-day usage curve, model breakdown, alerts, cap editors, unblock buttons |
97
98
  | `budget/status`, `budget/setSettings`, `budget/unblock` | Typert Remote | The client channel (the tab consumes these) |
98
99
 
99
100
  ## Permissions & data
@@ -121,7 +122,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). `cord
121
122
  pnpm install # node ^22.19 || >=24
122
123
  pnpm run typecheck # tsc: src + tests against the local harness checkout
123
124
  pnpm run typecheck:ci # tsc against the published 0.1.1-rc.2 types (no paths)
124
- pnpm test # vitest: 45 tests
125
+ pnpm test # vitest
125
126
  pnpm run build # tsc declarations + tsdown bundles (lib/)
126
127
  pnpm run verify:self-contained # dependency specs resolve from the registry
127
128
  pnpm run verify:artifacts # built ESM face + typert manifest + client bundle
package/README.pt.md CHANGED
@@ -35,7 +35,7 @@ O `dsh-budget` transforma o fluxo de eventos da sessão em um ciclo de governan
35
35
  - **Medição agregada** — tokens (entrada sem cache / saída / leitura de cache / escrita de cache), custo USD estimado e pegada de carbono por modelo, sessão e dia, precificados por uma tabela integrada de USD por 1M de tokens mesclada com `config.prices`.
36
36
  - **Governança de orçamento** — tetos de sessão/diários/mensais; alerta de limiar `warnRatio` (webhook POST + indicador de notificação de desktop) e três políticas ao estourar: `alert` (apenas notificar), `block` (curto-circuitar novas requisições até o usuário liberar), `degrade` (bloqueio com orientação corretiva nomeando o modelo mais barato do seu mapa `degradation`).
37
37
  - **Carbono e latência** — ponte token→carbono (tokens × kWh/token × PUE × intensidade da rede regional, portado do AI-Carbon-Footprint-Calculator) e percentis de latência por modelo.
38
- - **Superfícies** — a aba Budget em Settings (barras de uso, detalhamento por modelo, alertas, editores de teto, botões de desbloqueio) e o comando `/budget` (`/budget`, `/budget models`, `/budget unblock <scope>`).
38
+ - **Superfícies** — a aba Budget em Settings (barras de uso, curva de uso por dia, detalhamento por modelo, alertas, editores de teto, botões de desbloqueio) e o comando `/budget` (`/budget`, `/budget models`, `/budget unblock <scope>`).
39
39
 
40
40
  ## Início rápido
41
41
 
@@ -84,6 +84,7 @@ Todos os ajustes são campos `Config` do Schemastery (alteráveis pelo cordis.ym
84
84
  | `currency` | `{code: USD, rate: 1.0, decimals: 2}` | Moeda de exibição (custos calculados em USD) |
85
85
  | `outputLanguage` | `en` | Idioma de saída do `/budget`: `en` / `zh` |
86
86
  | `historyDays` | `30` | Dias de histórico de uso diário no snapshot |
87
+ | `persistence.enabled` / `intervalMs` | `true` / `10000` | Persistência durável diária/mensal entre reinícios (domínio de armazenamento); degrada para em-memória se o domínio estiver ausente |
87
88
 
88
89
  ## Ferramentas e superfícies
89
90
 
@@ -92,7 +93,7 @@ Todos os ajustes são campos `Config` do Schemastery (alteráveis pelo cordis.ym
92
93
  | `/budget` | Comando | Visão geral por escopo (uso, proporção, carbono, estado bloqueado) |
93
94
  | `/budget models` | Comando | Detalhamento por modelo com percentis de latência |
94
95
  | `/budget unblock <scope>` | Comando | Liberar um escopo bloqueado (`session` / `daily` / `monthly`) |
95
- | Settings → Plugins → Budget | Aba de Settings | Barras de uso, detalhamento, alertas, editores de teto, desbloqueio |
96
+ | Settings → Plugins → Budget | Aba de Settings | Barras de uso, curva de uso por dia, detalhamento, alertas, editores de teto, desbloqueio |
96
97
  | `budget/status`, `budget/setSettings`, `budget/unblock` | Typert Remote | Canal do cliente (consumido pela aba) |
97
98
 
98
99
  ## Permissões e dados
@@ -120,7 +121,7 @@ Todos os ajustes são campos `Config` do Schemastery (alteráveis pelo cordis.ym
120
121
  pnpm install # node ^22.19 || >=24
121
122
  pnpm run typecheck # tsc: src + tests contra o checkout local do harness
122
123
  pnpm run typecheck:ci # tsc contra os tipos publicados 0.1.1-rc.2 (sem paths)
123
- pnpm test # vitest: 45 testes
124
+ pnpm test # vitest
124
125
  pnpm run build # declarações tsc + bundles tsdown (lib/)
125
126
  pnpm run verify:self-contained # especificações de dependências resolvem pelo registry
126
127
  pnpm run verify:artifacts # face ESM + manifesto typert + bundle de cliente
package/README.zh.md CHANGED
@@ -35,7 +35,7 @@
35
35
  - **聚合计量** —— token(未缓存输入 / 输出 / 缓存读 / 缓存写)、估算 USD 成本与碳足迹,按模型/会话/天聚合;内置 USD/百万 token 价目表与 `config.prices` 合并定价。
36
36
  - **预算治理** —— 会话/日/月三档封顶;warnRatio 阈值告警(webhook POST + 桌面通知开关)与三种超限策略:`alert`(仅告警)、`block`(在用户解除前短路新模型请求)、`degrade`(阻断并给出指向 `degradation` 映射中更便宜模型的修正提示)。
37
37
  - **碳足迹与延迟** —— token→碳桥接(tokens × kWh/token × PUE × 区域电网强度,移植自 AI-Carbon-Footprint-Calculator)与按模型延迟百分位。
38
- - **界面** —— 设置页预算页签(用量条、模型明细、告警、上限编辑、解除阻断按钮)与 `/budget` 命令(`/budget`、`/budget models`、`/budget unblock <scope>`)。
38
+ - **界面** —— 设置页预算页签(用量条、按天用量曲线、模型明细、告警、上限编辑、解除阻断按钮)与 `/budget` 命令(`/budget`、`/budget models`、`/budget unblock <scope>`)。
39
39
 
40
40
  ## 快速开始
41
41
 
@@ -84,6 +84,7 @@ dsh --profile web --dump-config | grep -A2 'id: budget'
84
84
  | `currency` | `{code: USD, rate: 1.0, decimals: 2}` | 展示货币(成本以 USD 计算,仅展示换算) |
85
85
  | `outputLanguage` | `en` | `/budget` 输出语言:`en` / `zh` |
86
86
  | `historyDays` | `30` | 面板快照保留的按天用量历史天数 |
87
+ | `persistence.enabled` / `intervalMs` | `true` / `10000` | 日/月用量跨重启持久化(storage 域);域缺失时降级为进程内聚合 |
87
88
 
88
89
  ## 工具与界面
89
90
 
@@ -92,7 +93,7 @@ dsh --profile web --dump-config | grep -A2 'id: budget'
92
93
  | `/budget` | 命令 | 各作用域概览(用量、比例、碳足迹、阻断状态) |
93
94
  | `/budget models` | 命令 | 按模型明细 + 延迟百分位 |
94
95
  | `/budget unblock <scope>` | 命令 | 解除某作用域阻断(`session` / `daily` / `monthly`) |
95
- | 设置 → 插件 → 预算 | 设置页签 | 用量条、模型明细、告警、上限编辑、解除阻断按钮 |
96
+ | 设置 → 插件 → 预算 | 设置页签 | 用量条、按天用量曲线、模型明细、告警、上限编辑、解除阻断按钮 |
96
97
  | `budget/status`、`budget/setSettings`、`budget/unblock` | Typert Remote | 客户端通道(页签消费这些方法) |
97
98
 
98
99
  ## 权限与数据
@@ -120,7 +121,7 @@ dsh --profile web --dump-config | grep -A2 'id: budget'
120
121
  pnpm install # node ^22.19 || >=24
121
122
  pnpm run typecheck # tsc:src + tests,对照本地 harness checkout
122
123
  pnpm run typecheck:ci # tsc:对照已发布的 0.1.1-rc.2 类型(无 paths)
123
- pnpm test # vitest:45 个测试
124
+ pnpm test # vitest
124
125
  pnpm run build # tsc 声明 + tsdown bundles(lib/)
125
126
  pnpm run verify:self-contained # 依赖声明全部来自 registry
126
127
  pnpm run verify:artifacts # 构建产物 ESM 面 + typert manifest + 客户端 bundle
package/cordis.patch.yml CHANGED
@@ -71,3 +71,10 @@
71
71
  outputLanguage: en
72
72
  # Days of per-day usage history kept in the panel snapshot.
73
73
  historyDays: 30
74
+ # Durable day/month persistence across restarts (storage domain).
75
+ # When the storage domain is absent the plugin degrades to in-memory
76
+ # aggregation (the pre-persistence behavior).
77
+ persistence:
78
+ enabled: true
79
+ # Milliseconds between persistence snapshots (1000..300000).
80
+ intervalMs: 10000
package/lib/client.js CHANGED
@@ -39,6 +39,7 @@ window.__ModuleLoader__.load({
39
39
  const [desktopOn, setDesktopOn] = (0, react.useState)(false);
40
40
  const [saved, setSaved] = (0, react.useState)(null);
41
41
  const [busy, setBusy] = (0, react.useState)(false);
42
+ const [refreshMs, setRefreshMs] = (0, react.useState)(5e3);
42
43
  const load = async () => {
43
44
  try {
44
45
  const snapshot = await status();
@@ -51,6 +52,7 @@ window.__ModuleLoader__.load({
51
52
  setMonthlyCap(snapshot.scopes.find((scope) => scope.scope === "monthly")?.capUsd?.toString() ?? "");
52
53
  setAlertsOn(snapshot.alertsEnabled);
53
54
  setDesktopOn(snapshot.desktopNotifications);
55
+ setRefreshMs(snapshot.refreshIntervalMs);
54
56
  } catch (error) {
55
57
  setState({
56
58
  status: "error",
@@ -60,13 +62,16 @@ window.__ModuleLoader__.load({
60
62
  };
61
63
  (0, react.useEffect)(() => {
62
64
  load();
65
+ }, []);
66
+ (0, react.useEffect)(() => {
67
+ if (refreshMs <= 0) return;
63
68
  const timer = setInterval(() => {
64
69
  load();
65
- }, 5e3);
70
+ }, refreshMs);
66
71
  return () => {
67
72
  clearInterval(timer);
68
73
  };
69
- }, []);
74
+ }, [refreshMs]);
70
75
  const parseCap = (text) => {
71
76
  if (text.trim() === "") return null;
72
77
  const value = Number(text);
@@ -132,6 +137,7 @@ window.__ModuleLoader__.load({
132
137
  })]
133
138
  });
134
139
  const snapshot = state.snapshot;
140
+ const maxDayCost = Math.max(0, ...snapshot.days.map((day) => day.costUsd));
135
141
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
136
142
  className: "dbud-section",
137
143
  "data-dsh-budget": true,
@@ -143,7 +149,7 @@ window.__ModuleLoader__.load({
143
149
  children: t("scopes")
144
150
  }), snapshot.scopes.map((scope) => {
145
151
  const blocked = snapshot.blockedScopes.includes(scope.scope);
146
- const tone = scopeTone(scope, .8, blocked);
152
+ const tone = scopeTone(scope, snapshot.warnRatio, blocked);
147
153
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
148
154
  className: "dbud-row",
149
155
  children: [
@@ -183,6 +189,33 @@ window.__ModuleLoader__.load({
183
189
  }, scope.scope);
184
190
  })]
185
191
  }),
192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
193
+ className: "dbud-rows",
194
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
195
+ className: "dbud-heading",
196
+ children: t("usageCurve")
197
+ }), snapshot.days.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
198
+ className: "dbud-status",
199
+ children: "—"
200
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
201
+ className: "dbud-curve",
202
+ children: snapshot.days.map((day) => {
203
+ const height = maxDayCost === 0 ? 0 : Math.round(day.costUsd / maxDayCost * 100);
204
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
205
+ className: "dbud-curve-col",
206
+ title: `${day.day}: ${formatMoney(snapshot, day.costUsd)}`,
207
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
208
+ className: "dbud-curve-bar",
209
+ style: { height: `${height}%` },
210
+ "data-empty": day.costUsd === 0 ? "" : void 0
211
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
212
+ className: "dbud-curve-label",
213
+ children: day.day.slice(5)
214
+ })]
215
+ }, day.day);
216
+ })
217
+ })]
218
+ }),
186
219
  snapshot.degradedModel !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
187
220
  className: "dbud-notice",
188
221
  children: [
@@ -355,7 +388,8 @@ window.__ModuleLoader__.load({
355
388
  save: "保存",
356
389
  saved: "已保存(会话内生效,重载后恢复 cordis.yml 配置)",
357
390
  degraded: "已降级至",
358
- history: "最近告警"
391
+ history: "最近告警",
392
+ usageCurve: "用量曲线"
359
393
  };
360
394
  /** English dictionary (source of truth for keys). */
361
395
  const en = {
@@ -391,7 +425,8 @@ window.__ModuleLoader__.load({
391
425
  save: "Save",
392
426
  saved: "Saved (session-scoped; a reload restores the cordis.yml values)",
393
427
  degraded: "Degraded to",
394
- history: "Recent alerts"
428
+ history: "Recent alerts",
429
+ usageCurve: "Usage curve"
395
430
  };
396
431
  //#endregion
397
432
  //#region node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
@@ -4502,7 +4537,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4502
4537
  }),
4503
4538
  alertsEnabled: boolean(),
4504
4539
  desktopNotifications: boolean(),
4505
- degradedModel: string().nullable()
4540
+ degradedModel: string().nullable(),
4541
+ days: array(object({
4542
+ day: string(),
4543
+ costUsd: number(),
4544
+ tokens: number().int(),
4545
+ carbonKg: number()
4546
+ })),
4547
+ warnRatio: number().min(0).max(1),
4548
+ refreshIntervalMs: number().int().min(1e3)
4506
4549
  });
4507
4550
  /**
4508
4551
  * The `budget/status` invocation descriptor, shared verbatim by the host
@@ -4773,6 +4816,42 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4773
4816
  [data-dsh-budget] .dbud-alert[data-kind='over'] {
4774
4817
  color: var(--dsw-alias-state-error-primary);
4775
4818
  }
4819
+ [data-dsh-budget] .dbud-curve {
4820
+ display: flex;
4821
+ align-items: flex-end;
4822
+ gap: 4px;
4823
+ height: 96px;
4824
+ padding: 8px 8px 0;
4825
+ border: 1px solid var(--dsw-alias-border-l3);
4826
+ border-radius: 6px;
4827
+ background: var(--dsw-alias-bg-layer-1);
4828
+ }
4829
+ [data-dsh-budget] .dbud-curve-col {
4830
+ flex: 1 1 0;
4831
+ min-width: 6px;
4832
+ height: 100%;
4833
+ display: flex;
4834
+ flex-direction: column;
4835
+ justify-content: flex-end;
4836
+ align-items: stretch;
4837
+ gap: 4px;
4838
+ }
4839
+ [data-dsh-budget] .dbud-curve-bar {
4840
+ background: var(--dsw-alias-brand-primary);
4841
+ border-radius: 2px 2px 0 0;
4842
+ min-height: 1px;
4843
+ }
4844
+ [data-dsh-budget] .dbud-curve-bar[data-empty] {
4845
+ background: var(--dsw-alias-bg-layer-2);
4846
+ }
4847
+ [data-dsh-budget] .dbud-curve-label {
4848
+ font-size: 0.7em;
4849
+ text-align: center;
4850
+ color: var(--dsw-alias-label-secondary);
4851
+ overflow: hidden;
4852
+ text-overflow: ellipsis;
4853
+ white-space: nowrap;
4854
+ }
4776
4855
  [data-dsh-budget] .dbud-form {
4777
4856
  display: flex;
4778
4857
  flex-direction: column;