@linxin666/dsh-usage 0.3.24 → 0.4.1

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/lib/index.js CHANGED
@@ -45,7 +45,7 @@ function mountOnce(packageName, fn) {
45
45
  });
46
46
  }
47
47
  //#endregion
48
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-brand/lib/index.js
48
+ //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.7-rc.1_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-brand/lib/index.js
49
49
  /**
50
50
  * Duplicate-install-safe nominal primitive helpers.
51
51
  *
@@ -68,7 +68,7 @@ function brandString(value) {
68
68
  return value;
69
69
  }
70
70
  //#endregion
71
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-credentials@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-inva_8353d2c97c08a345a5c3047263f2fe4a/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
71
+ //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-credentials@0.1.7-rc.1_@deepseek-ai+cordis@4.0.4_@deepseek-ai+dsh-inva_9b80248df36b741e5eb76c73b242287a/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
72
72
  /**
73
73
  * Service Definition for the credential-reference capability seam (`ctx.credentials`). Settings and composition files carry
74
74
  * *references* to secrets — environment-variable names — while providers own
@@ -149,6 +149,48 @@ function dshHome() {
149
149
  return resolveDshHome();
150
150
  }
151
151
  //#endregion
152
+ //#region src/host/http.ts
153
+ /** Family-default JSON response headers; callers may append or override. */
154
+ const JSON_HEADERS = {
155
+ "content-type": "application/json; charset=utf-8",
156
+ "referrer-policy": "no-referrer"
157
+ };
158
+ /**
159
+ * Request init that asks a remote origin for an uncompressed body.
160
+ *
161
+ * The DSH host boots `@deepseek-ai/dsh-http-proxy`, whose top-level import of
162
+ * the npm `undici` copy replaces the legacy `undici.globalDispatcher.1` slot
163
+ * Node's built-in `fetch()` reads. That cross-major wrapper drops the
164
+ * `content-encoding` header and automatic decompression, so a plain fetch
165
+ * resolves to raw gzip/brotli/zstd bytes — compressed noise no JSON or text
166
+ * consumer can read. Every host fetch that parses a remote body must request
167
+ * identity encoding. Caller headers survive; `accept-encoding` is forced to
168
+ * `identity` because a caller value has no decoder behind it in this host.
169
+ * @param init - the caller's request init (signal, headers, method, ...).
170
+ * @returns a copy with `accept-encoding: identity` merged in.
171
+ */
172
+ function withIdentityEncoding(init = {}) {
173
+ const headers = new Headers(init.headers);
174
+ headers.set("accept-encoding", "identity");
175
+ return {
176
+ ...init,
177
+ headers
178
+ };
179
+ }
180
+ /**
181
+ * Write one JSON response. Default headers are the family defaults
182
+ * (content-type and referrer-policy); caller headers are appended or
183
+ * override them.
184
+ */
185
+ function writeJson(res, status, body, headers = {}) {
186
+ const payload = JSON.stringify(body);
187
+ res.writeHead(status, {
188
+ ...JSON_HEADERS,
189
+ ...headers
190
+ });
191
+ res.end(payload);
192
+ }
193
+ //#endregion
152
194
  //#region src/core/adapters.ts
153
195
  /** Parse a string/number into a finite number, else undefined. */
154
196
  function toNum(value) {
@@ -1010,8 +1052,9 @@ var UsageService = class {
1010
1052
  }
1011
1053
  /**
1012
1054
  * Stop timers and flush pending ledger writes. The returned promise
1013
- * resolves after the final flush lands, so a successor instance (quick
1014
- * disable → enable) can serialize its first load behind it.
1055
+ * resolves after the final flush lands, so a successor instance (the Host
1056
+ * reloads the profile row on a config change) can serialize its first load
1057
+ * behind it.
1015
1058
  */
1016
1059
  stop() {
1017
1060
  this.disposed = true;
@@ -1020,7 +1063,7 @@ var UsageService = class {
1020
1063
  this.sessionListenerDisposer?.();
1021
1064
  return this.flushLedger();
1022
1065
  }
1023
- /** Re-apply options live (settings change); retention shrink prunes now. */
1066
+ /** Re-apply options to this running instance; retention shrink prunes now. */
1024
1067
  applyOptions(options) {
1025
1068
  this.options = options;
1026
1069
  this.pruneIfNeeded();
@@ -1397,10 +1440,10 @@ var UsageService = class {
1397
1440
  apiKey: credential.key,
1398
1441
  ...credential.accountId !== void 0 ? { accountId: credential.accountId } : {}
1399
1442
  });
1400
- const response = await fetch(spec.url, {
1443
+ const response = await fetch(spec.url, withIdentityEncoding({
1401
1444
  headers: spec.headers,
1402
1445
  signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
1403
- });
1446
+ }));
1404
1447
  const body = await response.json().catch(() => void 0);
1405
1448
  if (!response.ok) throw new Error(providerErrorMessage(response.status, body));
1406
1449
  const parsed = half.parse(response.status, body);
@@ -1558,26 +1601,6 @@ function isUsageAllowed(ctx, request) {
1558
1601
  return isPairedOrLoopbackAllowed(ctx, request);
1559
1602
  }
1560
1603
  //#endregion
1561
- //#region src/host/http.ts
1562
- /** Family-default JSON response headers; callers may append or override. */
1563
- const JSON_HEADERS = {
1564
- "content-type": "application/json; charset=utf-8",
1565
- "referrer-policy": "no-referrer"
1566
- };
1567
- /**
1568
- * Write one JSON response. Default headers are the family defaults
1569
- * (content-type and referrer-policy); caller headers are appended or
1570
- * override them.
1571
- */
1572
- function writeJson(res, status, body, headers = {}) {
1573
- const payload = JSON.stringify(body);
1574
- res.writeHead(status, {
1575
- ...JSON_HEADERS,
1576
- ...headers
1577
- });
1578
- res.end(payload);
1579
- }
1580
- //#endregion
1581
1604
  //#region src/host/routes.ts
1582
1605
  /**
1583
1606
  * Overview route: provider balances, plan quotas, and token usage totals.
@@ -1648,6 +1671,12 @@ function makeUsageRefreshRoute(ctx, service) {
1648
1671
  //#region src/index.ts
1649
1672
  const name = "dsh-usage";
1650
1673
  const inject = ["webServer"];
1674
+ /**
1675
+ * Family settings namespace this row serves. Since 0.1.7 the settings surface
1676
+ * addresses one form per profile entry id and the Host generates this row's
1677
+ * page from the Config schema below, so the namespace is only the alias the
1678
+ * family settings bridge (dsh-web-settings) resolves onto this row's entry id.
1679
+ */
1651
1680
  const USAGE_SETTINGS_NAMESPACE = "dsh-usage";
1652
1681
  const Config = z.object({
1653
1682
  enabled: z.boolean().default(true),
@@ -1661,62 +1690,50 @@ function resolveConfig(config) {
1661
1690
  retainDays: typeof config?.retainDays === "number" ? config.retainDays : 180
1662
1691
  };
1663
1692
  }
1693
+ /**
1694
+ * Tail of the process-wide flush chain; undefined while no predecessor's final
1695
+ * ledger flush is still in flight. The Host reloads this profile row when its
1696
+ * configuration changes, so a successor activation is a dispose + apply pair in
1697
+ * the same process: its first ledger load must serialize behind its
1698
+ * predecessor's final write (and behind the reloads before that one).
1699
+ */
1700
+ let pendingStop;
1701
+ /**
1702
+ * Host body. The plugin's own Config schema above is the settings document the
1703
+ * 0.1.7 Host serves for this row; the effective config arrives here as the
1704
+ * `config` argument the Loader resolved and the Host reloads the row when the
1705
+ * user saves a change, so nothing in this body observes or writes settings.
1706
+ * @param ctx - host root context.
1707
+ * @param config - the row's resolved configuration.
1708
+ */
1664
1709
  const apply = mountOnce("@linxin666/dsh-usage", (ctx, config) => {
1665
- let source = () => config ?? {};
1710
+ const value = resolveConfig(config);
1711
+ if (!value.enabled) return;
1712
+ let live = true;
1666
1713
  let service;
1667
- let pendingStop;
1668
1714
  let disposeRoutes;
1669
- const rearm = () => {
1670
- const value = resolveConfig(source());
1671
- if (!value.enabled) {
1672
- pendingStop = service?.stop();
1673
- service = void 0;
1674
- disposeRoutes?.();
1675
- disposeRoutes = void 0;
1676
- return;
1677
- }
1678
- if (service === void 0) {
1679
- const next = new UsageService(ctx, value);
1680
- service = next;
1681
- const begin = () => {
1682
- if (service !== next) return;
1683
- next.start();
1684
- const disposers = [makeUsageOverviewRoute(ctx, next), makeUsageRefreshRoute(ctx, next)].map((route) => ctx.webServer.register(route));
1685
- disposeRoutes = () => {
1686
- for (const dispose of disposers) try {
1687
- dispose();
1688
- } catch {}
1689
- };
1690
- };
1691
- if (pendingStop !== void 0) pendingStop.then(begin, begin);
1692
- else begin();
1693
- } else service.applyOptions(value);
1715
+ const begin = () => {
1716
+ if (!live) return;
1717
+ const next = new UsageService(ctx, value);
1718
+ service = next;
1719
+ next.start();
1720
+ const disposers = [makeUsageOverviewRoute(ctx, next), makeUsageRefreshRoute(ctx, next)].map((route) => ctx.webServer.register(route));
1721
+ disposeRoutes = () => {
1722
+ for (const dispose of disposers) try {
1723
+ dispose();
1724
+ } catch {}
1725
+ };
1694
1726
  };
1695
- ctx.inject(["settings"], (settingsCtx) => {
1696
- try {
1697
- if (typeof settingsCtx.settings?.installSection === "function") settingsCtx.settings.installSection(ctx, USAGE_SETTINGS_NAMESPACE, Config, config ?? {}, {
1698
- setSource: (next) => {
1699
- source = next;
1700
- rearm();
1701
- },
1702
- onChange: rearm
1703
- });
1704
- else if (typeof settingsCtx.settings?.register === "function") {
1705
- const scope = settingsCtx.settings.register(USAGE_SETTINGS_NAMESPACE, Config, { base: config ?? {} });
1706
- source = () => scope?.get?.() ?? config ?? {};
1707
- scope?.watch?.(() => {
1708
- rearm();
1709
- });
1710
- rearm();
1711
- }
1712
- } catch {}
1713
- });
1714
1727
  ctx.effect(() => {
1715
- rearm();
1728
+ if (pendingStop === void 0) begin();
1729
+ else pendingStop = pendingStop.then(begin, begin).then(() => void 0);
1716
1730
  return () => {
1731
+ live = false;
1717
1732
  disposeRoutes?.();
1718
- service?.stop();
1733
+ disposeRoutes = void 0;
1734
+ const stopping = service?.stop();
1719
1735
  service = void 0;
1736
+ if (stopping !== void 0) pendingStop = (pendingStop ?? Promise.resolve()).then(() => stopping);
1720
1737
  };
1721
1738
  }, "dsh-usage: runtime");
1722
1739
  });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Sidebar foot card: the compact usage glance seated below the shell sidebar's
3
+ * Settings row. It is deliberately a GLANCE, not the panel the 2026-09-18
4
+ * removal took out. Two states, persisted across reloads:
5
+ *
6
+ * - expanded (default): a price-first headline (today's estimated spend,
7
+ * falling back to today's tokens when nothing priced was recorded), a
8
+ * tokens/calls line, up to two configured-provider balances, and an
9
+ * updated-at footer;
10
+ * - collapsed: a one-line strip with the gauge glyph, the spending
11
+ * provider's name, the label and the headline value.
12
+ *
13
+ * The card body is one button that opens the settings panel on the usage
14
+ * section, so detail lives in exactly one place; the corner chevron toggles
15
+ * the collapse state (sibling buttons — buttons inside a button are invalid
16
+ * HTML).
17
+ *
18
+ * Data comes from the same shared store the settings section reads; the card
19
+ * runs its own relaxed poll loop (30 s, visible-tab only) because the sidebar
20
+ * foot is permanently mounted. The card disappears while the plugin is
21
+ * disabled (settings flag) or while the host serves no usage routes (a 404
22
+ * means the host half is off), matching the section's own gating.
23
+ * @module @linxin666/dsh-usage/client/UsageFootCard
24
+ */
25
+ import { type ReactNode } from 'react';
26
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
27
+ import { type UsageSettings } from './UsageSectionCard.tsx';
28
+ import type { UsageStoreInstance } from './usage-store.ts';
29
+ /** Props the mount forwards into the React tree. */
30
+ export interface UsageFootCardProps {
31
+ /** The shared overview store (section and card read the same snapshot). */
32
+ store: UsageStoreInstance;
33
+ /** Fetch one overview now. */
34
+ poll: () => void;
35
+ /** Card-body click: open the settings panel on the usage section. */
36
+ onOpen: () => void;
37
+ /** The shared settings form; the card hides while the plugin is disabled. */
38
+ settings: ConfigForm<UsageSettings>;
39
+ /** Locale-change source; the plain copy re-renders on a language switch. */
40
+ locale?: {
41
+ subscribe(listener: () => void): () => void;
42
+ };
43
+ }
44
+ /** Poll cadence of the permanently seated card; the section's 10 s loop stays its own. */
45
+ export declare const FOOT_CARD_POLL_MS = 30000;
46
+ /** localStorage key holding the collapsed flag ('1' = collapsed strip). */
47
+ export declare const FOOT_CARD_COLLAPSED_KEY = "dsh-usage.foot-card.collapsed";
48
+ /** Read the persisted collapsed flag (absent = expanded). */
49
+ export declare function readFootCardCollapsed(): boolean;
50
+ /**
51
+ * Render the foot card content for the current store snapshot.
52
+ * @param props - store, poller, settings form and open callback from the mount.
53
+ * @returns the card, or null while disabled / host-off.
54
+ */
55
+ export declare function UsageFootCard(props: UsageFootCardProps): ReactNode;
@@ -8,7 +8,7 @@
8
8
  * @module @linxin666/dsh-usage/client/UsageSectionCard
9
9
  */
10
10
  import { type ReactNode } from 'react';
11
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
11
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
12
12
  import type { UsageStoreInstance } from './usage-store.ts';
13
13
  /** The settings fields this section edits (immediate-apply semantics). */
14
14
  export interface UsageSettings {
@@ -23,8 +23,8 @@ export interface UsageSectionFace {
23
23
  poll: () => void;
24
24
  /** Force a host probe cycle now (resolves with the fresh overview). */
25
25
  refresh: () => void;
26
- /** Whether a forced refresh is in flight (component-local state mirrors it). */
27
- settings: SettingsScope<UsageSettings>;
26
+ /** The shared configuration form this section's settings row reads and writes. */
27
+ settings: ConfigForm<UsageSettings>;
28
28
  }
29
29
  export interface UsageSectionProps extends UsageSectionFace {
30
30
  /** Close the settings panel (the shell owns the open state). */
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Sidebar foot card mounting.
3
+ *
4
+ * The foot's only slot (sidebar.footer.action) stacks ABOVE the Settings row
5
+ * and cannot host a block, so the card container is inserted into the shell's
6
+ * foot area directly above the Settings seat — keeping Settings the foot's
7
+ * terminal row instead of hanging a card off the sidebar floor. It self-heals
8
+ * with the page-wide body-mutation hub: a React re-render that displaces the
9
+ * container re-seats it on the next frame, and a whole-pane rebuild is noticed
10
+ * by the same body-level watcher the family entry rows use.
11
+ *
12
+ * The container is a plain div carrying its own React root, so it can never
13
+ * disturb the shell's reconciliation.
14
+ * @module @linxin666/dsh-usage/client/foot-card-mount
15
+ */
16
+ import { type UsageFootCardProps } from './UsageFootCard.tsx';
17
+ /** Stable data attribute identifying the injected card container. */
18
+ export declare const FOOT_CARD_SELECTOR = "[data-dsh-usage-foot-card]";
19
+ /**
20
+ * Open the settings panel on the usage section. The shell owns the panel's
21
+ * open state and exposes no service for it, so the card replays the user's
22
+ * own path: activate the sidebar Settings trigger, then pick the nav row
23
+ * carrying the section's (localized) label. Every step degrades silently —
24
+ * with the panel already open only the nav pick runs, and a missing row
25
+ * leaves the panel on its default section.
26
+ * @param label - the usage section's nav label in the active locale.
27
+ */
28
+ export declare function openUsageSettings(label: () => string): void;
29
+ /**
30
+ * Mount the sidebar foot card directly above the Settings row.
31
+ * @param props - the store/poll/settings/open inputs of the apply body.
32
+ * @returns disposer removing the container and its observers.
33
+ */
34
+ export declare function mountUsageFootCard(props: UsageFootCardProps): () => void;
@@ -1,27 +1,40 @@
1
1
  /**
2
2
  * dsh-usage browser half — seats the first-level 使用统计 settings section
3
- * (below the Workshop entry) and polls the host overview only while the
4
- * section is open. All provider probing and credential handling happens in
5
- * the host half; this bundle only renders the overview document.
3
+ * (below the Workshop entry) plus the compact usage glance card below the
4
+ * sidebar's Settings row. The section polls the host overview only while it
5
+ * is open; the foot card runs its own relaxed loop because the sidebar foot
6
+ * is permanently mounted. All provider probing and credential handling
7
+ * happens in the host half; this bundle only renders the overview document.
6
8
  * @module @linxin666/dsh-usage/client
7
9
  */
8
10
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
9
- import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client';
11
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
10
12
  import { type UsageSettings } from './UsageSectionCard.tsx';
11
13
  /** Required services. */
12
14
  export declare const inject: string[];
13
15
  export type { UsageSectionProps, UsageSectionFace } from './UsageSectionCard.tsx';
14
16
  export type { UsageUiState } from './usage-store.ts';
15
17
  export type { UsageSettings };
18
+ /**
19
+ * One settings namespace a family card binds. The 0.1.7 client exports no spec
20
+ * type (the form controller takes it privately), so the binder's input shape is
21
+ * restated here.
22
+ */
23
+ export interface UsageSettingsBindSpec<T> {
24
+ /** Settings namespace registered by the owning host plugin. */
25
+ namespace: string;
26
+ /** Narrow one wire section; undefined keeps the last accepted value. */
27
+ decode?: (section: unknown) => T | undefined;
28
+ }
16
29
  declare module '@deepseek-ai/cordis' {
17
30
  interface Context {
18
31
  /**
19
- * Optional rc.6 compatibility binder provided by dsh-web-settings;
20
- * absent when that group plugin is not installed, so callers fall back to
21
- * the official settings scope.
32
+ * Optional family settings binder provided by dsh-web-settings; absent when
33
+ * that group plugin is not installed, so callers fall back to the native
34
+ * per-entry forms (`ctx.configForms`).
22
35
  */
23
36
  webUiSettings?: {
24
- bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S>;
37
+ bind<S>(spec: UsageSettingsBindSpec<S>): ConfigForm<S>;
25
38
  };
26
39
  }
27
40
  }
@@ -46,6 +46,7 @@ export declare const zh: {
46
46
  'usage.config.title': string;
47
47
  'usage.config.enabled': string;
48
48
  'usage.config.pollIntervalSec': string;
49
+ 'usage.config.saveFailed': string;
49
50
  'usage.bank.title': string;
50
51
  'usage.bank.hint': string;
51
52
  'usage.bank.noUsage': string;
@@ -56,15 +57,11 @@ export declare const zh: {
56
57
  'usage.bank.save': string;
57
58
  'usage.bank.share': string;
58
59
  'usage.bank.drawError': string;
59
- 'usage.sidebar.entry.label': string;
60
- 'usage.sidebar.entry.tooltip': string;
61
- 'usage.sidebar.toggle.collapse': string;
62
- 'usage.sidebar.toggle.expand': string;
63
- 'usage.sidebar.empty': string;
64
- 'usage.sidebar.error': string;
65
- 'usage.sidebar.loading': string;
66
- 'usage.sidebar.balanceLeft': string;
67
- 'usage.sidebar.todayUsage': string;
60
+ 'usage.foot.cost': string;
61
+ 'usage.foot.noData': string;
62
+ 'usage.foot.open': string;
63
+ 'usage.foot.collapse': string;
64
+ 'usage.foot.expand': string;
68
65
  };
69
66
  /** English mirror; every zh key present. */
70
67
  export declare const en: Record<UsageKey, string>;
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Shared JSON body/response helpers for the host route families: one strict
3
3
  * bounded body reader, one lenient bounded body reader, one JSON object
4
- * narrow, and one JSON writer. Previously these were copy-pasted across the
4
+ * narrow, one JSON writer, and the outbound request-init helper that keeps a
5
+ * host fetch body decodable (withIdentityEncoding). Previously the readers
6
+ * were copy-pasted across the
5
7
  * package route files (routes.ts, update-routes.ts, mobile-api.ts, and each
6
8
  * family's route module) with drifting contracts: body caps ranging 4 KiB to
7
9
  * 1 MiB and four distinct overflow behaviors (reject, undefined, null, throw).
@@ -34,6 +36,21 @@ export declare function readJsonBody(req: IncomingMessage, opts?: {
34
36
  }): Promise<unknown | null>;
35
37
  /** Narrow a value to a JSON object, or undefined when it is not one. */
36
38
  export declare function asJsonObject(value: unknown): Record<string, unknown> | undefined;
39
+ /**
40
+ * Request init that asks a remote origin for an uncompressed body.
41
+ *
42
+ * The DSH host boots `@deepseek-ai/dsh-http-proxy`, whose top-level import of
43
+ * the npm `undici` copy replaces the legacy `undici.globalDispatcher.1` slot
44
+ * Node's built-in `fetch()` reads. That cross-major wrapper drops the
45
+ * `content-encoding` header and automatic decompression, so a plain fetch
46
+ * resolves to raw gzip/brotli/zstd bytes — compressed noise no JSON or text
47
+ * consumer can read. Every host fetch that parses a remote body must request
48
+ * identity encoding. Caller headers survive; `accept-encoding` is forced to
49
+ * `identity` because a caller value has no decoder behind it in this host.
50
+ * @param init - the caller's request init (signal, headers, method, ...).
51
+ * @returns a copy with `accept-encoding: identity` merged in.
52
+ */
53
+ export declare function withIdentityEncoding(init?: RequestInit): RequestInit;
37
54
  /**
38
55
  * Write one JSON response. Default headers are the family defaults
39
56
  * (content-type and referrer-policy); caller headers are appended or
@@ -60,11 +60,12 @@ export declare class UsageService {
60
60
  start(): void;
61
61
  /**
62
62
  * Stop timers and flush pending ledger writes. The returned promise
63
- * resolves after the final flush lands, so a successor instance (quick
64
- * disable → enable) can serialize its first load behind it.
63
+ * resolves after the final flush lands, so a successor instance (the Host
64
+ * reloads the profile row on a config change) can serialize its first load
65
+ * behind it.
65
66
  */
66
67
  stop(): Promise<void>;
67
- /** Re-apply options live (settings change); retention shrink prunes now. */
68
+ /** Re-apply options to this running instance; retention shrink prunes now. */
68
69
  applyOptions(options: UsageServiceOptions): void;
69
70
  /** Force one poll now (manual refresh route); joins an in-flight cycle. */
70
71
  refresh(): Promise<void>;
@@ -4,6 +4,12 @@ import z from 'schemastery';
4
4
  import { type UsageServiceOptions } from './host/usage-service.ts';
5
5
  export declare const name = "dsh-usage";
6
6
  export declare const inject: string[];
7
+ /**
8
+ * Family settings namespace this row serves. Since 0.1.7 the settings surface
9
+ * addresses one form per profile entry id and the Host generates this row's
10
+ * page from the Config schema below, so the namespace is only the alias the
11
+ * family settings bridge (dsh-web-settings) resolves onto this row's entry id.
12
+ */
7
13
  export declare const USAGE_SETTINGS_NAMESPACE: SettingsNamespace;
8
14
  export interface Config {
9
15
  enabled?: boolean;
@@ -17,4 +23,12 @@ export interface ResolvedConfig extends UsageServiceOptions {
17
23
  enabled: boolean;
18
24
  }
19
25
  export declare function resolveConfig(config?: Config): ResolvedConfig;
26
+ /**
27
+ * Host body. The plugin's own Config schema above is the settings document the
28
+ * 0.1.7 Host serves for this row; the effective config arrives here as the
29
+ * `config` argument the Loader resolved and the Host reloads the row when the
30
+ * user saves a change, so nothing in this body observes or writes settings.
31
+ * @param ctx - host root context.
32
+ * @param config - the row's resolved configuration.
33
+ */
20
34
  export declare const apply: (ctx: Context, config?: Config) => void;
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-usage",
3
- "description": "Usage statistics plugin for the dsh web GUI: per-provider balance and coding-plan quota detection plus a live token usage ledger, with the current session provider's today usage on the sidebar entry",
4
- "version": "0.3.24",
3
+ "description": "Usage statistics plugin for the dsh web GUI: per-provider balance and coding-plan quota detection plus a live token usage ledger, with a compact price glance card above the sidebar's Settings row",
4
+ "version": "0.4.1",
5
+ "icon": "icon.svg",
5
6
  "type": "module",
6
7
  "engines": {
7
8
  "node": "^22.19.0 || >=24.0.0"
@@ -26,12 +27,13 @@
26
27
  "lib/**/*.d.ts",
27
28
  "lib/**/*.d.ts.map",
28
29
  "src",
29
- "cordis.patch.yml"
30
+ "cordis.patch.yml",
31
+ "icon.svg"
30
32
  ],
31
33
  "license": "Apache-2.0",
32
34
  "dsh": {
33
35
  "engines": {
34
- "dsh": ">=0.1.5-rc.1"
36
+ "dsh": ">=0.1.7-rc.1"
35
37
  },
36
38
  "bundle": {
37
39
  "patch": "./cordis.patch.yml"
@@ -51,22 +53,22 @@
51
53
  "schemastery": "^3.18.0"
52
54
  },
53
55
  "peerDependencies": {
56
+ "@deepseek-ai/dsh": ">=0.1.7-rc.1",
54
57
  "react": "^18.2.0"
55
58
  },
56
59
  "devDependencies": {
57
- "@deepseek-ai/cordis": "^4.0.2",
58
- "@deepseek-ai/dsh-credentials": "^0.1.5-rc.1",
59
- "@deepseek-ai/dsh-client-connection": "^0.1.5-rc.1",
60
- "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
61
- "@deepseek-ai/dsh-client-store": "^0.1.5-rc.1",
62
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.1",
63
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.1",
64
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
65
- "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.1",
66
- "@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
67
- "@deepseek-ai/dsh-session": "^0.1.5-rc.1",
68
- "@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
69
- "@linxin666/dsh-pet": "0.3.24",
60
+ "@deepseek-ai/cordis": "^4.0.4",
61
+ "@deepseek-ai/dsh-credentials": "^0.1.7-rc.1",
62
+ "@deepseek-ai/dsh-client-connection": "^0.1.7-rc.1",
63
+ "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.1",
64
+ "@deepseek-ai/dsh-client-store": "^0.1.7-rc.1",
65
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.1",
66
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.1",
67
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.1",
68
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1",
69
+ "@deepseek-ai/dsh-llm": "^0.1.7-rc.1",
70
+ "@deepseek-ai/dsh-session": "^0.1.7-rc.1",
71
+ "@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
70
72
  "@testing-library/dom": "^10.4.1",
71
73
  "@testing-library/react": "^16.3.2",
72
74
  "@types/node": "^22.20.0",