Repository navigation
Conversation
A local, read-only token-usage dashboard for MiniMax Code. It reads the runtime's own per-call usage table and observability logs, so it shows the numbers the client actually recorded rather than an estimate reconstructed from session transcript files. - Usage trend with per-model stacked bars and cache-hit-rate / output-speed curves; five detail tables (per-model, daily, a 12-column per-call ledger, a turn-level conversation table, session ranking). - Output speed uses the same basis as the client interface: Sigma output tokens / Sigma decode duration, time-weighted, with P50/P90. - Every call is paired with its own decode and request durations. The pairing rule was verified against a token-signature ground truth over 16,723 calls with zero mismatches. - No Python, no extra process in the normal path, no third-party runtime code except a vendored copy of Chart.js (MIT, full licence text included). - Strictly read-only: no files written, no network, no telemetry. Query parameters are an explicit per-endpoint allow-list. Includes the English README with the required "## Tested environment" and "## Data & access" sections, a Chinese README, an Apache-2.0 LICENSE, a synthetic-data preview image, and a validation record.
MyPrototypeWhat
left a comment
There was a problem hiding this comment.
Hi @HaneWari,欢迎第一次向本仓库投稿!逐次调用台账、turn 级对话明细、以及和客户端同口径的输出速率,方向很清楚;文档也写得很完整(Data & access、与另外两块看板的对照、ENTRY_STDOUT 的说明、合成数据预览)。在 0414d527b9d9d53a2701ed7ab7153643e9588286 上本地看过:范围只有 plugins/hanewari/token-meter-board/ 和两个根 README 各一行;Apache-2.0 + 内置 Chart.js 的 MIT 文本齐全;代码路径上确认是 DatabaseSync(..., { readOnly: true }),没有对宿主文件的写入;npm run check 对本包是 0 errors, 1 warning(就是你说明过的 db-child.mjs stdout)。
不过有一项仓库红线,这一版没法合入。
必须修改
- 请去掉对 MiniMax Code 内部数据库的直接访问(只读也不行)
社区插件不能直接打开 MiniMax Code 的内部数据库文件,读、写都不允许。PR / README 里写的数据来源,和代码行为是一致的,并不是「文档写了、代码其实没开」:
miniapp/node/server.mjs:24-34:从context.dataDir向上最多 8 层,专门查找宿主数据目录里的内部数据库文件,再落到resolveDataDir()。miniapp/node/lib/common.mjs:39-55:dbPath/openDb用node:sqlite的DatabaseSync打开该库(readOnly: true)。- 查询侧实际读到的表包括用量台账、消息行、会话行:
common.mjs:74-78(local_runtime_token_usage)、facts.mjs:46-53(local_runtime_message_rows,并在 SQL 里substr出用户消息前 200 字)、queries.mjs:67(local_runtime_sessions的title/workspace_dir)。 - 同一条数据路径还顺带读了宿主观测日志与配置:
common.mjs:304(v2/observability/logs)、common.mjs:137-158(config.yaml,整文件读入再解析显示名)。
因此「只读」「不出网」「参数白名单挡住 dataDir」这些加固都成立,也改变不了:插件在打开并查询宿主内部库。请整条数据路径拿掉(数据库、为关联模型/状态而扫的观测日志、以及为显示名去读的宿主 config.yaml)。没有这条路径之后,看板需要改成不依赖宿主内部库的数据来源(例如画廊里已有、基于会话记录文件的做法),或先收缩到不碰宿主内部库的功能;README 的 Data & access 也请一并改到与真实行为一致。
页面实际会带出的内容(在去掉上述访问之前,也请心里有数——这些都来自那座库):对话表明细的 userPreview(用户消息前 200 字,facts.mjs:36 / queries.mjs:308)、会话标题与 id、会话排行 tooltip 里的绝对工作区路径(queries.mjs:392)。用户若把密钥或路径贴进对话,前 200 字会出现在本机页面的 JSON 响应里。
建议(不阻塞;当前红线未解决前可先放着)
/api/health与查询meta带绝对dataDir(server.mjs:173,queries.mjs:98):成功路径也会把宿主数据目录绝对路径送给页面。若以后有不依赖内部库的版本,建议改成 basename,或干脆不返回。- 报错文案里的绝对路径(例如
common.mjs:52的not found at ${file}):同样建议脱敏。 - Host / Origin:入口目前只校验 GET。可参考仓库里已有插件对 loopback
Host(以及写操作时的Origin)的检查,降低 DNS rebinding / 跨站误打到本机端口的风险。 ENTRY_STDOUT:你对db-child.mjs的说明和minimax-photo的处理方式一致,这条 warning 可以保留;若整条子进程读库路径随红线一起删掉,warning 也会自然消失。
谢谢投稿。去掉对宿主内部数据库(以及同路径上的观测日志 / 宿主配置)的访问之后,很乐意再看一版。
| if (envDir && fs.existsSync(envDir)) return path.resolve(envDir); | ||
| let dir = contextDataDir ? path.resolve(contextDataDir) : null; | ||
| for (let i = 0; dir && i < 8; i += 1) { | ||
| if (fs.existsSync(path.join(dir, 'v2', 'sqlite', 'runtime-state.sqlite'))) return dir; |
There was a problem hiding this comment.
必改: 这里从插件 dataDir 向上查找并定位 MiniMax Code 的内部数据库文件。社区插件不能直接打开宿主内部数据库(只读也不行),请去掉整条数据路径。
| return { db: null, error: `runtime state database not found at ${file} — is this a MiniMax Code data directory with recorded usage?` }; | ||
| } | ||
| try { | ||
| return { db: new sqlite.DatabaseSync(file, { readOnly: true }), error: null }; |
There was a problem hiding this comment.
必改: DatabaseSync 直接打开了 MiniMax Code 的内部数据库。请删除 openDb / dbPath 以及对宿主内部库的查询;只读模式不能作为例外。
| ${jsonField('$.usage.request_duration_ms', "IN ('integer','real')")} AS request_ms, | ||
| ${jsonField('$.finish_reason', "= 'text'")} AS finish_reason, | ||
| substr(${jsonField('$.msg_content', "= 'text'")}, 1, ${USER_PREVIEW_CHARS}) AS msg_preview | ||
| FROM local_runtime_message_rows |
There was a problem hiding this comment.
必改: 这里全表扫描宿主内部库的消息行,并取出用户消息前 200 字。请不要读宿主内部库;对话预览也不能再依赖这条路径。
| function loadSessionTitles(db) { | ||
| const titles = new Map(); | ||
| try { | ||
| for (const r of db.prepare('SELECT session_id, title, agent_name, workspace_dir FROM local_runtime_sessions').all()) { |
There was a problem hiding this comment.
必改: 从宿主内部库的会话表读取 title / workspace_dir。请去掉对宿主内部库的访问;绝对工作区路径也不应再经插件 API 带出。
| const maxBytesPerFile = options.maxBytesPerFile ?? 12 * 1024 * 1024; | ||
| const now = options.now ?? Date.now(); | ||
| const map = new Map(); | ||
| const dir = path.join(dataDir, 'v2', 'observability', 'logs'); |
There was a problem hiding this comment.
必改: 观测日志属于宿主内部存储,且是为补齐内部库里没有的模型名而扫的。请与内部库访问一并移除。
| let catalogCache = null; | ||
|
|
||
| export function loadCatalog(dataDir) { | ||
| const file = path.join(dataDir, 'config.yaml'); |
There was a problem hiding this comment.
必改: 这里整文件读取宿主 config.yaml(可能含自定义 provider 配置)。请不要读宿主配置文件;显示名需改用不依赖宿主内部文件的方式,或先去掉。
| sqliteInProcess, | ||
| queryMode: childMode === true ? 'child' : 'in-process', | ||
| }, | ||
| dataDir, |
There was a problem hiding this comment.
建议:/api/health 返回了绝对 dataDir。若以后还有健康检查,请只返回 basename,或省略该字段。
按评审意见(changes requested)把整条数据路径拿掉。 社区插件不能打开 MiniMax Code 的内部数据库,读写都不允许。本版不再访问: - v2/sqlite/runtime-state.sqlite - v2/observability/logs/ - config.yaml 数据源改为客户端为用户自己的对话写下的会话记录: v2/sessions/**/messages.jsonl + manifest.json。路径解析与画廊里已合入的 mcode-token-usage-board 同一套(自 context.dataDir 向上找 v2/sessions, 回落 ~/.minimax),标题规则也一致(首条用户消息前 80 字,剥离 system reminder)。 删除 lib/common.mjs、lib/trend.mjs、lib/facts.mjs、lib/db-child.mjs。 包内不再 import node:sqlite,不再派生任何子进程。随子进程回退一起消失的 ENTRY_STDOUT warning 自然消失:官方校验器现为 0 errors / 0 warnings。 新增 lib/sessions.mjs:按文件字节游标 + 首部哈希增量解析,message_id 全局去重, 扫描游标原子写入插件自己的 dataDir。重写 lib/queries.mjs 与 server.mjs: 逐端点参数白名单、loopback Host/Origin 校验(参照 repo-xray)、报错文案路径脱敏。 因此失去的能力(如实说明):会话记录里没有耗时,也没有工作区路径。 - 取消输出速率与 P50/P90 - 会话排行不再有绝对工作区路径 - 取消每轮用户消息前 200 字预览 - 模型显示名改用 transcript 里的原始模型 id 保留并改进:token 用量、缓存命中率、按模型分布、按天趋势、逐次调用台账、 对话明细与会话排行。对话状态改由 stopReason 推导(stop=已完成 / toolUse=未收尾 / aborted=已中止 / 无调用=进行中),KPI 增加「对话次数」。 扫描游标持久化后,全量扫描约 2.0 秒,之后每次增量刷新约 170 毫秒。 覆盖率改为同窗对比:会话记录 8,422 次调用 / 1,133,235,393 tokens 对内部账本 13,124 次 / 2,099,198,213 tokens,覆盖率 64.2% / 54.0%;缺口是被压缩掉的调用, 且账本自身保留期更短(最早一行 2026-09-28)。 README(中英)重写 Data & access 并与真实行为对齐,根 README 作品表格那一行 同步更新;docs/validation.md 重写为 2.0.0 验证记录,含评审意见逐条对照表; docs/preview.png 重新截取(合成数据,无速率列)。
|
@MyPrototypeWhat 谢谢评审,这条红线是对的——社区插件确实不该打开宿主内部数据库,哪怕只读。已按意见把整条数据路径拿掉,新提交 改了什么数据源换成会话记录文件,就是评审里建议的那条路:画廊里已合入的 于是这三个路径任何模式都不会被打开:
七条内联意见逐条
另外三条建议也一并采纳了:报错文案统一脱敏(绝对路径在出页面前替换为 因此失去的能力会话记录里没有耗时,也没有工作区路径,所以这几项取消了,不留空列:
保留并改进的是 token 用量、缓存命中率、按模型分布、按天趋势、逐次调用台账、对话明细与会话排行。对话状态改由 覆盖率(请重点看这个,它决定了看板还能不能叫"实时看板")会话记录不是完整账本——长对话被压缩时,那一段会被换成摘要,其中的调用就不在任何 参考机器实测(2026-10-23):会话记录侧 8,640 次调用 / 151 个会话(162 个文件、343.8 MiB);内部账本侧在它自己保留的窗口(2026-09-28 至今)里有 13,124 次调用。限制在同一窗口比:
反方向也有:账本最早一行是 2026-09-28,比会话记录晚三个多月,它自己已经过了保留期,那段时间的 220 次调用只剩会话记录还留着。 所以 README 里写明了:这些数字是"你的会话记录里还留着多少",不是账单,计费与配额仍以客户端自己的"用量"页面为准。 验证
完整的评审意见对照表、覆盖率与性能测量过程都写在包内 麻烦再看一下,尤其是 §覆盖率 那段——如果 64% 的调用覆盖率在评审看来仍然偏低,也可以讨论是否该把这个功能再收缩一档。 |
|
感谢 @MyPrototypeWhat 的评审。评审提出的红线是对的:社区插件不应打开宿主内部数据库,读写都不允许。 在收到评审后我已经按意见做了一版整改(本地版本号 2.0.0):数据源从 不过这次整改的代价是:会话记录里没有 decode / 请求耗时,也没有工作区目录,所以逐次调用台账从 12 列缩到 8 列,输出速率、P50/P90、decode / 请求全程时长、HTTP 状态码、逐轮用户消息预览全部无法保留;覆盖率相对内部账本约为调用 64.2% / token 54.0%。 考虑到这块看板的主要价值恰在其中一列,而这版又是在一个未公开的存储格式上做的权衡,作者决定不再推进市场投稿,改为本地自用,因此在此关闭本 PR。 已实现的这版整改不会浪费:它保留在作者自己的工作仓库里。如果日后有社区插件的合规路径被官方明确(例如官方提供只读 API 或允许的导出格式),可以再拿它来做讨论。评审中提到的几点——社区插件的数据访问边界、 再次感谢评审的时间与细致核对,尤其是逐条指出每一条访问路径的位置。 |
|
Closing per the author's decision to keep this dashboard for local use. |
中文摘要
提交 Token 仪表盘(插件 ID
token-meter-board):本机 MiniMax Code 的实时 token 用量看板。只读客户端为用户自己的对话写下的会话记录文件(v2/sessions/**/messages.jsonl),不打开运行时内部数据库、不扫观测日志、不读宿主配置。纯 Node,无需 Python,无子进程,全程只读、不出网、无遥测。界面为中文,README 提供英文与中文两版。预览图为合成数据实拍,不含任何真实会话内容。
评审意见逐条回应
评审结论为
changes requested,7 条内联意见全部处理如下。DatabaseSync直接打开内部数据库,"只读模式不能作为例外"(common.mjs:55)lib/common.mjs已删除。包内不再有任何import ... from 'node:sqlite'。回归测试对产物源码做静态断言,node:sqlite/DatabaseSync/v2/sqlite/runtime-state一处都不出现。dataDir向上查找并定位宿主内部数据库文件(server.mjs:29)v2/sessions,数据库定位逻辑整段移除。解析顺序:MINIMAX_DATA_DIR→ 自宿主注入的context.dataDir向上最多 10 层找v2/sessions→ 回落~/.minimax/v2/sessions。facts.mjs:53)lib/facts.mjs已删除。逐轮用户消息预览已从页面移除,对话表改用「所属会话」标识一轮。title/workspace_dir;绝对工作区路径不应经 API 带出(queries.mjs:67)mcode-token-usage-board同一规则,system reminder 会被剥离);会话排行与所有响应体不含任何绝对路径。common.mjs:304)v2/observability/logs/。模型显示名改用 transcript 记录的原始模型 id,不再去别处补齐。config.yaml(common.mjs:138)config.yaml。/api/health返回绝对dataDir,请只返回 basename(server.mjs:173)sessionsDir: "sessions"。查询meta不再携带任何路径字段。redactPaths(),页面收到的错误文案中绝对路径被替换为<path>;完整原文只进 Host 自己的日志。Host/Origin检查isLoopbackRequest(),与画廊plugins/yujiachen-y/repo-xray的实现逐行同款:Host不指向回环地址、或Origin不指向本监听端口 → 403。ENTRY_STDOUTwarning 若随子进程路径一起删掉会自然消失lib/db-child.mjs已删除,warning 自然消失。官方校验器现为 0 errors / 0 warnings。因此失去的能力(如实说明)
会话记录里没有耗时,也没有工作区路径,所以本版相应取消:
config.yaml的模型显示名(表里写原始模型 id)保留并改进的是:token 用量、缓存命中率、按模型分布、按天趋势、逐次调用台账、对话(turn)明细与会话排行。对话状态改由 transcript 的
stopReason推导(stop→ 已完成,toolUse→ 未收尾,aborted→ 已中止,尚无模型调用 → 进行中),KPI 新增「对话次数」。为什么数据源换成会话记录
评审建议"例如画廊里已有、基于会话记录文件的做法",本包采用的就是这一条:画廊里已合入的
plugins/amszuidas/mcode-token-usage-board读的就是v2/sessions/**/messages.jsonl+manifest.json,路径解析与标题规则都与它一致。What it does
A local, read-only token-usage dashboard for MiniMax Code. It reads the session records the client writes for your own conversations — the same source the gallery's
mcode-token-usage-boarduses — and nothing else. It never opens the runtime's internal database, its observability logs or its configuration file.Coverage: what the transcripts do not keep
Session records are not a complete accounting. When a long conversation is compacted, the runtime replaces the compacted part of the transcript with a summary, and the calls inside that part are no longer in any
messages.jsonl.Measured on a reference machine (2026-10-23): the board counts 8,640 calls across 151 sessions (162 transcript files, 343.8 MiB), covering 2026-06-17 onwards. The client's own internal ledger — which this board does not read — held 13,124 calls over the window it keeps (2026-09-28 onwards).
Restricting both sides to the same window:
The difference is calls that were compacted out, not calls that were mis-counted. It works the other way round too: the ledger's earliest row is 2026-09-28, more than three months after the transcripts begin — the ledger itself has passed its retention window, and those 220 calls survive only in the session records.
So these numbers are what your session records still contain, not a bill; billing and quota remain the product's own "usage" page.
How it differs from the two token boards already in the gallery
mcode-token-usage-boardmcode-usage-monitormessages.jsonl)messages.jsonl)The practical difference against
mcode-token-usage-board: same data source, but this board adds a per-call ledger, a turn-level table with live status, and incremental reading — the transcripts are ~344 MiB on a reference machine, so re-reading them on every refresh is what that board has to do.Against
mcode-usage-monitor: that board reads the runtime's internal database through Python, which is exactly the access the review rules out for community plugins. This board does not, and does not need Python.Install and open
Copy
plugins/hanewari/token-meter-board/into your MiniMax Code data directory (by default~/.minimax/plugins/token-meter-board/), keep the hidden.minimax-plugindirectory, restart MiniMax Code, confirm the plugin is enabled, and open Token 仪表盘 — or just ask the Agent to open it. Full instructions are in the package README.Data & access
Files read (read-only, never modified):
<dataDir>/v2/sessions/**/messages.jsonl<dataDir>/v2/sessions/**/manifest.jsonNothing else is read. The runtime's internal database (
v2/sqlite/runtime-state.sqlite), its observability logs (v2/observability/logs/) and its configuration file (config.yaml) are not opened, in any mode.What the page displays (so screenshots can carry more than you might expect): session titles — the first 80 characters of your first message in that session, with injected system reminders removed, the same rule the gallery's token-usage board uses — plus session ids and model ids. No message body, no per-turn preview, no absolute path.
Written: exactly one file — a scan cursor at
<plugin data dir>/session-scan-state.json, written atomically (temp file + rename) when transcripts changed. It holds file paths, session ids, session titles and per-call counters so that a restart resumes instead of re-reading everything. It is a cache: deleting it only costs one full rescan. Browser storage holds exactly one thing: the theme choice, under the keytmb-theme.Network: none. The Mini App serves one page on
127.0.0.1and answers its own API routes; it makes no request other than to its own origin. Query parameters are an explicit per-endpoint allow-list, so a crafted query string cannot reach the query layer with an unexpected key — including anything that looks like a data-directory override. Requests whoseHostheader does not name the loopback interface, or whoseOriginheader does not name this listener, are refused with 403.Subprocess: none. No process is spawned and no shell is used.
Node.js: any Node.js that ships
node:fs/promisesandnode:http— no experimental flag and no native module is needed. The runtime used for testing is Node.js v24.18.0.Tested environment
context.dataDir— but please treat the other platforms as untested.Validation results
npm run validate→plugins/hanewari/token-meter-board: 0 errors, 0 warnings, and the repository-level check reportsrepository: OK. TheENTRY_STDOUTwarning is gone together with the child-process path that caused it.npm test→ 67 of 69 pass. The two failures are not caused by this package: they are the repository's owntest/files.test.mjscases that create a symbolic link in a temporary fixture to prove the validator rejects one. On this Windows accountfs.symlinkreturnsEPERM(Developer Mode is off), so the fixture cannot be created — reproduced on a pristine upstream checkout with no package of mine in it. CI runs onubuntu-latest, where symlinks are available.v2/sqlite,runtime-state,node:sqlite,DatabaseSync,v2/observability,config.yaml,workspace_dir,workspaceDir,userPreview.Performance (162 files / 343.8 MiB, real HTTP):
The reader keeps a byte cursor and a head hash per file, so it only parses what was appended since the last scan. The cursor is persisted in the plugin's own data directory, so even a cold start resumes instead of re-reading everything. Endpoints answer in 2–17 ms once the aggregate is warm.
Details, including the full review-comment mapping table, are in the package's
docs/validation.md.Third-party components
Chart.js v4.4.3 — vendored at
miniapp/client/vendor/chart.umd.min.jsfor offline use. MIT License, © Chart.js Contributors. The full licence text ships with the package atminiapp/client/vendor/LICENSE-chartjs.txt.License
Apache-2.0. The vendored Chart.js copy keeps its own MIT licence, reproduced in
miniapp/client/vendor/LICENSE-chartjs.txt.