@nexus-aethra/dshell-usage 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/lib/client/heatmap.js +96 -0
- package/lib/client/heatmap.js.map +1 -0
- package/lib/client/index.js +36 -0
- package/lib/client/index.js.map +1 -0
- package/lib/client/locales.js +95 -0
- package/lib/client/locales.js.map +1 -0
- package/lib/client/nav-icon.js +88 -0
- package/lib/client/nav-icon.js.map +1 -0
- package/lib/client/page.js +289 -0
- package/lib/client/page.js.map +1 -0
- package/lib/client.js +1146 -0
- package/lib/client.js.map +1 -0
- package/lib/index-store.js +250 -0
- package/lib/index-store.js.map +1 -0
- package/lib/index.js +41 -0
- package/lib/index.js.map +1 -0
- package/lib/protocol.js +9 -0
- package/lib/protocol.js.map +1 -0
- package/lib/route.js +63 -0
- package/lib/route.js.map +1 -0
- package/lib/scan.js +187 -0
- package/lib/scan.js.map +1 -0
- package/lib/types/client/heatmap.d.ts +69 -0
- package/lib/types/client/heatmap.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +14 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/locales.d.ts +62 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/nav-icon.d.ts +28 -0
- package/lib/types/client/nav-icon.d.ts.map +1 -0
- package/lib/types/client/page.d.ts +18 -0
- package/lib/types/client/page.d.ts.map +1 -0
- package/lib/types/index-store.d.ts +87 -0
- package/lib/types/index-store.d.ts.map +1 -0
- package/lib/types/index.d.ts +19 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/protocol.d.ts +115 -0
- package/lib/types/protocol.d.ts.map +1 -0
- package/lib/types/route.d.ts +19 -0
- package/lib/types/route.d.ts.map +1 -0
- package/lib/types/scan.d.ts +94 -0
- package/lib/types/scan.d.ts.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","useCallback","useRef","useMemo","_jsxs","_jsx"],"sources":["client/locales.js","../../../../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.production.min.js","../../../../node_modules/.pnpm/react@18.3.1/node_modules/react/jsx-runtime.js","../../std/lib/contracts.js","client/heatmap.js","client/page.js","client/nav-icon.js","client/index.js"],"sourcesContent":["/**\n * `dshellUsage` namespace dictionaries, and the namespace's declaration.\n *\n * The namespace merge lives with its key set so that any module naming\n * `TranslateNS<'dshellUsage'>` or `PropsLocale<'dshellUsage'>` needs only this\n * file. Keys are lowercase and dotted: `nav.*` (the settings navigation row),\n * `range.*` (the window selector), `chart.*` (the two charts' captions),\n * `table.*` (the route breakdown), and `state.*` (empty, busy, freshness, and\n * the load failure).\n */\n/** Simplified Chinese dictionary and key-set source of truth. */\nexport const zh = {\n // The Settings navigation row this page owns.\n 'nav': '用量',\n 'title': '模型 token 消耗',\n 'subtitle': '按模型汇总每个会话上报的用量。索引建在 dshell 自己的数据目录里,首次打开会扫一遍全部历史会话,之后只补新增的部分。',\n // The window selector. `{days}` is the day count.\n 'range.7': '近 7 天',\n 'range.30': '近 30 天',\n 'range.all': '全部',\n 'range.label': '时间范围',\n 'rebuild': '重新聚合',\n 'rebuild.busy': '正在聚合…',\n 'chart.heatmap': '每日热力(近 26 周内)',\n 'chart.heatmapDay': '{day} · {total} tok · {turns} 次调用',\n 'chart.heatmapLess': '少',\n 'chart.heatmapMore': '多',\n 'chart.heatmapPeak': '最忙一天 {total} tok',\n 'chart.heatmapTruncated': '只画最近 26 周',\n 'chart.curve': '每日消耗(按模型)',\n 'chart.pie': '模型占比',\n 'summary.total': '合计',\n 'summary.uncached': '未缓存输入',\n 'summary.output': '输出',\n 'summary.cacheRead': '缓存读',\n 'summary.turns': '调用',\n 'summary.hitRate': '缓存命中率',\n 'table.title': '按模型明细',\n 'table.model': '模型',\n 'table.input': '输入',\n 'table.output': '输出',\n 'table.cacheRead': '缓存读',\n 'table.cacheWrite': '缓存写',\n 'table.turns': '调用',\n 'table.total': '合计',\n 'state.empty.title': '还没有可统计的用量',\n 'state.empty.body': '索引里没有任何一条带用量上报的模型调用。跑一次 agent 会话后再点「重新聚合」。',\n 'state.freshness': '索引更新于 {day}',\n 'state.skipped': '有 {count} 个会话的日志读不了,未计入(详情见悬停提示)。',\n 'state.scanned': '本次扫描列出 {sessions} 个会话,读取了 {read} 个,命中 {turns} 次调用。',\n 'state.error': '读取用量失败:{reason}',\n 'state.loading': '正在读取索引…',\n};\n/** English dictionary, keyed by the same set. */\nexport const en = {\n 'nav': 'Usage',\n 'title': 'Model token usage',\n 'subtitle': 'Per-model totals folded from the usage every session reports. The index lives in dshell\\'s own data directory: the first visit scans every session once, and later visits add only what is new.',\n 'range.7': 'Last 7 days',\n 'range.30': 'Last 30 days',\n 'range.all': 'All time',\n 'range.label': 'Range',\n 'rebuild': 'Rebuild',\n 'rebuild.busy': 'Rebuilding…',\n 'chart.heatmap': 'Daily heatmap (last 26 weeks)',\n 'chart.heatmapDay': '{day} · {total} tok · {turns} calls',\n 'chart.heatmapLess': 'Less',\n 'chart.heatmapMore': 'More',\n 'chart.heatmapPeak': 'Busiest day {total} tok',\n 'chart.heatmapTruncated': 'Showing the last 26 weeks',\n 'chart.curve': 'Daily usage by model',\n 'chart.pie': 'Share by model',\n 'summary.total': 'Total',\n 'summary.uncached': 'Uncached input',\n 'summary.output': 'Output',\n 'summary.cacheRead': 'Cache read',\n 'summary.turns': 'Calls',\n 'summary.hitRate': 'Cache hit rate',\n 'table.title': 'By model',\n 'table.model': 'Model',\n 'table.input': 'Input',\n 'table.output': 'Output',\n 'table.cacheRead': 'Cache read',\n 'table.cacheWrite': 'Cache write',\n 'table.turns': 'Calls',\n 'table.total': 'Total',\n 'state.empty.title': 'No usage to count yet',\n 'state.empty.body': 'The index holds no model call that reported usage. Run an agent session, then press Rebuild.',\n 'state.freshness': 'Index updated {day}',\n 'state.skipped': '{count} session logs could not be read and are not counted (hover for the reason).',\n 'state.scanned': 'This scan listed {sessions} sessions, read {read}, and counted {turns} calls.',\n 'state.error': 'Reading usage failed: {reason}',\n 'state.loading': 'Reading the index…',\n};\n//# sourceMappingURL=locales.js.map","/**\n * @license React\n * react-jsx-runtime.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n'use strict';var f=require(\"react\"),k=Symbol.for(\"react.element\"),l=Symbol.for(\"react.fragment\"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:!0,ref:!0,__self:!0,__source:!0};\nfunction q(c,a,g){var b,d={},e=null,h=null;void 0!==g&&(e=\"\"+g);void 0!==a.key&&(e=\"\"+a.key);void 0!==a.ref&&(h=a.ref);for(b in a)m.call(a,b)&&!p.hasOwnProperty(b)&&(d[b]=a[b]);if(c&&c.defaultProps)for(b in a=c.defaultProps,a)void 0===d[b]&&(d[b]=a[b]);return{$$typeof:k,type:c,key:e,ref:h,props:d,_owner:n.current}}exports.Fragment=l;exports.jsx=q;exports.jsxs=q;\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-jsx-runtime.production.min.js');\n} else {\n module.exports = require('./cjs/react-jsx-runtime.development.js');\n}\n","/**\n * Every dshell wire contract: the `/api/dshell/*` paths and the shapes that\n * cross them.\n *\n * Layering rule for this file, and for this package as a whole: it declares\n * FACTS, not behaviour. No dsh packages, no runtime state, and no imports except\n * types from this package's own sibling modules — so both halves of every plugin\n * (host route and browser face) can import the same declaration, and a contract\n * change is a compile error on both sides instead of a silent drift. The browser\n * faces used to restate these shapes by hand precisely because there was nowhere\n * shared to put them.\n *\n * Sections below were moved verbatim from the packages' own protocol modules,\n * which now re-export from here; the package-local headers are kept because they\n * carry the reasoning for each shape.\n */\n/**\n * The file navigator's wire vocabulary, shared by the host route and the\n * browser face.\n *\n * Deliberately free of value imports: the browser half imports this module, and\n * anything it pulls in would be bundled into `client.js`.\n *\n * A path on the wire is always the CANONICAL absolute path **in the session's\n * own execution world** — the host path for a local session, the device path\n * for a device-bound one. That is the string the client shows, splits into\n * segments, trims for `..`, and hands back to list again, so both ends agree on\n * one spelling without either knowing which world produced it.\n */\n/** The exact `/api` path the file navigator talks to. */\nexport const DSHELL_FILES_PATH = '/api/dshell/files';\n// ─── files — the two-world transfer view ───\n// moved from packages/dshell/files/src/transfer-protocol.ts\n/**\n * The file-transfer wire vocabulary: one device session's two file trees, and\n * the copies between them.\n *\n * Separate from `./protocol.ts` because it is a different route with a\n * different subject. The navigator's route answers about ONE world (the\n * session's); this one is about the pair — this machine and the device the\n * session runs on — and about work that outlives one request.\n *\n * Deliberately free of value imports: the browser face imports this module, and\n * anything it pulled in would be bundled into `client.js`. `Buffer`-style host\n * types are not needed on the browser side at all.\n */\n/** The exact `/api` path the transfer view talks to. */\nexport const DSHELL_TRANSFER_PATH = '/api/dshell/transfer';\n// ─── buffer — the cross-session pipe: links, tickets, grants, transfers ───\n// moved from packages/dshell/buffer/src/protocol.ts\n/**\n * The buffer's wire vocabulary, shared by the host route and the browser face.\n *\n * Deliberately free of value imports: the browser half imports this module, and\n * anything it pulls in would be bundled into `client.js`. Identifiers that the\n * host validates against (a request `action`, a ticket `state`) are plain\n * strings here and narrowed on the host side.\n *\n * Session ids travel as plain strings rather than the branded `SessionId`\n * because this is a JSON boundary; the host converts at the edges.\n */\n/** The exact `/api` path the pipe UI talks to. */\nexport const DSHELL_BUFFER_PATH = '/api/dshell/buffer';\n/** The plugin name every buffer-authored message carries as its provenance. */\nexport const BUFFER_PLUGIN = 'dshell-buffer';\n/** States after which a ticket is settled and never changes again. */\nexport const SETTLED_STATES = ['done', 'failed', 'timeout', 'cancelled'];\n// ─── ssh — devices, bindings, their requests/responses ───\n// moved from packages/dshell/ssh/src/protocol.ts\n/**\n * dshell-ssh wire — one exact `/api/dshell/ssh` route, plus the device shapes\n * both halves agree on.\n *\n * The route carries what must not travel through the settings document: the\n * private keys themselves. A device's durable record is small JSON; its key is\n * a separate file written 0600 under `$DSH_HOME/dshell/ssh/keys/`.\n *\n * This module is imported by the browser half through the package's\n * `./protocol` subpath, so it must stay free of value imports: anything it\n * pulls in would be bundled into the client.\n */\n/** Exact `/api` route path owned by the SSH device registry. */\nexport const DSHELL_SSH_PATH = '/api/dshell/ssh';\n/** Settings namespace owned by this plugin; also the device card's slot key. */\nexport const SSH_SETTINGS_NAMESPACE = 'dshell-ssh';\n// ─── workspace — the session delete route ───\n// moved from packages/dshell/workspace/src/protocol.ts\n/**\n * dshell session-panel wire — design 4.7 follow-up.\n *\n * This route carries ONE dshell-only concern now: the history purge. Its\n * companion, the archive tag, has moved to upstream and left this contract:\n *\n * - The **archive set is upstream's.** `workspaceRegistry.archiveSession` /\n * `unarchiveSession` own it, the stock `workspace-controller` row (enabled in\n * this profile) exposes both as remote commands and pushes a\n * `{ type: 'archived' }` increment, and `ui-settings-unarchive-sessions` —\n * the settings page this profile renders — is its restore surface. dshell\n * supplies the registry's storage half and nothing else; there is no dshell\n * archive UI and no dshell archive route. See packages/dshell/workspace/\n * src/index.ts.\n * - The **history purge still has no upstream.** `SessionPersistence` is\n * append-only — create/open/stat/list and no delete — `session-controller`\n * has no delete command, and the jsonl lease documents \"Release never\n * removes\". `api-session/removed` is a `session/disposed` relay that\n * upstream's own client turns into a snapshot flag, deleting nothing.\n *\n * The purge travels over dshell's own exact route on the shared `/api` channel\n * rather than through the Typert Remote table (whose client artifacts are\n * generated from dsh's own packages).\n *\n * The path sits below the shared channel exactly like file-upload's: the\n * physical carrier applies dsh's trust and authentication policy before the\n * handler ever runs, so the route itself does no authorization.\n *\n * Shared by both halves of the package, so the constant and the shapes live\n * in one file that neither the host nor the client bundle owns.\n */\n/** Exact `/api` route path owned by the dshell session panel. */\nexport const DSHELL_SESSIONS_PATH = '/api/dshell/sessions';\n// ─── terminal-bridge — the shell's history read ───\n// moved from packages/dshell/terminal-bridge/src/route.ts\nexport const DSHELL_PTY_PATH = '/api/dshell/pty';\n// ─── terminal-bridge — the frame stream over the shared API channel ───\n/**\n * Downstream half of the frame stream: a long-lived GET whose response body is\n * newline-delimited JSON, one bridge frame per line.\n *\n * The wire itself is unchanged — these are the very frames the ws upgrade\n * carries — but a Response body is reachable from any composition the\n * `connection` service is in, while `registerUpgrade` needs `webServer`, which\n * the desktop shell deliberately does not compose. Identity is explicit because\n * an HTTP stream has no socket to key on: both halves carry the client's own\n * `clientId`, and the GET carries the bind (`sessionId` + `stream`) that a ws\n * would have sent as a frame.\n */\nexport const DSHELL_STREAM_PATH = '/api/dshell/stream';\n/**\n * Upstream half: one POST per control frame (`input`, `resize`, `signal`,\n * `agent-open`, `reconnect`), identified by the same `clientId` the GET opened\n * with. Answers with no body; the stream is the only place results appear.\n */\nexport const DSHELL_STREAM_SEND_PATH = '/api/dshell/stream/send';\n// ─── dshell's own data root, and the directory browser that picks it ───\n/**\n * Environment variable holding the directory dshell keeps its own files under.\n *\n * dshell derives every one of its paths from a harness home — `DSH_HOME` when\n * the deployment sets one, `~/.dsh` otherwise — and puts `dshell/` (device\n * registry, buffer state, session tags, mount points) and `dshell-pty/`\n * (transcripts, their timelines, the history database) beneath it.\n *\n * This variable overrides that home for dshell ALONE. The distinct name is the\n * whole point: `DSH_HOME` belongs to the harness and moving it moves sessions,\n * settings and storage too, while a reader who wants their shell transcripts on\n * another disk is not asking to relocate dsh. Two deployments set it — the\n * settings card (`dataDir` in the `dshell` namespace, applied by the host half\n * at start) and a user or unit file exporting it directly, which is what the\n * packaged builds use.\n *\n * It is read at each path resolution rather than cached, for the same reason\n * dsh's own resolution is: the value is a fact about the process, set once\n * before anything asks.\n */\nexport const DSHELL_HOME_ENV = 'DSHELL_HOME';\n/**\n * The bundle package this workspace publishes.\n *\n * Named here because the Plugins panel keys a plugin's own configuration by\n * `<bundle package>#<row id>` (see dsh's ui-plugin-manager slot contract), so a\n * package mounting a config page there has to spell the bundle it ships in —\n * and spelling it in two places is how one of them goes stale.\n */\nexport const DSHELL_BUNDLE_NAME = '@nexus-aethra/dshell-bundle';\n/**\n * Exact `/api` route path owned by dshell's directory browser.\n *\n * The settings card's data-directory field cannot be typed by everyone and\n * cannot be a native folder dialog: a browser has no access to a host path, and\n * the harness may not even be on the machine drawing the page (the desktop\n * shell and a remote `dsh web` are the same client). So the host lists its own\n * directories and the card draws them — the picker is an ordinary route on the\n * shared `/api` channel, under the same trust and authentication policy as the\n * rest of dshell's routes.\n */\nexport const DSHELL_DIRS_PATH = '/api/dshell/dirs';\n/** The cordis service name `DshellFileSystem` publishes under. */\nexport const DEVICE_FS_SERVICE = 'dshellDeviceFs';\n// ─── usage — the per-model token index the settings page charts ───\n/**\n * The usage index route: one exact `/api` endpoint behind dsh's existing trust\n * and authentication fence, in the shape every other dshell route uses.\n *\n * The page reads aggregates and triggers the scan through the same endpoint:\n * the scan is the expensive half and belongs on the host, where the session\n * events already are, so the browser asks for it rather than doing it.\n */\nexport const DSHELL_USAGE_PATH = '/api/dshell/usage';\n//# sourceMappingURL=contracts.js.map","/**\n * The heatmap's layout: a fixed calendar rectangle of day cells.\n *\n * The grid is filled the way a page is read — top to bottom, then the next\n * column — so the newest day always sits in the BOTTOM-RIGHT corner. That is\n * the shape a reader expects from a \"last 26 weeks\" block, and it is why the\n * cells are not weekday-aligned: aligning columns to weeks would leave the most\n * recent day wherever its weekday happened to fall, which reads as a hole in the\n * corner rather than as \"this is now\".\n *\n * Pure and separate from the page for the same reason the fold is separate from\n * the store: the interesting part is date arithmetic (which day a cell holds,\n * which cells the history actually covers, how a token count becomes a shade),\n * and that is testable without a browser while a chart is not.\n *\n * Days are the host's own `YYYY-MM-DD` local days, and the anchor is the host's\n * today — the index bucketed by LOCAL day when it scanned, so the grid reads\n * them as local dates too, or a bucket would slide across a cell boundary near\n * midnight.\n */\n/** How many shades a non-empty day can take, above \"nothing recorded\". */\nexport const HEAT_LEVELS = 4;\n/** The shape of the grid: columns of a week, however many weeks fit. */\nexport const HEAT_ROWS = 7;\n/** The local date of a `YYYY-MM-DD` label, at local midnight. */\nfunction dateOf(day) {\n return new Date(`${day}T00:00:00`);\n}\n/** The `YYYY-MM-DD` label of a local date, the way the index spells days. */\nfunction labelOf(date) {\n const pad = (value) => String(value).padStart(2, '0');\n return `${String(date.getFullYear())}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n}\n/**\n * Lay the recorded days out in a full rectangle of `weeks` columns.\n *\n * The newest cell is the anchor: the last column's bottom cell holds the anchor\n * day, and every earlier cell steps back one day, so the grid reads oldest at\n * the top-left and newest at the bottom-right. The rectangle is always full —\n * cells the history does not reach are present but empty — because a grid that\n * grew and shrank with the data would move the corner as the history changed.\n *\n * @param days - one entry per recorded day, any order.\n * @param weeks - how many columns to draw.\n * @param anchor - the host's today, `YYYY-MM-DD`; the bottom-right cell.\n * @returns the columns, left to right.\n */\nexport function heatmapColumns(days, weeks, anchor) {\n const byDay = new Map(days.map(day => [day.day, day]));\n const max = Math.max(1, ...days.map(day => day.total));\n const end = dateOf(anchor);\n const columns = [];\n for (let column = 0; column < weeks; column += 1) {\n const cells = [];\n for (let row = 0; row < HEAT_ROWS; row += 1) {\n // Counted back from the anchor, so the loop's last cell is the anchor\n // itself however many columns the grid has.\n const back = (weeks - 1 - column) * HEAT_ROWS + (HEAT_ROWS - 1 - row);\n const date = new Date(end);\n date.setDate(date.getDate() - back);\n const label = labelOf(date);\n if (date.getTime() > end.getTime()) {\n cells.push(undefined);\n continue;\n }\n const recorded = byDay.get(label);\n cells.push(recorded === undefined\n ? undefined\n : {\n ...recorded,\n level: recorded.total === 0\n ? 0\n : Math.min(HEAT_LEVELS, 1 + Math.floor((recorded.total / max) * (HEAT_LEVELS - 1))),\n });\n }\n columns.push(cells);\n }\n return columns;\n}\n/**\n * Whether the history reaches further back than the grid draws.\n *\n * @param days - the same days handed to {@link heatmapColumns}.\n * @param weeks - the same width.\n * @param anchor - the same anchor.\n * @returns whether any recorded day fell off the left edge.\n */\nexport function heatmapTruncated(days, weeks, anchor) {\n if (days.length === 0)\n return false;\n const oldest = days.reduce((earliest, day) => (day.day < earliest ? day.day : earliest), days[0].day);\n const start = dateOf(anchor);\n start.setDate(start.getDate() - (weeks * HEAT_ROWS - 1));\n return dateOf(oldest).getTime() < start.getTime();\n}\n//# sourceMappingURL=heatmap.js.map","import { jsx as _jsx, jsxs as _jsxs } from \"react/jsx-runtime\";\n/**\n * The usage page: one settings entry that charts what every model has cost.\n *\n * Everything on it comes from one host endpoint. `summary` reads the index,\n * `scan` extends it first — the host owns both the session events and the\n * index, so the browser asks for a rebuild rather than performing one.\n *\n * The two charts answer different questions and neither is derivable from the\n * other: the stacked area shows how the total moved over time and which route\n * moved it, and the pie shows what share each route holds across the whole\n * window. Both are hand-rolled SVG — the client bundle inlines its imports, and\n * a charting library would be the largest thing in it by an order of magnitude.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { DSHELL_USAGE_PATH } from '@nexus-aethra/dshell-std';\nimport { HEAT_LEVELS, heatmapColumns, heatmapTruncated } from './heatmap.js';\n/** A fixed palette: a route keeps its colour as the window changes. */\nconst PALETTE = [\n '#5B8FF9', '#61DDAA', '#F6BD16', '#7262FD', '#78D3F8',\n '#9661BC', '#F6903D', '#008685', '#F08BB4', '#E8684A',\n];\nconst RANGES = [\n { days: 7, key: 'range.7' },\n { days: 30, key: 'range.30' },\n { days: null, key: 'range.all' },\n];\n/** Compact token counts: the axis and the table both read better rounded. */\nconst counts = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 });\n/** Rough sizes; the page is a settings column, not a full viewport. */\nconst CURVE = { width: 720, height: 200, padLeft: 56, padRight: 12, padTop: 12, padBottom: 24 };\nconst PIE = { size: 200, radius: 88, inner: 52 };\n/** The heatmap's grid geometry: a fixed half-year block at settings scale. */\nconst HEAT = { cell: 13, gap: 3, weeks: 26 };\n/** The heatmap's shades, from \"a little\" to \"the busiest day\", tinted like the first curve. */\nconst HEAT_SHADES = ['#5B8FF9', '#4a78e0', '#3c62c4', '#2f4da6'];\n/** Every bucket summed — the measure both charts and the table sort on. */\nfunction total(buckets) {\n return buckets.uncachedInputTokens + buckets.outputTokens + buckets.cacheReadTokens + buckets.cacheWriteTokens;\n}\n/** One model's curve: the path across the days, and a point at each day. */\nfunction linePoints(values, xs, y) {\n return values.map((value, index) => `${String(xs[index])},${String(y(value))}`).join(' ');\n}\n/**\n * One ring segment.\n *\n * A segment covering the whole circle needs its own spelling: its start and end\n * points coincide, and an arc between coincident points draws nothing — which\n * is exactly what a reader with one model would see. The full turn is two half\n * turns, outer and inner wound in opposite directions so the middle stays open.\n */\nfunction ringPath(start, end) {\n const { size, radius, inner } = PIE;\n const cx = size / 2;\n const cy = size / 2;\n const at = (r, angle) => [\n cx + r * Math.cos(angle),\n cy + r * Math.sin(angle),\n ];\n if (end - start >= Math.PI * 2 - 1e-6) {\n const outer = `M${String(cx)},${String(cy - radius)}`\n + ` A${String(radius)},${String(radius)} 0 1 1 ${String(cx)},${String(cy + radius)}`\n + ` A${String(radius)},${String(radius)} 0 1 1 ${String(cx)},${String(cy - radius)} Z`;\n const hole = `M${String(cx)},${String(cy - inner)}`\n + ` A${String(inner)},${String(inner)} 0 1 0 ${String(cx)},${String(cy + inner)}`\n + ` A${String(inner)},${String(inner)} 0 1 0 ${String(cx)},${String(cy - inner)} Z`;\n return `${outer} ${hole}`;\n }\n const [ox1, oy1] = at(radius, start);\n const [ox2, oy2] = at(radius, end);\n const [ix2, iy2] = at(inner, end);\n const [ix1, iy1] = at(inner, start);\n const large = end - start > Math.PI ? 1 : 0;\n return `M${String(ox1)},${String(oy1)}`\n + ` A${String(radius)},${String(radius)} 0 ${String(large)} 1 ${String(ox2)},${String(oy2)}`\n + ` L${String(ix2)},${String(iy2)}`\n + ` A${String(inner)},${String(inner)} 0 ${String(large)} 0 ${String(ix1)},${String(iy1)} Z`;\n}\n/** The page body. */\nexport function UsageSection({ t }) {\n const [days, setDays] = useState(7);\n const [summary, setSummary] = useState(undefined);\n const [error, setError] = useState(undefined);\n const [busy, setBusy] = useState(false);\n const [note, setNote] = useState(undefined);\n /** Sessions the last scan had to skip, so the page can say the totals are partial. */\n const [skipped, setSkipped] = useState(undefined);\n const ask = useCallback(async (action, window) => {\n setBusy(true);\n setError(undefined);\n try {\n const response = await fetch(DSHELL_USAGE_PATH, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ action, days: window }),\n });\n const body = await response.json();\n if ('error' in body)\n throw new Error(body.error);\n setSummary(body);\n // The counts come from the scan's own report: how many sessions exist,\n // how many of them needed decoding, and how many turns were counted are\n // three different numbers, and none of them is derivable from the rows\n // the page is about to draw.\n setNote(action === 'scan' && body.scanned !== undefined\n ? t('state.scanned', {\n sessions: counts.format(body.scanned.sessions),\n read: counts.format(body.scanned.read),\n turns: counts.format(body.scanned.turns),\n })\n : undefined);\n // A scan that skipped a session produced partial totals, and a reader who\n // is not told reads the number as the whole. The reason travels with it.\n setSkipped(body.scanned !== undefined && body.scanned.skipped > 0\n ? { count: body.scanned.skipped, reason: body.scanned.skipReason }\n : undefined);\n }\n catch (reason) {\n setError(reason instanceof Error ? reason.message : String(reason));\n }\n finally {\n setBusy(false);\n }\n }, [t]);\n // Opening the page extends the index once — the host coalesces a burst of\n // opens behind its own interval — and a later window change only re-reads,\n // because the window is a query argument rather than a filter over one big\n // payload. Without that first scan a reader who never presses 「重新聚合」\n // would read a frozen index, which is what the page did before.\n const firstOpen = useRef(true);\n useEffect(() => {\n const action = firstOpen.current ? 'scan' : 'summary';\n firstOpen.current = false;\n void ask(action, days);\n }, [ask, days]);\n const curve = useMemo(() => {\n if (summary === undefined)\n return undefined;\n const daysList = summary.daysList;\n const models = summary.models;\n if (daysList.length === 0 || models.length === 0)\n return undefined;\n const span = CURVE.width - CURVE.padLeft - CURVE.padRight;\n // A lone day has no interval to interpolate along, so it sits in the middle\n // of the plot rather than at its left edge.\n const xs = daysList.length === 1\n ? [CURVE.padLeft + span / 2]\n : daysList.map((_, index) => CURVE.padLeft + (index * span) / (daysList.length - 1));\n const seriesValues = models.map(model => daysList.map(day => summary.byDay\n .filter(row => row.day === day && `${row.provider}/${row.model}` === model)\n .reduce((sum, row) => sum + total(row), 0)));\n // Scaled to the highest single value, not to a stacked total: these are\n // separate curves, so each one has to be readable on its own.\n const max = Math.max(1, ...seriesValues.flat());\n const y = (value) => CURVE.height - CURVE.padBottom\n - (value / max) * (CURVE.height - CURVE.padTop - CURVE.padBottom);\n return {\n xs,\n y,\n max,\n daysList,\n series: models.map((model, index) => ({\n model,\n color: PALETTE[index % PALETTE.length],\n points: linePoints(seriesValues[index], xs, y),\n markers: seriesValues[index].map((value, position) => ({ x: xs[position], y: y(value) })),\n })),\n };\n }, [summary]);\n const pie = useMemo(() => {\n if (summary === undefined)\n return undefined;\n const rows = [...summary.totals];\n const sum = rows.reduce((carry, row) => carry + total(row), 0);\n if (sum === 0)\n return undefined;\n let angle = -Math.PI / 2;\n return {\n sum,\n slices: rows.map((row, index) => {\n const share = total(row) / sum;\n const start = angle;\n const end = angle + share * Math.PI * 2;\n angle = end;\n return { row, color: PALETTE[index % PALETTE.length], share, path: ringPath(start, end) };\n }),\n };\n }, [summary]);\n /** The window's headline numbers: what the two charts are shares and shapes of. */\n const headline = useMemo(() => {\n if (summary === undefined)\n return undefined;\n const sums = summary.totals.reduce((carry, row) => ({\n uncachedInputTokens: carry.uncachedInputTokens + row.uncachedInputTokens,\n outputTokens: carry.outputTokens + row.outputTokens,\n cacheReadTokens: carry.cacheReadTokens + row.cacheReadTokens,\n cacheWriteTokens: carry.cacheWriteTokens + row.cacheWriteTokens,\n turns: carry.turns + row.turns,\n }), { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, turns: 0 });\n const billed = sums.uncachedInputTokens + sums.cacheReadTokens;\n return {\n ...sums,\n total: total(sums),\n // A hit rate is the one number that says whether the cache is working;\n // it is read over the input side only, which is where a cache read lands.\n hitRate: billed === 0 ? undefined : sums.cacheReadTokens / billed,\n };\n }, [summary]);\n /** The calendar grid: a fixed rectangle of weeks, newest day at the bottom right. */\n const heat = useMemo(() => {\n if (summary === undefined || summary.heat.length === 0)\n return undefined;\n const columns = heatmapColumns(summary.heat, HEAT.weeks, summary.today);\n if (columns.length === 0)\n return undefined;\n return {\n columns,\n truncated: heatmapTruncated(summary.heat, HEAT.weeks, summary.today),\n busiest: Math.max(...summary.heat.map(day => day.total)),\n };\n }, [summary]);\n return (_jsxs(\"section\", { style: { display: 'flex', flexDirection: 'column', gap: 20, padding: '4px 2px' }, children: [_jsxs(\"header\", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(\"h2\", { style: { margin: 0, fontSize: 16 }, children: t('title') }), _jsx(\"p\", { style: { margin: 0, opacity: 0.7, lineHeight: 1.6 }, children: t('subtitle') })] }), heat !== undefined && (_jsxs(\"figure\", { style: { margin: 0, display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsx(\"figcaption\", { style: { opacity: 0.8 }, children: t('chart.heatmap') }), _jsx(\"div\", { style: { display: 'flex', gap: HEAT.gap, overflowX: 'auto' }, children: heat.columns.map((column, index) => (_jsx(\"div\", { style: { display: 'grid', gridTemplateRows: `repeat(7, ${String(HEAT.cell)}px)`, gap: HEAT.gap }, children: column.map((cell, slot) => cell === undefined\n ? _jsx(\"span\", { style: { width: HEAT.cell, height: HEAT.cell, borderRadius: 3, background: 'currentColor', opacity: 0.06 } }, `empty-${String(slot)}`)\n : (_jsx(\"span\", { title: t('chart.heatmapDay', {\n day: cell.day,\n total: counts.format(cell.total),\n turns: counts.format(cell.turns),\n }), style: {\n width: HEAT.cell, height: HEAT.cell, borderRadius: 3,\n background: cell.level === 0 ? 'currentColor' : HEAT_SHADES[cell.level - 1],\n opacity: cell.level === 0 ? 0.06 : 0.35 + (cell.level / HEAT_LEVELS) * 0.65,\n } }, cell.day))) }, `column-${String(index)}`))) }), _jsxs(\"div\", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, opacity: 0.7 }, children: [_jsx(\"span\", { children: t('chart.heatmapLess') }), [0, 1, 2, 3, 4].map(level => (_jsx(\"span\", { style: {\n width: HEAT.cell, height: HEAT.cell, borderRadius: 3,\n background: level === 0 ? 'currentColor' : HEAT_SHADES[level - 1],\n opacity: level === 0 ? 0.06 : 0.35 + (level / HEAT_LEVELS) * 0.65,\n } }, String(level)))), _jsx(\"span\", { children: t('chart.heatmapMore') }), _jsx(\"span\", { style: { marginLeft: 6, opacity: 0.8 }, children: t('chart.heatmapPeak', { total: counts.format(heat.busiest) }) }), heat.truncated ? _jsxs(\"span\", { style: { opacity: 0.8 }, children: [\"\\u00B7 \", t('chart.heatmapTruncated')] }) : null] })] })), _jsxs(\"div\", { style: { display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }, children: [_jsx(\"span\", { style: { opacity: 0.7 }, children: t('range.label') }), RANGES.map(range => (_jsx(\"button\", { type: \"button\", onClick: () => { setDays(range.days); }, \n /* Selection is stated with weight, edge and emphasis rather than an\n inverted fill: an inverted fill needs a surface colour that reads\n against it, and inventing one goes dark-on-dark in a theme this\n page does not own. */\n style: {\n padding: '4px 12px',\n borderRadius: 999,\n cursor: 'pointer',\n border: days === range.days ? '1.5px solid currentColor' : '1px solid currentColor',\n background: 'transparent',\n color: 'inherit',\n fontWeight: days === range.days ? 600 : 400,\n opacity: days === range.days ? 1 : 0.55,\n }, children: t(range.key) }, range.key))), _jsx(\"button\", { type: \"button\", disabled: busy, onClick: () => { void ask('scan', days); }, style: { marginLeft: 'auto', padding: '4px 14px', borderRadius: 6, cursor: busy ? 'default' : 'pointer' }, children: busy ? t('rebuild.busy') : t('rebuild') })] }), summary?.scannedAt != null && (_jsxs(\"div\", { style: { opacity: 0.6, fontSize: 12 }, children: [t('state.freshness', { day: summary.scannedAt }), note === undefined ? '' : ` · ${note}`] })), skipped !== undefined && (_jsxs(\"div\", { style: {\n fontSize: 12, lineHeight: 1.6, padding: '6px 10px', borderRadius: 8,\n color: 'var(--dsw-alias-state-warning-primary, #b45309)',\n background: 'var(--dsw-alias-interactive-bg-hover-warning, rgba(245,158,11,.12))',\n }, title: skipped.reason, children: [t('state.skipped', { count: counts.format(skipped.count) }), skipped.reason === undefined ? null : (_jsxs(\"span\", { style: { opacity: 0.7 }, children: [\" \\u00B7 \", shorten(skipped.reason, 120)] }))] })), error !== undefined && (_jsx(\"div\", { style: { color: 'var(--dsh-danger, #c0392b)', fontSize: 13 }, children: t('state.error', { reason: error }) })), summary === undefined && error === undefined && _jsx(\"div\", { style: { opacity: 0.6 }, children: t('state.loading') }), headline !== undefined && headline.turns > 0 && (_jsx(\"div\", { style: { display: 'flex', gap: 18, flexWrap: 'wrap' }, children: [\n ['summary.total', counts.format(headline.total)],\n ['summary.uncached', counts.format(headline.uncachedInputTokens)],\n ['summary.output', counts.format(headline.outputTokens)],\n ['summary.cacheRead', counts.format(headline.cacheReadTokens)],\n ['summary.turns', counts.format(headline.turns)],\n ...headline.hitRate === undefined\n ? []\n : [['summary.hitRate', `${(headline.hitRate * 100).toFixed(1)}%`]],\n ].map(([key, value]) => (_jsxs(\"div\", { style: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(\"span\", { style: { fontSize: 11, opacity: 0.6 }, children: t(key) }), _jsx(\"span\", { style: { fontSize: 18, fontVariantNumeric: 'tabular-nums' }, children: value })] }, key))) })), summary !== undefined && curve === undefined && (_jsxs(\"div\", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsx(\"strong\", { children: t('state.empty.title') }), _jsx(\"span\", { style: { opacity: 0.7 }, children: t('state.empty.body') })] })), curve !== undefined && (_jsxs(\"figure\", { style: { margin: 0, display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsx(\"figcaption\", { style: { opacity: 0.8 }, children: t('chart.curve') }), _jsxs(\"svg\", { viewBox: `0 0 ${String(CURVE.width)} ${String(CURVE.height)}`, style: { width: '100%', height: 'auto' }, role: \"img\", children: [[0, 0.5, 1].map(fraction => {\n const value = curve.max * fraction;\n return (_jsxs(\"g\", { children: [_jsx(\"line\", { x1: CURVE.padLeft, x2: CURVE.width - CURVE.padRight, y1: curve.y(value), y2: curve.y(value), stroke: \"currentColor\", strokeOpacity: 0.15, strokeDasharray: fraction === 0 ? undefined : '3 3' }), _jsx(\"text\", { x: CURVE.padLeft - 8, y: curve.y(value) + 4, textAnchor: \"end\", fontSize: 10, fill: \"currentColor\", fillOpacity: 0.6, children: counts.format(value) })] }, fraction));\n }), curve.series.map(series => (_jsxs(\"g\", { children: [_jsx(\"polyline\", { points: series.points, fill: \"none\", stroke: series.color, strokeWidth: 2, strokeLinejoin: \"round\" }), series.markers.map((marker, position) => (_jsx(\"circle\", { cx: marker.x, cy: marker.y, r: 3, fill: series.color }, `${series.model}-${String(position)}`)))] }, series.model))), curve.daysList.map((day, index) => (index % Math.ceil(curve.daysList.length / 8) === 0\n ? (_jsx(\"text\", { x: curve.xs[index], y: CURVE.height - 6, textAnchor: \"middle\", fontSize: 10, fill: \"currentColor\", fillOpacity: 0.6, children: day.slice(5) }, day))\n : null))] }), _jsx(Legend, { series: curve.series })] })), pie !== undefined && (_jsxs(\"figure\", { style: { margin: 0, display: 'flex', gap: 20, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx(\"figcaption\", { style: { opacity: 0.8, alignSelf: 'flex-start' }, children: t('chart.pie') }), _jsxs(\"svg\", { viewBox: `0 0 ${String(PIE.size)} ${String(PIE.size)}`, style: { width: PIE.size, height: PIE.size }, role: \"img\", children: [pie.slices.map(slice => (_jsx(\"path\", { d: slice.path, fill: slice.color, fillOpacity: 0.85, fillRule: \"evenodd\" }, `${slice.row.provider}/${slice.row.model}`))), _jsx(\"text\", { x: PIE.size / 2, y: PIE.size / 2 - 2, textAnchor: \"middle\", fontSize: 16, fill: \"currentColor\", children: counts.format(pie.sum) }), _jsx(\"text\", { x: PIE.size / 2, y: PIE.size / 2 + 14, textAnchor: \"middle\", fontSize: 10, fill: \"currentColor\", fillOpacity: 0.6, children: \"tok\" })] }), _jsxs(\"table\", { style: { borderCollapse: 'collapse', fontSize: 13 }, children: [_jsx(\"thead\", { children: _jsxs(\"tr\", { style: { opacity: 0.7 }, children: [_jsx(\"th\", { style: cellStyle('left'), children: t('table.model') }), _jsx(\"th\", { style: cellStyle('right'), children: t('table.total') }), _jsx(\"th\", { style: cellStyle('right'), children: t('table.turns') })] }) }), _jsx(\"tbody\", { children: pie.slices.map(slice => (_jsxs(\"tr\", { children: [_jsxs(\"td\", { style: cellStyle('left'), children: [_jsx(\"span\", { style: { display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: slice.color, marginRight: 6 } }), slice.row.model, _jsx(\"span\", { style: { opacity: 0.5, marginLeft: 6 }, children: slice.row.provider })] }), _jsxs(\"td\", { style: cellStyle('right'), children: [counts.format(total(slice.row)), \" \", _jsxs(\"span\", { style: { opacity: 0.5 }, children: [\"(\", (slice.share * 100).toFixed(1), \"%)\"] })] }), _jsx(\"td\", { style: cellStyle('right'), children: counts.format(slice.row.turns) })] }, `${slice.row.provider}/${slice.row.model}`))) })] })] })), pie !== undefined && (_jsxs(\"details\", { children: [_jsx(\"summary\", { style: { cursor: 'pointer', opacity: 0.8 }, children: t('table.title') }), _jsxs(\"table\", { style: { borderCollapse: 'collapse', fontSize: 13, marginTop: 10, width: '100%' }, children: [_jsx(\"thead\", { children: _jsxs(\"tr\", { style: { opacity: 0.7 }, children: [_jsx(\"th\", { style: cellStyle('left'), children: t('table.model') }), _jsx(\"th\", { style: cellStyle('right'), children: t('table.input') }), _jsx(\"th\", { style: cellStyle('right'), children: t('table.output') }), _jsx(\"th\", { style: cellStyle('right'), children: t('table.cacheRead') }), _jsx(\"th\", { style: cellStyle('right'), children: t('table.cacheWrite') }), _jsx(\"th\", { style: cellStyle('right'), children: t('table.turns') }), _jsx(\"th\", { style: cellStyle('right'), children: t('table.total') })] }) }), _jsx(\"tbody\", { children: summary?.totals.map((row, index) => (_jsxs(\"tr\", { children: [_jsxs(\"td\", { style: cellStyle('left'), children: [_jsx(\"span\", { style: { display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: PALETTE[index % PALETTE.length], marginRight: 6 } }), row.model, _jsx(\"span\", { style: { opacity: 0.5, marginLeft: 6 }, children: row.provider })] }), _jsx(\"td\", { style: cellStyle('right'), children: counts.format(row.uncachedInputTokens) }), _jsx(\"td\", { style: cellStyle('right'), children: counts.format(row.outputTokens) }), _jsx(\"td\", { style: cellStyle('right'), children: counts.format(row.cacheReadTokens) }), _jsx(\"td\", { style: cellStyle('right'), children: counts.format(row.cacheWriteTokens) }), _jsx(\"td\", { style: cellStyle('right'), children: counts.format(row.turns) }), _jsx(\"td\", { style: cellStyle('right'), children: counts.format(total(row)) })] }, `${row.provider}/${row.model}`))) })] })] }))] }));\n}\n/** The curve's colour key, in the order the lines are drawn. */\nfunction Legend({ series }) {\n return (_jsx(\"div\", { style: { display: 'flex', gap: 14, flexWrap: 'wrap', fontSize: 12 }, children: series.map(entry => (_jsxs(\"span\", { style: { display: 'inline-flex', alignItems: 'center', gap: 5 }, children: [_jsx(\"span\", { style: { width: 8, height: 8, borderRadius: 2, background: entry.color } }), entry.model] }, entry.model))) }));\n}\n/** One table cell's alignment and padding, so the two tables match. */\nfunction cellStyle(align) {\n return { textAlign: align, padding: '3px 10px 3px 0' };\n}\n/**\n * Shorten a host error for a one-line note.\n *\n * A refusal names the session, the reason and the log path — useful, and far too\n * long to sit in a settings line; the full text stays in the note's `title`.\n */\nfunction shorten(text, limit) {\n return text.length <= limit ? text : `${text.slice(0, limit)}…`;\n}\n//# sourceMappingURL=page.js.map","/**\n * The usage entry's own icon in the settings navigation.\n *\n * dsh's settings shell picks a nav icon from a map of the section ids IT ships\n * (`account`, `models`, `plugins`, …) and draws a generic gear for anything\n * else, and rc.2's `settings.section` seat carries no icon of its own — so a\n * contributed section cannot ask for one and ours read as \"some settings\".\n *\n * Hence the one DOM patch in this package: find our own row by the label we\n * registered, and put a gauge where the gear was. It is the same technique\n * dshell's sidebar section uses (and the artwork is copied verbatim from\n * `@deepseek-ai/dsh-client-ui-primitives`, medium weight, 16 viewBox, 1.3px\n * stroke) because a surface mounted into a node dsh owns cannot render React\n * components into it.\n *\n * The row is re-created whenever the dialog opens, so a MutationObserver\n * watches for it; the patch is idempotent (it re-checks the label and a marker\n * attribute) and only touches the svg node React will not write to again.\n * Delete this file the day `settings.section` accepts an icon.\n */\n/**\n * The gauge artwork, verbatim from the primitives package\n * (`IconGaugeOutlineMedium`): the same three paths, in the same order.\n */\nconst GAUGE_PATHS = [\n { d: 'M3.4041 13.096C2.49514 12.187 1.87614 11.0288 1.62537 9.76798C1.37459 8.50716 1.50331 7.20028 1.99525 6.01261C2.48719 4.82494 3.32025 3.80981 4.3891 3.09557C5.45795 2.38134 6.71458 2.00008 8.0001 2C9.28563 2.00008 10.5423 2.38134 11.6111 3.09557C12.68 3.80981 13.513 4.82494 14.005 6.01261C14.4969 7.20028 14.6256 8.50716 14.3748 9.76798C14.1241 11.0288 13.5051 12.187 12.5961 13.096', stroke: true },\n { d: 'M8 8.49994L11.6114 4.88855', stroke: true },\n { d: 'M8 9.75C8.69036 9.75 9.25 9.19036 9.25 8.5C9.25 7.80964 8.69036 7.25 8 7.25C7.30964 7.25 6.75 7.80964 6.75 8.5C6.75 9.19036 7.30964 9.75 8 9.75Z', stroke: false },\n];\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n/** The marker that says a row already carries our artwork. */\nconst MARKER = 'data-dshell-usage-icon';\n/** One gauge, sized and coloured like the icons beside it. */\nfunction gauge(size, className) {\n const svg = document.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('width', String(size));\n svg.setAttribute('height', String(size));\n svg.setAttribute('viewBox', '0 0 16 16');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke-width', '1.3');\n svg.setAttribute('aria-hidden', 'true');\n if (className !== null)\n svg.setAttribute('class', className);\n for (const spec of GAUGE_PATHS) {\n const path = document.createElementNS(SVG_NS, 'path');\n path.setAttribute('d', spec.d);\n if (spec.stroke)\n path.setAttribute('stroke', 'currentColor');\n else\n path.setAttribute('fill', 'currentColor');\n svg.append(path);\n }\n return svg;\n}\n/**\n * Keep the settings navigation's usage row drawn with the gauge.\n *\n * @param label - the section label as the shell renders it, in the live locale.\n * @returns the disposer that stops watching.\n */\nexport function watchUsageNavIcon(label) {\n if (typeof document === 'undefined')\n return () => { };\n const patch = () => {\n for (const dialog of document.querySelectorAll('[role=\"dialog\"]')) {\n for (const row of dialog.querySelectorAll('nav button')) {\n const text = row.textContent?.trim() ?? '';\n if (text !== label())\n continue;\n const svg = row.querySelector('svg');\n if (svg === null || svg.getAttribute(MARKER) !== null)\n continue;\n // React owns both children by position and neither is re-rendered while\n // the dialog lives, so the replacement is what the reader sees; the\n // pristine node would only come back with a full re-mount, which the\n // observer catches.\n const replacement = gauge(Number(svg.getAttribute('width') ?? 16) || 16, svg.getAttribute('class'));\n replacement.setAttribute(MARKER, '');\n svg.replaceWith(replacement);\n }\n }\n };\n patch();\n const observer = new MutationObserver(() => { patch(); });\n observer.observe(document.body, { childList: true, subtree: true });\n return () => { observer.disconnect(); };\n}\n//# sourceMappingURL=nav-icon.js.map","/**\n * The usage page's client face: the locale dictionaries and the Settings\n * navigation row that mounts the page.\n *\n * `settings.section` is the seat dsh's own Plugins page uses, so this page is a\n * peer of it rather than a tab inside it — the charts want the full settings\n * column, and a reader looking for \"what did this cost\" looks in the\n * navigation, not under a plugin list.\n */\nimport { en, zh } from './locales.js';\nimport { UsageSection } from './page.js';\nimport { watchUsageNavIcon } from './nav-icon.js';\n/** The locale namespace, matching the key `locales.ts` declares. */\nconst NS = 'dshellUsage';\nexport const name = '@nexus-aethra/dshell-usage/client';\nexport const inject = ['slots', 'locale'];\nexport function apply(ctx) {\n const t = ctx.locale.bind(NS);\n // Registered through an effect so a composition that unloads this plugin\n // takes its copy with it.\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dshell-usage: dictionaries');\n // Ordered after the Plugins page (15) so the navigation reads\n // sessions → plugins → usage, and named with its own id so it is added beside\n // the shipped entries rather than replacing one.\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'usage',\n order: 40,\n label: () => t('nav'),\n locale: NS,\n }, UsageSection));\n // The row's own icon, because this seat carries none: dsh draws a generic\n // gear for any section id it does not ship (see nav-icon.ts).\n ctx.effect(() => watchUsageNavIcon(() => t('nav')), 'dshell-usage: navigation icon');\n}\n//# sourceMappingURL=index.js.map"],"x_google_ignoreList":[1,2],"mappings":"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"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The usage index: one SQLite database holding per-day, per-route token
|
|
3
|
+
* buckets, plus the per-session cursor that makes a rescan incremental.
|
|
4
|
+
*
|
|
5
|
+
* Node's built-in `node:sqlite` is the engine, the same choice
|
|
6
|
+
* `@nexus-aethra/dshell-storage` makes for command history and for the same
|
|
7
|
+
* reasons: no native dependency and no install script.
|
|
8
|
+
*
|
|
9
|
+
* Why a table and not the JSON document this could have been: the page asks
|
|
10
|
+
* aggregate questions ("this route over these days", "every day for this
|
|
11
|
+
* route") and a document would have to be parsed whole to answer any of them.
|
|
12
|
+
* The buckets are stored UNSHIFTED, one row per (day, route), and the row is
|
|
13
|
+
* added to on rescan rather than rewritten, which is what lets a rescan touch
|
|
14
|
+
* only the sessions that grew.
|
|
15
|
+
*
|
|
16
|
+
* Writes are deliberately coarse. A scan accumulates its rows in memory and
|
|
17
|
+
* calls {@link UsageStore.applyBatch} once, inside a single transaction: the
|
|
18
|
+
* host must not write per model turn, per event, or per session — see
|
|
19
|
+
* `scan.ts` for the policy that decides when a batch is worth flushing.
|
|
20
|
+
*
|
|
21
|
+
* Format — `PRAGMA user_version = 1`.
|
|
22
|
+
*
|
|
23
|
+
* usage_bucket(day, provider, model, uncached_input, output, cache_read,
|
|
24
|
+
* cache_write, turns)
|
|
25
|
+
* PRIMARY KEY (day, provider, model)
|
|
26
|
+
* `day` is `YYYY-MM-DD` in the HOST's local time, decided at scan time so
|
|
27
|
+
* a query never has to guess a zone. Every deployment here has the
|
|
28
|
+
* browser and the host on one machine, and the response states the days
|
|
29
|
+
* it covers, so a reader can tell what the buckets mean.
|
|
30
|
+
* usage_cursor(session_id, seq)
|
|
31
|
+
* PRIMARY KEY (session_id)
|
|
32
|
+
* the highest event seq already counted for that session; absent means
|
|
33
|
+
* never scanned.
|
|
34
|
+
* usage_meta(key, value)
|
|
35
|
+
* `scannedAt`, the local day the index was last extended.
|
|
36
|
+
*/
|
|
37
|
+
import { mkdirSync } from 'node:fs';
|
|
38
|
+
import { dirname, resolve } from 'node:path';
|
|
39
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
40
|
+
const LAYOUT = 1;
|
|
41
|
+
const ZERO = {
|
|
42
|
+
uncachedInputTokens: 0,
|
|
43
|
+
outputTokens: 0,
|
|
44
|
+
cacheReadTokens: 0,
|
|
45
|
+
cacheWriteTokens: 0,
|
|
46
|
+
turns: 0,
|
|
47
|
+
};
|
|
48
|
+
/** Open databases by absolute path, so one process holds one handle per file. */
|
|
49
|
+
const stores = new Map();
|
|
50
|
+
/** One open index. */
|
|
51
|
+
export class UsageStore {
|
|
52
|
+
path;
|
|
53
|
+
#db;
|
|
54
|
+
constructor(path) {
|
|
55
|
+
this.path = path;
|
|
56
|
+
this.#db = new DatabaseSync(path);
|
|
57
|
+
this.#db.exec('PRAGMA journal_mode = WAL');
|
|
58
|
+
this.#db.exec('PRAGMA synchronous = NORMAL');
|
|
59
|
+
this.#migrate();
|
|
60
|
+
}
|
|
61
|
+
/** Bring an older layout forward, or refuse one this build does not know. */
|
|
62
|
+
#migrate() {
|
|
63
|
+
const row = this.#db.prepare('PRAGMA user_version').get();
|
|
64
|
+
const version = row?.user_version ?? 0;
|
|
65
|
+
if (version === LAYOUT)
|
|
66
|
+
return;
|
|
67
|
+
if (version !== 0) {
|
|
68
|
+
// Forward-only: a newer layout read by an older build would silently
|
|
69
|
+
// drop the columns it does not know, so it stops instead.
|
|
70
|
+
throw new Error(`dshell usage: index at ${this.path} has layout ${String(version)}, this build writes ${String(LAYOUT)}`);
|
|
71
|
+
}
|
|
72
|
+
this.#db.exec(`
|
|
73
|
+
CREATE TABLE usage_bucket (
|
|
74
|
+
day TEXT NOT NULL,
|
|
75
|
+
provider TEXT NOT NULL,
|
|
76
|
+
model TEXT NOT NULL,
|
|
77
|
+
uncached_input INTEGER NOT NULL DEFAULT 0,
|
|
78
|
+
output INTEGER NOT NULL DEFAULT 0,
|
|
79
|
+
cache_read INTEGER NOT NULL DEFAULT 0,
|
|
80
|
+
cache_write INTEGER NOT NULL DEFAULT 0,
|
|
81
|
+
turns INTEGER NOT NULL DEFAULT 0,
|
|
82
|
+
PRIMARY KEY (day, provider, model)
|
|
83
|
+
);
|
|
84
|
+
CREATE TABLE usage_cursor (
|
|
85
|
+
session_id TEXT NOT NULL PRIMARY KEY,
|
|
86
|
+
seq INTEGER NOT NULL
|
|
87
|
+
);
|
|
88
|
+
CREATE TABLE usage_meta (
|
|
89
|
+
key TEXT NOT NULL PRIMARY KEY,
|
|
90
|
+
value TEXT NOT NULL
|
|
91
|
+
);
|
|
92
|
+
PRAGMA user_version = ${String(LAYOUT)};
|
|
93
|
+
`);
|
|
94
|
+
}
|
|
95
|
+
/** The highest seq already counted for one session, or undefined. */
|
|
96
|
+
cursorFor(sessionId) {
|
|
97
|
+
const row = this.#db.prepare('SELECT seq FROM usage_cursor WHERE session_id = ?').get(sessionId);
|
|
98
|
+
return row?.seq;
|
|
99
|
+
}
|
|
100
|
+
/** Every cursor, so a scan can decide what to read without a query per session. */
|
|
101
|
+
cursors() {
|
|
102
|
+
const rows = this.#db.prepare('SELECT session_id, seq FROM usage_cursor').all();
|
|
103
|
+
return new Map(rows.map(row => [row.session_id, row.seq]));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Add one scan's rows and advance its cursors, in one transaction.
|
|
107
|
+
*
|
|
108
|
+
* Additive on purpose: a rescan hands over only what it newly counted, so the
|
|
109
|
+
* statement can never overwrite a bucket it did not read.
|
|
110
|
+
*/
|
|
111
|
+
applyBatch(batch) {
|
|
112
|
+
if (batch.rows.length === 0 && batch.cursors.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
const bucket = this.#db.prepare(`
|
|
115
|
+
INSERT INTO usage_bucket (day, provider, model, uncached_input, output, cache_read, cache_write, turns)
|
|
116
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
117
|
+
ON CONFLICT (day, provider, model) DO UPDATE SET
|
|
118
|
+
uncached_input = uncached_input + excluded.uncached_input,
|
|
119
|
+
output = output + excluded.output,
|
|
120
|
+
cache_read = cache_read + excluded.cache_read,
|
|
121
|
+
cache_write = cache_write + excluded.cache_write,
|
|
122
|
+
turns = turns + excluded.turns
|
|
123
|
+
`);
|
|
124
|
+
const cursor = this.#db.prepare(`
|
|
125
|
+
INSERT INTO usage_cursor (session_id, seq) VALUES (?, ?)
|
|
126
|
+
ON CONFLICT (session_id) DO UPDATE SET seq = excluded.seq
|
|
127
|
+
`);
|
|
128
|
+
const meta = this.#db.prepare(`
|
|
129
|
+
INSERT INTO usage_meta (key, value) VALUES ('scannedAt', ?)
|
|
130
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value
|
|
131
|
+
`);
|
|
132
|
+
this.#db.exec('BEGIN');
|
|
133
|
+
try {
|
|
134
|
+
for (const row of batch.rows) {
|
|
135
|
+
bucket.run(row.day, row.provider, row.model, row.uncachedInputTokens, row.outputTokens, row.cacheReadTokens, row.cacheWriteTokens, row.turns);
|
|
136
|
+
}
|
|
137
|
+
for (const entry of batch.cursors)
|
|
138
|
+
cursor.run(entry.sessionId, entry.seq);
|
|
139
|
+
meta.run(batch.scannedAt);
|
|
140
|
+
this.#db.exec('COMMIT');
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
this.#db.exec('ROLLBACK');
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The aggregate the page renders, over one window.
|
|
149
|
+
*
|
|
150
|
+
* @param days - Window length in days, or null for everything on record.
|
|
151
|
+
* @returns Day rows for the curve, route totals for the pie and table, and
|
|
152
|
+
* the two axis lists both need so the page never derives its own.
|
|
153
|
+
*/
|
|
154
|
+
summary(days) {
|
|
155
|
+
const bound = days === null ? undefined : localDay(new Date(Date.now() - days * 86_400_000));
|
|
156
|
+
const where = bound === undefined ? '' : 'WHERE day >= ?';
|
|
157
|
+
const args = bound === undefined ? [] : [bound];
|
|
158
|
+
const dayRows = this.#db.prepare(`
|
|
159
|
+
SELECT day, provider, model, uncached_input, output, cache_read, cache_write, turns
|
|
160
|
+
FROM usage_bucket ${where}
|
|
161
|
+
ORDER BY day, provider, model
|
|
162
|
+
`).all(...args);
|
|
163
|
+
const byDay = dayRows.map(row => ({
|
|
164
|
+
day: String(row.day),
|
|
165
|
+
provider: String(row.provider),
|
|
166
|
+
model: String(row.model),
|
|
167
|
+
uncachedInputTokens: Number(row.uncached_input),
|
|
168
|
+
outputTokens: Number(row.output),
|
|
169
|
+
cacheReadTokens: Number(row.cache_read),
|
|
170
|
+
cacheWriteTokens: Number(row.cache_write),
|
|
171
|
+
turns: Number(row.turns),
|
|
172
|
+
}));
|
|
173
|
+
const totals = new Map();
|
|
174
|
+
for (const row of byDay) {
|
|
175
|
+
const key = `${row.provider}\u0000${row.model}`;
|
|
176
|
+
const running = totals.get(key) ?? { provider: row.provider, model: row.model, ...ZERO };
|
|
177
|
+
totals.set(key, {
|
|
178
|
+
provider: row.provider,
|
|
179
|
+
model: row.model,
|
|
180
|
+
uncachedInputTokens: running.uncachedInputTokens + row.uncachedInputTokens,
|
|
181
|
+
outputTokens: running.outputTokens + row.outputTokens,
|
|
182
|
+
cacheReadTokens: running.cacheReadTokens + row.cacheReadTokens,
|
|
183
|
+
cacheWriteTokens: running.cacheWriteTokens + row.cacheWriteTokens,
|
|
184
|
+
turns: running.turns + row.turns,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const totalsList = [...totals.values()].sort((left, right) => total(right) - total(left));
|
|
188
|
+
// The calendar reads ALL time, whatever window the charts are showing: a
|
|
189
|
+
// rhythm question ("which days do I burn tokens on") is not the same
|
|
190
|
+
// question as "what did this week cost", and re-shaping the calendar every
|
|
191
|
+
// time the reader moves the range would answer neither.
|
|
192
|
+
const heat = this.#db.prepare(`
|
|
193
|
+
SELECT day, sum(uncached_input + output + cache_read + cache_write) AS total, sum(turns) AS turns
|
|
194
|
+
FROM usage_bucket GROUP BY day ORDER BY day
|
|
195
|
+
`).all().map(row => ({
|
|
196
|
+
day: String(row.day),
|
|
197
|
+
total: Number(row.total),
|
|
198
|
+
turns: Number(row.turns),
|
|
199
|
+
}));
|
|
200
|
+
const scanned = this.#db.prepare('SELECT value FROM usage_meta WHERE key = ?').get('scannedAt');
|
|
201
|
+
const built = this.#db.prepare('SELECT count(*) AS n FROM usage_cursor').get();
|
|
202
|
+
return {
|
|
203
|
+
built: (built?.n ?? 0) > 0,
|
|
204
|
+
days,
|
|
205
|
+
scannedAt: scanned?.value ?? null,
|
|
206
|
+
today: localDay(new Date()),
|
|
207
|
+
daysList: [...new Set(byDay.map(row => row.day))].sort(),
|
|
208
|
+
models: totalsList.map(row => routeName(row)),
|
|
209
|
+
byDay,
|
|
210
|
+
totals: totalsList,
|
|
211
|
+
heat,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
close() {
|
|
215
|
+
this.#db.close();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Every bucket summed — the pie's measure and the table's sort key. */
|
|
219
|
+
export function total(buckets) {
|
|
220
|
+
return buckets.uncachedInputTokens + buckets.outputTokens + buckets.cacheReadTokens + buckets.cacheWriteTokens;
|
|
221
|
+
}
|
|
222
|
+
/** How a route is named in the UI and in the response's `models` list. */
|
|
223
|
+
export function routeName(route) {
|
|
224
|
+
return `${route.provider}/${route.model}`;
|
|
225
|
+
}
|
|
226
|
+
/** `YYYY-MM-DD` in the host's local time. */
|
|
227
|
+
export function localDay(at) {
|
|
228
|
+
const month = String(at.getMonth() + 1).padStart(2, '0');
|
|
229
|
+
const day = String(at.getDate()).padStart(2, '0');
|
|
230
|
+
return `${String(at.getFullYear())}-${month}-${day}`;
|
|
231
|
+
}
|
|
232
|
+
/** Open (or reuse) the index at one path. */
|
|
233
|
+
export function openUsageStore(path) {
|
|
234
|
+
const key = path === ':memory:' ? path : resolve(path);
|
|
235
|
+
const existing = stores.get(key);
|
|
236
|
+
if (existing !== undefined)
|
|
237
|
+
return existing;
|
|
238
|
+
if (key !== ':memory:')
|
|
239
|
+
mkdirSync(dirname(key), { recursive: true });
|
|
240
|
+
const store = new UsageStore(key);
|
|
241
|
+
stores.set(key, store);
|
|
242
|
+
return store;
|
|
243
|
+
}
|
|
244
|
+
/** Close and forget one path's index; the plugin's teardown path. */
|
|
245
|
+
export function closeUsageStore(path) {
|
|
246
|
+
const key = path === ':memory:' ? path : resolve(path);
|
|
247
|
+
stores.get(key)?.close();
|
|
248
|
+
stores.delete(key);
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=index-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-store.js","sourceRoot":"","sources":["../src/index-store.ts"],"names":[],"mappings":"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"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dshell usage: the durable per-model token index and the settings page that
|
|
3
|
+
* charts it.
|
|
4
|
+
*
|
|
5
|
+
* The host half is small on purpose. It owns one SQLite index under dshell's
|
|
6
|
+
* data root, one scanner over `ctx.sessionQuery`, and one route; everything the
|
|
7
|
+
* reader sees is folded from the same three. The page lives in `./client`.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here subscribes to turns. A scan is requested — by the page on open,
|
|
10
|
+
* or by the reader's rebuild action — and the scanner coalesces repeated
|
|
11
|
+
* requests behind a minimum interval, so a host with a busy agent still writes
|
|
12
|
+
* to disk at most once per window instead of once per model turn.
|
|
13
|
+
*/
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { DSHELL_DATA_ROOT_SERVICE } from '@nexus-aethra/dshell-std';
|
|
16
|
+
import { closeUsageStore, openUsageStore } from './index-store.js';
|
|
17
|
+
import { createUsageRoute } from './route.js';
|
|
18
|
+
import { UsageScanner } from './scan.js';
|
|
19
|
+
/** The one service this package cannot compose without. */
|
|
20
|
+
export const inject = [DSHELL_DATA_ROOT_SERVICE];
|
|
21
|
+
/** The index file's name under `<data root>/usage/`. */
|
|
22
|
+
const INDEX_FILE = 'usage.sqlite';
|
|
23
|
+
export function apply(ctx) {
|
|
24
|
+
void (async () => {
|
|
25
|
+
const plan = await ctx.get(DSHELL_DATA_ROOT_SERVICE).settled;
|
|
26
|
+
const path = join(plan.root, 'usage', INDEX_FILE);
|
|
27
|
+
const store = openUsageStore(path);
|
|
28
|
+
ctx.effect(() => () => { closeUsageStore(path); }, 'dshell-usage: close the index');
|
|
29
|
+
// The scanner is built once the query engine is up, and the route is
|
|
30
|
+
// registered from the same injection: both need it, and neither can be
|
|
31
|
+
// constructed before it exists.
|
|
32
|
+
ctx.inject(['sessionQuery', 'connection'], (usageCtx) => {
|
|
33
|
+
const query = usageCtx.get('sessionQuery');
|
|
34
|
+
const scanner = new UsageScanner(store, query);
|
|
35
|
+
usageCtx.effect(() => () => { scanner.dispose(); }, 'dshell-usage: stop the scanner');
|
|
36
|
+
usageCtx.effect(() => usageCtx.connection.fetch.register(createUsageRoute({ store, scanner })), 'dshell-usage: usage route');
|
|
37
|
+
console.info(`dshell-usage: index at ${path}`);
|
|
38
|
+
});
|
|
39
|
+
})();
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,OAAO,EAAE,wBAAwB,EAA2B,MAAM,0BAA0B,CAAA;AAC5F,OAAO,EAAE,eAAe,EAAE,cAAc,EAAmB,MAAM,kBAAkB,CAAA;AACnF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,EAAE,YAAY,EAA0B,MAAM,WAAW,CAAA;AAIhE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,wBAAwB,CAAU,CAAA;AAEzD,wDAAwD;AACxD,MAAM,UAAU,GAAG,cAAc,CAAA;AAEjC,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,MAAM,IAAI,GAAG,MAAO,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAwB,CAAC,OAAO,CAAA;QACpF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,CAAA;QACjD,MAAM,KAAK,GAAe,cAAc,CAAC,IAAI,CAAC,CAAA;QAC9C,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA,CAAC,CAAC,EAAE,+BAA+B,CAAC,CAAA;QAElF,qEAAqE;QACrE,uEAAuE;QACvE,gCAAgC;QAChC,GAAG,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE;YACtD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAiC,CAAA;YAC1E,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;YAC9C,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAA,CAAC,CAAC,EAAE,gCAAgC,CAAC,CAAA;YACpF,QAAQ,CAAC,MAAM,CACb,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,EAC9E,2BAA2B,CAC5B,CAAA;YACD,OAAO,CAAC,IAAI,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAA;QAChD,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,EAAE,CAAA;AACN,CAAC"}
|
package/lib/protocol.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The usage index's wire and storage shapes.
|
|
3
|
+
*
|
|
4
|
+
* One request type and one response type, because the page both reads the
|
|
5
|
+
* aggregate and asks for a scan through the same endpoint — the scan belongs to
|
|
6
|
+
* the host, which is where the session events already are.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/lib/route.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The usage route: one exact `/api` endpoint behind dsh's existing trust and
|
|
3
|
+
* authentication fence, mirroring the pipe and files routes' shape.
|
|
4
|
+
*
|
|
5
|
+
* Both halves of the feature go through it. Reading is the common case; the
|
|
6
|
+
* scan is offered here too because it is the host that owns the session events
|
|
7
|
+
* and the index, so the browser asks for one rather than doing one.
|
|
8
|
+
*/
|
|
9
|
+
import { DSHELL_USAGE_PATH } from '@nexus-aethra/dshell-std';
|
|
10
|
+
/** JSON response in the shape the page parses. */
|
|
11
|
+
function respond(body, status = 200) {
|
|
12
|
+
return new Response(JSON.stringify(body), {
|
|
13
|
+
status,
|
|
14
|
+
headers: { 'content-type': 'application/json' },
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** The window a request asks for, clamped so a hostile number cannot ask for a century. */
|
|
18
|
+
function windowOf(days) {
|
|
19
|
+
if (typeof days !== 'number' || !Number.isFinite(days) || days <= 0)
|
|
20
|
+
return null;
|
|
21
|
+
return Math.min(Math.trunc(days), 3650);
|
|
22
|
+
}
|
|
23
|
+
/** Bind the route to the index and the scanner. */
|
|
24
|
+
export function createUsageRoute(deps) {
|
|
25
|
+
const answer = async (request) => {
|
|
26
|
+
if (request.method === 'GET')
|
|
27
|
+
return deps.store.summary(null);
|
|
28
|
+
const input = await request.json();
|
|
29
|
+
const days = windowOf(input.days);
|
|
30
|
+
if (input.action === 'scan') {
|
|
31
|
+
const outcome = await deps.scanner.rescan();
|
|
32
|
+
// A rebuild is what the reader is waiting for, so the summary that
|
|
33
|
+
// answers it is read after the scan, not before — and it carries the
|
|
34
|
+
// scan's own counts, which the page reports.
|
|
35
|
+
return {
|
|
36
|
+
...deps.store.summary(days),
|
|
37
|
+
scanned: {
|
|
38
|
+
sessions: outcome.sessions,
|
|
39
|
+
read: outcome.read,
|
|
40
|
+
turns: outcome.turns,
|
|
41
|
+
skipped: outcome.skipped,
|
|
42
|
+
...outcome.skipReason === undefined ? {} : { skipReason: outcome.skipReason },
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return deps.store.summary(days);
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
path: DSHELL_USAGE_PATH,
|
|
50
|
+
methods: ['GET', 'POST'],
|
|
51
|
+
requestBody: 'buffered',
|
|
52
|
+
fetch: async (request) => {
|
|
53
|
+
try {
|
|
54
|
+
return respond(await answer(request));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
58
|
+
return respond({ error: reason }, 400);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=route.js.map
|
package/lib/route.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAW5D,kDAAkD;AAClD,SAAS,OAAO,CAAC,IAAmB,EAAE,MAAM,GAAG,GAAG;IAChD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC;AAED,2FAA2F;AAC3F,SAAS,QAAQ,CAAC,IAAa;IAC7B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAChF,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;AACzC,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,gBAAgB,CAAC,IAAoB;IACnD,MAAM,MAAM,GAAG,KAAK,EAAE,OAAgB,EAA0B,EAAE;QAChE,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAC7D,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,EAAkB,CAAA;QAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAA;YAC3C,mEAAmE;YACnE,qEAAqE;YACrE,6CAA6C;YAC7C,OAAO;gBACL,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBAC3B,OAAO,EAAE;oBACP,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,GAAG,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE;iBAC9E;aACF,CAAA;QACH,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC,CAAA;IAED,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,UAAU;QACvB,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACvB,IAAI,CAAC;gBACH,OAAO,OAAO,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;YACvC,CAAC;YACD,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;gBACrE,OAAO,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAA;YACxC,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|