@ychris12138/dsh-usage-stats 0.2.7 → 0.2.9

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/README.md CHANGED
@@ -53,8 +53,8 @@ dsh plugin --profile web add "github:Ychris12138/dsh-usage-stats"
53
53
  升级或卸载:
54
54
 
55
55
  ```bash
56
- dsh plugin --profile web update dsh-usage-stats
57
- dsh plugin --profile web remove dsh-usage-stats
56
+ dsh plugin --profile web update "@ychris12138/dsh-usage-stats"
57
+ dsh plugin --profile web remove "@ychris12138/dsh-usage-stats"
58
58
  ```
59
59
 
60
60
  <details>
@@ -66,7 +66,7 @@ PowerShell、命令提示符和 macOS/Linux 终端使用同一条命令:
66
66
  npx --yes github:Ychris12138/dsh-usage-stats
67
67
  ```
68
68
 
69
- 安装器会把运行文件复制到 `~/.dsh/profiles/node_modules/dsh-usage-stats`,并在 `profiles/web/cordis.patch.yml` 中幂等启用插件。重复运行即可更新,不会重复追加配置。设置了 `DSH_HOME` 时使用该目录。
69
+ 安装器会把运行文件复制到 `~/.dsh/profiles/node_modules/@ychris12138/dsh-usage-stats`,并在 `profiles/web/cordis.patch.yml` 中以带引号的 scoped identity 幂等启用插件。重复运行即可更新,不会重复追加配置;旧版 `name: dsh-usage-stats` 和未加引号的 `name: @ychris12138/dsh-usage-stats` 会自动迁移。设置了 `DSH_HOME` 时使用该目录。
70
70
 
71
71
  `dsh plugin` 与 `npx` 是两条独立安装路径,请选择其中一种;不要同时保留手工 Cordis entry 和 bundle 注册,否则会重复挂载。
72
72
 
@@ -147,7 +147,7 @@ Z.ai 全球区使用 `api.z.ai`,中国区使用 `open.bigmodel.cn`。MiniMax
147
147
 
148
148
  ### New API、Sub2API 与自定义 monitor
149
149
 
150
- 在现有 `name: dsh-usage-stats` Cordis entry 下合并 `config`,不要追加第二个插件 entry。monitor 键必须是 Harness 中真实存在的 provider id;未知 provider、adapter 或非法映射会在路由和 timer 注册前阻止插件启动。例外:monitor 同时显式提供 `usageBaseURL` 与 `credentialRef` 时视为自包含,会在 provider 注册可见前临时物化为 provider(适用于 Harness 设置页里后加载的 provider),此时不要求该 provider 已出现在注册表中。
150
+ 在现有 `name: "@ychris12138/dsh-usage-stats"` Cordis entry 下合并 `config`,不要追加第二个插件 entry。monitor 键必须是 Harness 中真实存在的 provider id;未知 provider、adapter 或非法映射会在路由和 timer 注册前阻止插件启动。例外:monitor 同时显式提供 `usageBaseURL` 与 `credentialRef` 时视为自包含,会在 provider 注册可见前临时物化为 provider(适用于 Harness 设置页里后加载的 provider),此时不要求该 provider 已出现在注册表中。
151
151
 
152
152
  <details>
153
153
  <summary><strong>展开 monitor 配置示例</strong></summary>
@@ -158,7 +158,7 @@ New API 默认用 provider 推理 Token 查询 `/api/usage/token/`,并从 `/ap
158
158
  # ~/.dsh/profiles/web/cordis.patch.yml
159
159
  - insert:
160
160
  - id: usage-stats
161
- name: dsh-usage-stats
161
+ name: "@ychris12138/dsh-usage-stats"
162
162
  config:
163
163
  monitors:
164
164
  relay-a: # Harness provider id
@@ -264,7 +264,7 @@ Constraints:
264
264
 
265
265
  Procedure:
266
266
  1. Confirm node, npx, and dsh are available.
267
- 2. Prefer `dsh plugin --profile web update dsh-usage-stats` when already installed; otherwise use `dsh plugin --profile web add "github:Ychris12138/dsh-usage-stats"`.
267
+ 2. Prefer `dsh plugin --profile web update "@ychris12138/dsh-usage-stats"` when already installed; otherwise use `dsh plugin --profile web add "github:Ychris12138/dsh-usage-stats"`.
268
268
  3. If unavailable, use: npx --yes github:Ychris12138/dsh-usage-stats
269
269
  4. Do not combine bundle installation with an existing manual dsh-usage-stats Cordis entry.
270
270
  5. For npx, require a verified package and exactly one Cordis entry, then run again with --check.
package/lib/client.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * half's loopback-only endpoints via same-origin fetch.
9
9
  */
10
10
  window.__ModuleLoader__.load({
11
- id: "dsh-usage-stats",
11
+ id: "@ychris12138/dsh-usage-stats",
12
12
  factory: (require) => {
13
13
  var module = { exports: {} };
14
14
  var exports = module.exports;
@@ -139,7 +139,7 @@ window.__ModuleLoader__.load({
139
139
  const tagId = "dsh-usage-stats/UsageStats.module.css";
140
140
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
141
141
  const tag = document.createElement("style");
142
- tag.dataset.plugin = "dsh-usage-stats";
142
+ tag.dataset.plugin = "@ychris12138/dsh-usage-stats";
143
143
  tag.dataset.pluginCss = tagId;
144
144
  tag.textContent = css;
145
145
  document.head.appendChild(tag);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ychris12138/dsh-usage-stats",
3
3
  "description": "Token usage heatmap, provider balances, and subscription quotas for the dsh web GUI",
4
- "version": "0.2.7",
4
+ "version": "0.2.9",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/Ychris12138/dsh-usage-stats.git"
@@ -33,13 +33,24 @@ Set DSH_HOME to override the default ~/.dsh location.`);
33
33
  const sourceRoot = dirname(dirname(fileURLToPath(import.meta.url)));
34
34
  const sourcePackage = JSON.parse(await readFile(join(sourceRoot, "package.json"), "utf8"));
35
35
  const dshHome = process.env.DSH_HOME ?? join(homedir(), ".dsh");
36
- const target = join(dshHome, "profiles", "node_modules", "dsh-usage-stats");
36
+ const packagePath = sourcePackage.name.split("/");
37
+ if (packagePath.some((part) => part === "" || part === "." || part === "..")) {
38
+ throw new Error(`invalid package name: ${sourcePackage.name}`);
39
+ }
40
+ const target = join(dshHome, "profiles", "node_modules", ...packagePath);
37
41
  const patchPath = join(dshHome, "profiles", "web", "cordis.patch.yml");
38
- const pluginLine = /^\s+name:\s*dsh-usage-stats\s*$/gm;
42
+ const legacyPackageName = sourcePackage.name.split("/").at(-1);
43
+ const quotedPackageName = JSON.stringify(sourcePackage.name);
44
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45
+ const scopedNamePattern = escapeRegExp(sourcePackage.name);
46
+ const legacyNamePattern = escapeRegExp(legacyPackageName);
47
+ const pluginLine = new RegExp(`^\\s+name:\\s*(?:"${scopedNamePattern}"|'${scopedNamePattern}')\\s*$`, "gm");
48
+ const unquotedScopedPluginLine = new RegExp(`^(\\s+name:\\s*)${scopedNamePattern}\\s*$`, "gm");
49
+ const legacyPluginLine = new RegExp(`^(\\s+name:\\s*)(?:"${legacyNamePattern}"|'${legacyNamePattern}'|${legacyNamePattern})\\s*$`, "gm");
39
50
  const patchBlock = `# dsh-usage-stats: token usage heatmap + DeepSeek balance
40
51
  - insert:
41
52
  - id: usage-stats
42
- name: dsh-usage-stats
53
+ name: ${quotedPackageName}
43
54
  `;
44
55
  const emptySequenceRoot = /^\[\](?:[ \t]+#.*)?$/;
45
56
 
@@ -76,10 +87,26 @@ function assertNoEmptyRootConflict(text) {
76
87
  }
77
88
  }
78
89
 
90
+ function countMatches(text, pattern) {
91
+ return [...String(text).matchAll(pattern)].length;
92
+ }
93
+
94
+ /** Migrate the pre-scope entry and repair an invalid unquoted scoped YAML value. */
95
+ function normalizePluginIdentity(text) {
96
+ const scopedCount = countMatches(text, pluginLine);
97
+ const legacyCount = countMatches(text, legacyPluginLine) + countMatches(text, unquotedScopedPluginLine);
98
+ if (scopedCount + legacyCount > 1) {
99
+ throw new Error(`multiple dsh-usage-stats entries in ${patchPath}; keep one ${quotedPackageName} entry`);
100
+ }
101
+ return String(text)
102
+ .replace(legacyPluginLine, (_, prefix) => `${prefix}${quotedPackageName}`)
103
+ .replace(unquotedScopedPluginLine, (_, prefix) => `${prefix}${quotedPackageName}`);
104
+ }
105
+
79
106
  /** Preserve existing YAML/comments while adding exactly one plugin patch entry. */
80
107
  function enablePluginInPatch(text) {
81
- const base = withoutEmptySequenceRoot(text);
82
- if ([...base.matchAll(pluginLine)].length > 0) return base;
108
+ const base = normalizePluginIdentity(withoutEmptySequenceRoot(text));
109
+ if (countMatches(base, pluginLine) > 0) return base;
83
110
  return base.trim() === "" ? patchBlock : `${base.trimEnd()}\n\n${patchBlock}`;
84
111
  }
85
112
 
@@ -100,10 +127,12 @@ async function verify(expectEnabled) {
100
127
  throw new Error(`installed package is ${installed.name ?? "unknown"}@${installed.version ?? "unknown"}; expected ${sourcePackage.name}@${sourcePackage.version}`);
101
128
  }
102
129
  if (expectEnabled) {
103
- const patch = await readOptional(patchPath);
104
- const count = patch === null ? 0 : [...patch.matchAll(pluginLine)].length;
105
- if (count !== 1) throw new Error(`expected exactly one dsh-usage-stats entry in ${patchPath}; found ${count}`);
130
+ const patch = await readOptional(patchPath) ?? "";
106
131
  assertNoEmptyRootConflict(patch);
132
+ const count = countMatches(patch, pluginLine);
133
+ if (count !== 1) throw new Error(`expected exactly one dsh-usage-stats entry in ${patchPath}; found ${count}`);
134
+ if (countMatches(patch, legacyPluginLine) > 0) throw new Error(`legacy package name ${legacyPackageName} remains in ${patchPath}`);
135
+ if (countMatches(patch, unquotedScopedPluginLine) > 0) throw new Error(`scoped package name must be quoted in ${patchPath}`);
107
136
  }
108
137
  console.log(`Verified ${sourcePackage.name}@${sourcePackage.version}`);
109
138
  console.log(` package: ${target}`);
@@ -139,4 +168,4 @@ if (enable) {
139
168
 
140
169
  await verify(enable);
141
170
  console.log("Installation complete. Restart dsh web, then hard-refresh the browser.");
142
- console.log("Balance is optional: configure DEEPSEEK_API_KEY in <DSH_HOME>/.credentials.yaml.");
171
+ console.log("Balance is optional: configure DEEPSEEK_API_KEY in <DSH_HOME>/.credentials.yaml.");