dsh-token-chart 0.0.0-stage → 0.1.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -2
  3. package/cordis.patch.yml +7 -0
  4. package/icon.svg +7 -0
  5. package/lib/buckets.js +115 -0
  6. package/lib/chunk/echarts.entry.js +25 -0
  7. package/lib/client.echarts.js +58 -0
  8. package/lib/client.js +1743 -0
  9. package/lib/constants.js +22 -0
  10. package/lib/index.js +640 -0
  11. package/lib/paths.js +24 -0
  12. package/lib/prefs.js +54 -0
  13. package/lib/store.js +273 -0
  14. package/lib/token-chart.css +568 -0
  15. package/lib/types/buckets.d.ts +24 -0
  16. package/lib/types/chunk/echarts.entry.d.ts +11 -0
  17. package/lib/types/constants.d.ts +22 -0
  18. package/lib/types/index.d.ts +8 -0
  19. package/lib/types/paths.d.ts +3 -0
  20. package/lib/types/prefs.d.ts +8 -0
  21. package/lib/types/store.d.ts +72 -0
  22. package/lib/types/types.d.ts +230 -0
  23. package/lib/types/update.d.ts +24 -0
  24. package/lib/types.js +10 -0
  25. package/lib/update.js +110 -0
  26. package/locale/en.json +4 -0
  27. package/locale/zh.json +4 -0
  28. package/package.json +110 -4
  29. package/src/buckets.ts +120 -0
  30. package/src/chunk/echarts.entry.ts +27 -0
  31. package/src/client/api.ts +126 -0
  32. package/src/client/charts.tsx +255 -0
  33. package/src/client/chrome.tsx +127 -0
  34. package/src/client/contract.ts +62 -0
  35. package/src/client/drawer.tsx +113 -0
  36. package/src/client/format.ts +36 -0
  37. package/src/client/globals.d.ts +21 -0
  38. package/src/client/index.tsx +227 -0
  39. package/src/client/locales.ts +164 -0
  40. package/src/client/models.ts +76 -0
  41. package/src/client/mount.tsx +312 -0
  42. package/src/client/panel.tsx +152 -0
  43. package/src/client/pill.ts +21 -0
  44. package/src/client/react.ts +12 -0
  45. package/src/client/series.tsx +140 -0
  46. package/src/client/settings.tsx +347 -0
  47. package/src/client/styles/components.less +191 -0
  48. package/src/client/styles/index.less +7 -0
  49. package/src/client/styles/tailwind.css +21 -0
  50. package/src/client/styles/tokens.less +25 -0
  51. package/src/client/table.tsx +153 -0
  52. package/src/client/ui.tsx +87 -0
  53. package/src/constants.ts +26 -0
  54. package/src/index.ts +673 -0
  55. package/src/paths.ts +28 -0
  56. package/src/prefs.ts +61 -0
  57. package/src/store.ts +295 -0
  58. package/src/types.ts +216 -0
  59. package/src/update.ts +106 -0
@@ -0,0 +1,72 @@
1
+ import type { Buckets, SessionEvent, TurnRecord, TurnRow } from './types.ts';
2
+ /** Serialised store file. */
3
+ export interface StoreDocument {
4
+ version: number;
5
+ savedAt: number;
6
+ retentionDays: number;
7
+ sessions: Array<{
8
+ id: string;
9
+ turns: Array<Record<string, unknown>>;
10
+ }>;
11
+ }
12
+ /**
13
+ * Rolling usage store: session → turn → bucket totals, with the same
14
+ * replacement semantics the shipped `tokenUsage` projection uses, so the
15
+ * numbers a reader sees here agree with the product's own "用量" pill.
16
+ */
17
+ /** One session's turns, keyed by turn number. */
18
+ export interface SessionRecord {
19
+ turns: Map<number, TurnRecord>;
20
+ }
21
+ export declare class UsageStore {
22
+ #private;
23
+ /** Session id → its turns. */
24
+ readonly sessions: Map<string, SessionRecord>;
25
+ /** One session record, created on demand. */
26
+ session(sessionId: string): SessionRecord;
27
+ /**
28
+ * Fold one committed session event into the store.
29
+ *
30
+ * Mirrors `@deepseek-ai/dsh-token-meter`'s `tokenUsage` projection — each
31
+ * settlement contributes the last usage sample in its stream, a resample of
32
+ * the same slot replaces rather than adds, and `llm/retry-started` opens a new
33
+ * slot so a retried attempt bills again — but stores one bucket set per
34
+ * `(step, attempt)` instead of a single running slot. That difference is what
35
+ * makes the fold idempotent: folding the same log twice yields the same
36
+ * totals, which the product's single-slot fold does not survive.
37
+ * @param sessionId - the session the event was committed to.
38
+ * @param event - one durable session event.
39
+ * @returns whether the store changed.
40
+ */
41
+ apply(sessionId: string, event: SessionEvent): boolean;
42
+ /**
43
+ * The session's whole-window totals, folded from the per-slot buckets.
44
+ *
45
+ * This is deliberately not "the sum of the turn rows a reader sees": it is
46
+ * one fold over every settled (turn, step, attempt) of the session, which is
47
+ * the same rule the product's own `tokenUsage` projection applies. The two
48
+ * agree by construction, but the intent — and any future change — belongs
49
+ * here, not in the presentation layer.
50
+ * @param sessionId - the session to total.
51
+ */
52
+ sessionTotals(sessionId: string): Buckets;
53
+ /** Drop turns older than the retention window and empty sessions. */
54
+ prune(now?: number): boolean;
55
+ /** Every turn of every session inside the retention window, oldest first. */
56
+ turns(): TurnRow[];
57
+ /** Per-turn rows of one session, ordered by turn. */
58
+ sessionTurns(sessionId: string): TurnRow[];
59
+ hasSession(sessionId: string): boolean;
60
+ /** Serialize the retention window to plain JSON. */
61
+ toJSON(): StoreDocument;
62
+ /**
63
+ * Restore a serialized store, dropping anything outside the window.
64
+ *
65
+ * A document from an older schema is discarded rather than interpreted: the
66
+ * activation-time backfill rebuilds it from the session logs, and guessing at
67
+ * a superseded layout is exactly how a total drifts.
68
+ * @param document - the parsed store file.
69
+ * @returns whether the document was accepted.
70
+ */
71
+ load(document: unknown): boolean;
72
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * The contract both halves share: preference shape, usage buckets, and the
3
+ * JSON reports the browser half consumes.
4
+ *
5
+ * The browser half imports the same file, so nothing here may reference Node
6
+ * or DOM globals.
7
+ *
8
+ * @module dsh-token-chart/types
9
+ */
10
+ /** Provider-reported usage for one settlement, already normalised. */
11
+ export interface Buckets {
12
+ input: number;
13
+ output: number;
14
+ cacheRead: number;
15
+ cacheWrite: number;
16
+ }
17
+ /** Buckets plus the two derived numbers every surface shows. */
18
+ export interface Derived extends Buckets {
19
+ /** `input + cacheRead + cacheWrite` — what the provider bills as prompt. */
20
+ cache: number;
21
+ /** billed input + output. */
22
+ total: number;
23
+ /** `cacheRead / (input + cacheRead + cacheWrite)`. */
24
+ cacheHitRate: number;
25
+ }
26
+ export type MetricId = 'input' | 'output' | 'cache' | 'total';
27
+ export type ChartId = 'bar' | 'line' | 'pie' | 'table' | 'multi-bar' | 'multi-line' | 'bar-line';
28
+ /** User-facing preferences, stored on the Host and mirrored in the page. */
29
+ export interface Prefs {
30
+ sessionChart: ChartId;
31
+ dailyChart: ChartId;
32
+ sessionMetrics: MetricId[];
33
+ dailyMetrics: MetricId[];
34
+ showTable: boolean;
35
+ showCacheHitRate: boolean;
36
+ interceptUsageButton: boolean;
37
+ }
38
+ /** One usage chunk as a provider reports it. */
39
+ export interface UsageSample {
40
+ inputTokens?: number;
41
+ outputTokens?: number;
42
+ cacheReadTokens?: number;
43
+ cacheWriteTokens?: number;
44
+ totalTokens?: number;
45
+ }
46
+ /** One committed session event, structurally narrowed to what the fold reads. */
47
+ export interface SessionEvent {
48
+ type?: string;
49
+ seq?: number;
50
+ time?: number;
51
+ data?: {
52
+ turn?: number;
53
+ step?: number;
54
+ stream?: readonly unknown[];
55
+ usage?: UsageSample;
56
+ message?: {
57
+ source?: {
58
+ provider?: string;
59
+ model?: string;
60
+ };
61
+ };
62
+ [key: string]: unknown;
63
+ };
64
+ }
65
+ /** Per-turn store record: one bucket slot per settled `(step, attempt)`. */
66
+ export interface TurnRecord {
67
+ turn: number;
68
+ time: number;
69
+ lastTime: number;
70
+ slots: Map<string, Buckets>;
71
+ attempts: Map<number, number>;
72
+ totals: Buckets;
73
+ routes: string[];
74
+ }
75
+ /** A turn as the reports expose it (slot bookkeeping stays inside the store). */
76
+ export interface TurnRow {
77
+ sessionId: string;
78
+ turn: number;
79
+ time: number;
80
+ lastTime: number;
81
+ totals: Buckets;
82
+ routes: string[];
83
+ }
84
+ /** One calendar day of the daily panel. */
85
+ export interface DayBucket extends Derived {
86
+ key: string;
87
+ label: string;
88
+ turns: number;
89
+ sessions: number;
90
+ }
91
+ /** One point of the "today" chart: a single turn, in time order. */
92
+ export interface Point extends Derived {
93
+ time: number;
94
+ label: string;
95
+ day: string;
96
+ sessionId: string;
97
+ turn: number;
98
+ }
99
+ /** Cross-check of our fold against the product's own `tokenUsage` projection. */
100
+ export interface CrossCheck {
101
+ source: 'sessionProjection' | 'unavailable';
102
+ total?: number;
103
+ matches?: boolean;
104
+ }
105
+ /** `GET /session` payload. */
106
+ export interface SessionReport {
107
+ sessionId: string;
108
+ turns: Array<Derived & {
109
+ turn: number;
110
+ time: number;
111
+ label: string;
112
+ routes: string[];
113
+ }>;
114
+ totals: Derived & {
115
+ turns: number;
116
+ firstTime: number;
117
+ lastTime: number;
118
+ routes: string[];
119
+ };
120
+ crossCheck: CrossCheck;
121
+ }
122
+ /** `GET /overview` payload. */
123
+ export interface OverviewReport {
124
+ range: string;
125
+ days: number;
126
+ floor: number;
127
+ generatedAt: number;
128
+ retentionDays: number;
129
+ buckets: DayBucket[];
130
+ points: Point[];
131
+ totals: Derived & {
132
+ turns: number;
133
+ sessions: number;
134
+ };
135
+ }
136
+ /** `GET /version` payload. */
137
+ export interface VersionReport {
138
+ name: string;
139
+ version: string;
140
+ registry: string;
141
+ status: 'ok' | 'unpublished' | 'error' | 'unknown';
142
+ latest: string | null;
143
+ updateAvailable: boolean;
144
+ checkedAt: number;
145
+ install: {
146
+ kind: 'registry' | 'local' | 'unknown';
147
+ spec: string;
148
+ };
149
+ run: UpdateRun;
150
+ }
151
+ /** State of the one update attempt this Host may be running. */
152
+ export interface UpdateRun {
153
+ phase: 'idle' | 'running' | 'done' | 'failed' | 'manual';
154
+ startedAt: number;
155
+ finishedAt: number;
156
+ message: string;
157
+ error: string;
158
+ }
159
+ /** Uniform success/failure envelope of every route. */
160
+ export type ApiEnvelope<T> = {
161
+ ok: true;
162
+ value: T;
163
+ } | {
164
+ ok: false;
165
+ error: {
166
+ code: string;
167
+ message: string;
168
+ command?: string;
169
+ };
170
+ };
171
+ /** Logger face a Host plugin may rely on. */
172
+ export interface Logger {
173
+ info?: (...args: unknown[]) => void;
174
+ warn?: (...args: unknown[]) => void;
175
+ }
176
+ /** `ctx.sessionQuery` — read committed session logs for the backfill. */
177
+ export interface SessionQueryLike {
178
+ listSessions: () => Promise<Array<{
179
+ header?: {
180
+ id?: string;
181
+ };
182
+ }>>;
183
+ readSession: (sessionId: string) => Promise<{
184
+ events?: SessionEvent[];
185
+ } | undefined>;
186
+ }
187
+ /** `ctx.sessions` — live Session objects, needed by the projection cross-check. */
188
+ export interface SessionsLike {
189
+ get: (sessionId: string) => unknown;
190
+ }
191
+ /** `ctx.sessionProjections` — read the product's own fold for one session. */
192
+ export interface SessionProjectionsLike {
193
+ stateOf: (session: unknown, key: string) => {
194
+ totals?: Record<string, number>;
195
+ } | undefined;
196
+ }
197
+ /** `ctx.pluginManager` — the install path shared with `dsh plugin add`. */
198
+ export interface PluginManagerLike {
199
+ installBundle: (spec: string, options?: Record<string, unknown>) => Promise<{
200
+ application?: string;
201
+ } | undefined>;
202
+ }
203
+ /** Minimal HTTP request/response faces the routes need. */
204
+ export interface HttpRequest {
205
+ method?: string;
206
+ url?: string;
207
+ [Symbol.asyncIterator](): AsyncIterator<Uint8Array>;
208
+ }
209
+ export interface HttpResponse {
210
+ statusCode: number;
211
+ setHeader: (name: string, value: string) => void;
212
+ end: (body?: string) => void;
213
+ }
214
+ /** Where the Host registers a route. */
215
+ export interface WebServerLike {
216
+ register: (route: {
217
+ kind: 'prefix' | 'exact';
218
+ path: string;
219
+ handler: (req: HttpRequest, res: HttpResponse) => void | Promise<void>;
220
+ }) => () => void;
221
+ }
222
+ /** The Cordis context surface this plugin uses, typed structurally. */
223
+ export interface HostContext {
224
+ logger?: Logger;
225
+ effect: (callback: () => unknown, description?: string) => unknown;
226
+ on: (name: string, callback: (...args: any[]) => void) => () => void;
227
+ inject: (deps: string[], callback: (services: Record<string, any>) => unknown) => unknown;
228
+ pluginManager?: PluginManagerLike;
229
+ webServer?: WebServerLike;
230
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Compare two dotted versions, tolerating a prerelease suffix.
3
+ *
4
+ * `1.2.0` outranks `1.2.0-rc.1`, and a missing numeric segment counts as 0, so
5
+ * the check never offers a downgrade for tags like `0.2.0-rc.2` + `0.2.0`.
6
+ * @param left - the version in hand.
7
+ * @param right - the candidate version.
8
+ * @returns -1, 0 or 1 for left &lt; right, equal, left &gt; right.
9
+ */
10
+ export declare function compareVersions(left: unknown, right: unknown): -1 | 0 | 1;
11
+ /** The registry the profile installs from, read from its `.npmrc` when present. */
12
+ export declare function profileRegistry(): string;
13
+ /** The package's own manifest, read next to this module. */
14
+ export declare function readOwnManifest(): Promise<{
15
+ name: string;
16
+ version: string;
17
+ }>;
18
+ /**
19
+ * Ask a registry for a package's `latest` dist-tag.
20
+ * @param packageName - the package to look up.
21
+ * @param registry - registry base URL.
22
+ * @returns the version string, or undefined when the registry has no such package.
23
+ */
24
+ export declare function fetchLatestVersion(packageName: string, registry?: string): Promise<string | undefined>;
package/lib/types.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The contract both halves share: preference shape, usage buckets, and the
3
+ * JSON reports the browser half consumes.
4
+ *
5
+ * The browser half imports the same file, so nothing here may reference Node
6
+ * or DOM globals.
7
+ *
8
+ * @module dsh-token-chart/types
9
+ */
10
+ export {};
package/lib/update.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Version comparison and the registry lookup behind the update check.
3
+ *
4
+ * @module dsh-token-chart/update
5
+ */
6
+ import { readFileSync } from 'node:fs';
7
+ import path from 'node:path';
8
+ import { readFile } from 'node:fs/promises';
9
+ import { DEFAULT_REGISTRY, PACKAGE_NAME, UPDATE_FETCH_TIMEOUT_MS } from './constants.js';
10
+ /**
11
+ * Compare two dotted versions, tolerating a prerelease suffix.
12
+ *
13
+ * `1.2.0` outranks `1.2.0-rc.1`, and a missing numeric segment counts as 0, so
14
+ * the check never offers a downgrade for tags like `0.2.0-rc.2` + `0.2.0`.
15
+ * @param left - the version in hand.
16
+ * @param right - the candidate version.
17
+ * @returns -1, 0 or 1 for left &lt; right, equal, left &gt; right.
18
+ */
19
+ export function compareVersions(left, right) {
20
+ const parse = (value) => {
21
+ const text = String(value ?? '')
22
+ .trim()
23
+ .replace(/^v/, '');
24
+ const [core, ...rest] = text.split('-');
25
+ const numbers = core.split('.').map((part) => {
26
+ const parsed = Number.parseInt(part, 10);
27
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
28
+ });
29
+ while (numbers.length < 3)
30
+ numbers.push(0);
31
+ return { numbers, prerelease: rest.join('-') };
32
+ };
33
+ const a = parse(left);
34
+ const b = parse(right);
35
+ for (let index = 0; index < 3; index += 1) {
36
+ if (a.numbers[index] !== b.numbers[index])
37
+ return a.numbers[index] < b.numbers[index] ? -1 : 1;
38
+ }
39
+ if (a.prerelease === b.prerelease)
40
+ return 0;
41
+ if (a.prerelease === '')
42
+ return 1;
43
+ if (b.prerelease === '')
44
+ return -1;
45
+ return a.prerelease < b.prerelease ? -1 : 1;
46
+ }
47
+ /** The registry the profile installs from, read from its `.npmrc` when present. */
48
+ export function profileRegistry() {
49
+ const dir = process.env.DSH_PROFILE_DIR;
50
+ if (typeof dir !== 'string' || dir.trim() === '')
51
+ return DEFAULT_REGISTRY;
52
+ try {
53
+ const text = readFileSync(path.join(dir.trim(), '.npmrc'), 'utf8');
54
+ const match = /^\s*registry\s*=\s*(\S+)\s*$/m.exec(text);
55
+ if (match === null)
56
+ return DEFAULT_REGISTRY;
57
+ return match[1].endsWith('/') ? match[1] : `${match[1]}/`;
58
+ }
59
+ catch {
60
+ return DEFAULT_REGISTRY;
61
+ }
62
+ }
63
+ /** The package's own manifest, read next to this module. */
64
+ export async function readOwnManifest() {
65
+ // The Host artifact lives in `lib/`, one level below the package root, while a
66
+ // source-mode run sits in `src/`. Try the package root first, then the module's
67
+ // own directory, so neither layout silently falls back to a placeholder version.
68
+ const candidates = [new URL('../package.json', import.meta.url), new URL('./package.json', import.meta.url)];
69
+ for (const url of candidates) {
70
+ try {
71
+ const parsed = JSON.parse(await readFile(url, 'utf8'));
72
+ if (typeof parsed.version === 'string' && parsed.version !== '') {
73
+ return { name: String(parsed.name ?? PACKAGE_NAME), version: parsed.version };
74
+ }
75
+ }
76
+ catch {
77
+ /* try the next candidate */
78
+ }
79
+ }
80
+ return { name: PACKAGE_NAME, version: '0.0.0' };
81
+ }
82
+ /**
83
+ * Ask a registry for a package's `latest` dist-tag.
84
+ * @param packageName - the package to look up.
85
+ * @param registry - registry base URL.
86
+ * @returns the version string, or undefined when the registry has no such package.
87
+ */
88
+ export async function fetchLatestVersion(packageName, registry = DEFAULT_REGISTRY) {
89
+ const base = registry.endsWith('/') ? registry : `${registry}/`;
90
+ const url = `${base}${encodeURIComponent(packageName).replace('%40', '@')}`;
91
+ const controller = new AbortController();
92
+ const timer = setTimeout(() => controller.abort(), UPDATE_FETCH_TIMEOUT_MS);
93
+ try {
94
+ const response = await fetch(url, {
95
+ signal: controller.signal,
96
+ headers: { accept: 'application/vnd.npm.install-v1+json, application/json' },
97
+ });
98
+ if (response.status === 404)
99
+ return undefined;
100
+ if (!response.ok)
101
+ throw new Error(`registry answered ${response.status}`);
102
+ const body = (await response.json());
103
+ const latest = body['dist-tags']?.latest;
104
+ return typeof latest === 'string' && latest !== '' ? latest : undefined;
105
+ }
106
+ finally {
107
+ clearTimeout(timer);
108
+ }
109
+ }
110
+ /* ------------------------------------------------------------------ paths */
package/locale/en.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Token usage charts",
3
+ "description": "Token usage statistics and visualization: a per-session drawer, a today / last 3 days / last 7 days panel, and configurable chart forms."
4
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Token 用量图表",
3
+ "description": "统计并可视化 Token 用量:会话用量抽屉、今天 / 近 3 天 / 近 7 天面板,以及可自由配置的图表形式。"
4
+ }
package/package.json CHANGED
@@ -1,6 +1,112 @@
1
1
  {
2
2
  "name": "dsh-token-chart",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Token usage statistics and chart visualization for DeepSeek Harness: per-session drawer, today / last 3 / last 7 days panel, and chart configuration.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./client": "./lib/client.js",
14
+ "./cordis.patch.yml": "./cordis.patch.yml",
15
+ "./package.json": "./package.json",
16
+ "./locale/*.json": "./locale/*.json"
17
+ },
18
+ "files": [
19
+ "lib",
20
+ "src",
21
+ "cordis.patch.yml",
22
+ "icon.svg",
23
+ "locale",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "dsh": {
28
+ "manifestVersion": 1,
29
+ "bundle": {
30
+ "patch": "./cordis.patch.yml"
31
+ },
32
+ "client": {
33
+ "platform": "web",
34
+ "inject": [
35
+ "@deepseek-ai/dsh-client-locale",
36
+ "@deepseek-ai/dsh-client-ui-slots",
37
+ "@deepseek-ai/dsh-client-ui-conversation",
38
+ "@deepseek-ai/dsh-client-ui-sidebar",
39
+ "@deepseek-ai/dsh-client-ui-sidebar-right",
40
+ "@deepseek-ai/dsh-client-ui-settings"
41
+ ]
42
+ }
43
+ },
44
+ "meta": {
45
+ "title": "Token 用量图表",
46
+ "description": "统计并可视化 Token 用量:会话抽屉、今天 / 近 3 天 / 近 7 天面板与图表配置。"
47
+ },
48
+ "icon": "./icon.svg",
49
+ "engines": {
50
+ "node": ">=20",
51
+ "dsh": ">=0.2.0-rc.2"
52
+ },
53
+ "author": "lugezuishuai",
54
+ "license": "MIT",
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/lugezuishuai/dsh-token-chart.git"
58
+ },
59
+ "homepage": "https://github.com/lugezuishuai/dsh-token-chart#readme",
60
+ "bugs": {
61
+ "url": "https://github.com/lugezuishuai/dsh-token-chart/issues"
62
+ },
63
+ "keywords": [
64
+ "deepseek",
65
+ "harness",
66
+ "dsh",
67
+ "dsh-plugin",
68
+ "token",
69
+ "usage",
70
+ "dashboard",
71
+ "echarts"
72
+ ],
73
+ "publishConfig": {
74
+ "registry": "https://registry.npmjs.org/",
75
+ "access": "public"
76
+ },
77
+ "scripts": {
78
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
79
+ "clean": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\"",
80
+ "build": "npm run clean && npm run build:host && npm run build:styles && npm run build:client && npm run build:chunk",
81
+ "build:host": "tsc -p tsconfig.json",
82
+ "build:styles": "node scripts/build-styles.mjs",
83
+ "build:client": "node scripts/build-client.mjs",
84
+ "build:chunk": "node scripts/build-chunk.mjs",
85
+ "prepare": "npm run build",
86
+ "prepack": "npm run build && node scripts/preflight.mjs",
87
+ "prepublishOnly": "node scripts/preflight.mjs",
88
+ "preflight": "node scripts/preflight.mjs",
89
+ "check": "npm run typecheck && npm run build && node scripts/preflight.mjs && npm run test:host",
90
+ "test": "vitest run",
91
+ "test:host": "vitest run --project host",
92
+ "test:client": "vitest run --project client",
93
+ "test:watch": "vitest",
94
+ "format": "prettier --write 'src/**/*.{ts,tsx}' 'tests/**/*.mjs' 'scripts/**/*.mjs'"
95
+ },
96
+ "devDependencies": {
97
+ "@tailwindcss/cli": "^4.3.3",
98
+ "@types/node": "^26.6.4",
99
+ "@types/react": "^18.3.31",
100
+ "@types/react-dom": "^18.3.7",
101
+ "echarts": "^6.1.0",
102
+ "esbuild": "^0.28.2",
103
+ "jsdom": "^30.1.1",
104
+ "less": "^4.9.1",
105
+ "prettier": "^3.9.9",
106
+ "react": "^18.3.1",
107
+ "react-dom": "^18.3.1",
108
+ "tailwindcss": "^4.3.3",
109
+ "typescript": "^7.0.2",
110
+ "vitest": "^5.0.3"
111
+ }
112
+ }