dsh-skill-browser 1.9.9 → 1.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README-es.md +1 -1
  3. package/README-hi.md +1 -1
  4. package/README-pt.md +1 -1
  5. package/README-zh.md +4 -4
  6. package/README.md +75 -68
  7. package/docs/images/panel-browse.png +0 -0
  8. package/docs/images/panel-ledger.png +0 -0
  9. package/lib/client.js +256 -148
  10. package/lib/client.js.bak-6colimp-20261028-230915 +3748 -0
  11. package/lib/client.js.bak-auth12-20261029-020107 +3749 -0
  12. package/lib/client.js.bak-auth12b-20261029-020140 +3749 -0
  13. package/lib/client.js.bak-auth12c-20261029-020206 +3749 -0
  14. package/lib/client.js.bak-auth12d-20261029-020232 +3747 -0
  15. package/lib/client.js.bak-auto-20261028-171351 +3669 -0
  16. package/lib/client.js.bak-auto2-20261028-171422 +3676 -0
  17. package/lib/client.js.bak-auto3-20261028-171545 +3676 -0
  18. package/lib/client.js.bak-followball-20261028-224743 +3720 -0
  19. package/lib/client.js.bak-gearico-20261028-230937 +3748 -0
  20. package/lib/client.js.bak-glow-20261030 +3676 -0
  21. package/lib/client.js.bak-grid-20261028-220142 +3680 -0
  22. package/lib/client.js.bak-guide-20261029-003108 +3750 -0
  23. package/lib/client.js.bak-guide2-20261029-003213 +3751 -0
  24. package/lib/client.js.bak-guide3-20261029-003236 +3763 -0
  25. package/lib/client.js.bak-guide4-20261029-003249 +3768 -0
  26. package/lib/client.js.bak-guidefc-20261029-003535 +3775 -0
  27. package/lib/client.js.bak-guideico-20261029-013809 +3776 -0
  28. package/lib/client.js.bak-guideorder-20261029-004345 +3775 -0
  29. package/lib/client.js.bak-headbtn-20261028-232114 +3750 -0
  30. package/lib/client.js.bak-menu6col-20261028-225126 +3741 -0
  31. package/lib/client.js.bak-menu6col2-20261028-225348 +3741 -0
  32. package/lib/client.js.bak-menu6col2-20261028-225527 +3741 -0
  33. package/lib/client.js.bak-monobtn-20261028-230502 +3741 -0
  34. package/lib/client.js.bak-navsimple-20261029-015235 +3786 -0
  35. package/lib/client.js.bak-nobadge-20261029-015354 +3784 -0
  36. package/lib/client.js.bak-nodrag-20261028-170713 +3651 -0
  37. package/lib/client.js.bak-official-20261028-171240 +3653 -0
  38. package/lib/client.js.bak-official2-20261028-171304 +3668 -0
  39. package/lib/client.js.bak-renumber-20261028-215518 +3681 -0
  40. package/lib/client.js.bak-sbgap-20261029-013144 +3776 -0
  41. package/lib/client.js.bak-sbgap-20261029-013202 +3776 -0
  42. package/lib/client.js.bak-sbrowh-20261029-012307 +3776 -0
  43. package/lib/client.js.bak-sbrowh-20261029-012333 +3776 -0
  44. package/lib/client.js.bak-scope-sb-20261029-011711 +3776 -0
  45. package/lib/client.js.bak-skinclarity-20261029-011027 +3776 -0
  46. package/lib/client.js.bak-skincols-20261028-224614 +3720 -0
  47. package/lib/client.js.bak-star-20261028-231205 +3748 -0
  48. package/lib/client.js.bak-star-20261028-231233 +3748 -0
  49. package/lib/client.js.bak-sync-20261028-220538 +3680 -0
  50. package/lib/index.js +1 -1
  51. package/locale/en.json +1 -1
  52. package/locale/zh.json +1 -1
  53. package/package.json +86 -85
  54. package/scripts/audit-contrast.mjs +215 -0
  55. package/scripts/check-bom.mjs +51 -0
  56. package/docs/images/skill-browser-panel.png +0 -0
package/lib/index.js CHANGED
@@ -46,7 +46,7 @@ var listGroups = grMod.listGroups, createGroup = grMod.createGroup,
46
46
  var keywordClassify = kwMod.keywordClassify;
47
47
 
48
48
  var PREFIX = "/dsh-skill-browser";
49
- export var VERSION = "1.9.9";
49
+ export var VERSION = "1.9.10";
50
50
  var CACHE_TTL_MS = 30 * 1000;
51
51
  var DIAG_FILE = join(homedir(), ".dsh", "data", "dsh-skill-browser", "diag.log");
52
52
  var CONFIG_FILE = join(homedir(), ".dsh", "data", "dsh-skill-browser", "config.json");
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "Skill Browser",
4
- "description": "Open a floating-ball panel to browse, search and inspect your local skill library with one/two-level categories and full SKILL.md viewer; enable or disable skills per card, per category or library-wide with automatic backups; auto-log every skill call in a failure ledger. Configurable skill root (native directory chooser), draggable ball with one-click reset."
4
+ "description": "One floating ball opens a panel that turns your local skill library into something you can browse, search and govern: one/two-level categories plus custom groups (tree/flat views, manual groups, instant keyword grouping, AI auto-grouping with AI covers — grouping is view-only and never moves skill files), per-skill version badges with a \"no version yet\" fallback, per-card / per-category / library-wide enable-disable with automatic backups, and a full SKILL.md viewer. A call-ledger dashboard adds date ranges, day/week/month/year granularity, rankings, trend chart and failure details, fed automatically by the host's tools/result event. One-click hot reload, all 128 ball skins contrast-adaptive, configurable skill root, draggable and resettable ball."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "技能浏览器",
4
- "description": "以悬浮球打开技能面板,按一级/二级分类检索本机技能库,查看 SKILL.md 全文,单个/分类级/全库批量启停技能(自动备份),并自动登记技能调用失效台账;技能库位置可配置(原生目录选择框),悬浮球可拖拽、可一键复位。"
4
+ "description": "技能浏览器:悬浮球打开面板,把本机技能库变成可浏览、可检索、可治理的资产——两级分类 + 自定义分组(树状/平铺双模式、手动建组、⚡关键词分组、🤖AI 自动分组与 AI 封面,分组只改视图不动技能文件)、版本号徽章(无版本显示「暂未编版本号」)、单卡/分类/全库启停(自动备份)、SKILL.md 全文查看;调用台账看板(起止日期 + 日/周/月/年 + 排行榜 + 趋势图 + 失效明细),监听 tools/result 自动登记每次技能调用结果。⚡ 一键热重载,128 款皮肤全量自适应配色,技能库位置可配置,悬浮球可拖拽可复位。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,86 +1,87 @@
1
- {
2
- "name": "dsh-skill-browser",
3
- "version": "1.9.9",
4
- "description": "技能浏览器:把 ~/.dsh/skills 里的技能做成可视化面板(搜索/分类/启停/详情/调用台账)。v1.9.0 自定义分组+版本号+AI 分组+AI 封面;v1.9.1 调用台账看板;v1.9.2 全皮肤自适应配色;v1.9.3 两级分组+滚动条+悬浮球层级;v1.9.4 面板⚡热重载;v1.9.5 吸顶表头不透明;v1.9.6 详情层隔离+分片渲染+增量读取;v1.9.7 关键词快速分组;v1.9.8 树状/平铺双模式分组栏;v1.9.9 零配置模型自动发现+全皮肤文字可见性加固。",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "sideEffects": false,
8
- "engines": {
9
- "dsh": ">=0.1.7-rc.1",
10
- "node": "^22.19.0 || >=24.0.0"
11
- },
12
- "exports": {
13
- ".": "./lib/index.js",
14
- "./client": "./lib/client.js",
15
- "./package.json": "./package.json",
16
- "./locale/*.json": "./locale/*.json",
17
- "./icon": "./icon.svg"
18
- },
19
- "files": [
20
- "lib",
21
- "lib/**/*.js",
22
- "lib/**/*.ps1",
23
- "cordis.patch.yml",
24
- "README.md",
25
- "CHANGELOG.md",
26
- "LICENSE",
27
- "lib/orb/*.html",
28
- "docs/images/**",
29
- "locale/*.json",
30
- "icon.svg",
31
- "README-zh.md",
32
- "README-es.md",
33
- "README-pt.md",
34
- "README-hi.md"
35
- ],
36
- "license": "MIT",
37
- "keywords": [
38
- "dsh-plugin",
39
- "deepseek-harness",
40
- "skill",
41
- "技能",
42
- "skills-browser",
43
- "skill-browser",
44
- "fluent",
45
- "cfd",
46
- "分类",
47
- "category",
48
- "chinese",
49
- "中文简介"
50
- ],
51
- "dsh": {
52
- "bundle": {
53
- "patch": "./cordis.patch.yml"
54
- },
55
- "client": {
56
- "inject": [
57
- "@deepseek-ai/dsh-client-connection",
58
- "@deepseek-ai/dsh-client-runtime",
59
- "@deepseek-ai/dsh-client-ui-settings",
60
- "@deepseek-ai/dsh-client-ui-slots",
61
- "@deepseek-ai/dsh-client-locale"
62
- ],
63
- "platform": "web"
64
- }
65
- },
66
- "peerDependencies": {
67
- "@deepseek-ai/cordis": "^4.0.2",
68
- "@deepseek-ai/dsh": ">=0.1.7-rc.1",
69
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.1 || ^0.2.0-rc.1"
70
- },
71
- "peerDependenciesMeta": {
72
- "@deepseek-ai/dsh-llm": {
73
- "optional": true
74
- }
75
- },
76
- "devDependencies": {
77
- "@deepseek-ai/cordis": "^4.0.2",
78
- "@deepseek-ai/dsh": ">=0.1.7-rc.1"
79
- },
80
- "repository": {
81
- "type": "git",
82
- "url": "git+https://github.com/loyalchiiina/dsh-skill-browser.git"
83
- },
84
- "homepage": "https://github.com/loyalchiiina/dsh-skill-browser#readme",
85
- "packageManager": "pnpm@11.7.0"
1
+ {
2
+ "name": "dsh-skill-browser",
3
+ "version": "1.9.11",
4
+ "description": "技能浏览器:悬浮球打开面板,把本机技能库变成可浏览、可检索、可治理的资产——三个 Tab(技能浏览/调用台账/快速上手)、树状/平铺双视图、手动+关键词+AI 三种分组(分组只改视图不动技能文件)、调用台账看板(调用次数/成功率/趋势图/技能调用排行)、130 款三球同步皮肤(6 列宽屏菜单)、7 步快速上手、DSH 设置页悬浮球导航分区;一键热重载,技能库位置可配置,悬浮球可拖拽可复位。",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "sideEffects": false,
8
+ "engines": {
9
+ "dsh": "\u003e=0.1.7-rc.1",
10
+ "node": "^22.19.0 || \u003e=24.0.0"
11
+ },
12
+ "exports": {
13
+ ".": "./lib/index.js",
14
+ "./client": "./lib/client.js",
15
+ "./package.json": "./package.json",
16
+ "./locale/*.json": "./locale/*.json",
17
+ "./icon": "./icon.svg"
18
+ },
19
+ "files": [
20
+ "lib",
21
+ "lib/**/*.js",
22
+ "lib/**/*.ps1",
23
+ "cordis.patch.yml",
24
+ "README.md",
25
+ "CHANGELOG.md",
26
+ "LICENSE",
27
+ "lib/orb/*.html",
28
+ "docs/images/**",
29
+ "locale/*.json",
30
+ "icon.svg",
31
+ "README-zh.md",
32
+ "README-es.md",
33
+ "README-pt.md",
34
+ "README-hi.md",
35
+ "scripts"
36
+ ],
37
+ "license": "MIT",
38
+ "keywords": [
39
+ "dsh-plugin",
40
+ "deepseek-harness",
41
+ "skill",
42
+ "技能",
43
+ "skills-browser",
44
+ "skill-browser",
45
+ "fluent",
46
+ "cfd",
47
+ "分类",
48
+ "category",
49
+ "chinese",
50
+ "中文简介"
51
+ ],
52
+ "dsh": {
53
+ "bundle": {
54
+ "patch": "./cordis.patch.yml"
55
+ },
56
+ "client": {
57
+ "inject": [
58
+ "@deepseek-ai/dsh-client-connection",
59
+ "@deepseek-ai/dsh-client-runtime",
60
+ "@deepseek-ai/dsh-client-ui-settings",
61
+ "@deepseek-ai/dsh-client-ui-slots",
62
+ "@deepseek-ai/dsh-client-locale"
63
+ ],
64
+ "platform": "web"
65
+ }
66
+ },
67
+ "peerDependencies": {
68
+ "@deepseek-ai/cordis": "^4.0.2",
69
+ "@deepseek-ai/dsh": "\u003e=0.1.7-rc.1",
70
+ "@deepseek-ai/dsh-llm": "^0.1.7-rc.1 || ^0.2.0-rc.1"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "@deepseek-ai/dsh-llm": {
74
+ "optional": true
75
+ }
76
+ },
77
+ "devDependencies": {
78
+ "@deepseek-ai/cordis": "^4.0.2",
79
+ "@deepseek-ai/dsh": "\u003e=0.1.7-rc.1"
80
+ },
81
+ "repository": {
82
+ "type": "git",
83
+ "url": "git+https://github.com/loyalchiiina/dsh-skill-browser.git"
84
+ },
85
+ "homepage": "https://github.com/loyalchiiina/dsh-skill-browser#readme",
86
+ "packageManager": "pnpm@11.7.0"
86
87
  }
@@ -0,0 +1,215 @@
1
+ // 全皮肤对比度总审计(唯一权威口径)
2
+ //
3
+ // 用法:node scripts/audit-contrast.mjs [--verbose]
4
+ // 退出码:0 = 全部达标;1 = 有不达标项(明细打印到 stdout)
5
+ //
6
+ // 口径说明:
7
+ // - 皮肤目录(128 款)来自 lib/client.js 内联的 window.__DSH_BALL_SKINS。
8
+ // - 语义变量来自真实调用链:applySkinNow() 把 skinBallCss() + skinPanelCss()
9
+ // 写进 <style id="dsb-skin-style">,本脚本切皮肤后直接从该 style 的
10
+ // textContent 里解析 --dsb-* 实际值 —— 不复制一份「期望值」,
11
+ // 避免审计与生产实现各算一套、出现假绿。
12
+ // - 背景一律取「实际叠层后的颜色」:
13
+ // panel = pbgA || bgA
14
+ // card = chipBg over panel (卡片/排行行真实底色)
15
+ // surface = --dsb-surface (吸顶表头不透明底色,实算值)
16
+ // gtile1/2= shade over tile1/2 (分组大类行是线性渐变,两个端点分别校验)
17
+ // obg1/2 = --dsb-onbrand-bg1/2 (品牌渐变两个端点,只测中点会漏浅端)
18
+ // - 渐变底必须按最差端点判定,这是 v1.9.10 修的 bug:
19
+ // 中点达标但浅端只有 3.7–4.4:1,实际渲染出来白字就是看不清。
20
+ import fs from "node:fs";
21
+ import vm from "node:vm";
22
+ import { createRequire } from "node:module";
23
+ import { fileURLToPath } from "node:url";
24
+ import { dirname, join } from "node:path";
25
+
26
+ const require = createRequire(import.meta.url);
27
+ const HERE = dirname(fileURLToPath(import.meta.url));
28
+ const CLIENT = join(HERE, "..", "lib", "client.js");
29
+ const VERBOSE = process.argv.includes("--verbose");
30
+
31
+ // ---------- 最小 DOM stub:只为让 client.js 的 factory 跑完并产出 style ----------
32
+ const registry = {};
33
+ function mkEl(tag) {
34
+ const el = {
35
+ tagName: tag, __ev: {}, style: {}, dataset: {}, children: [], options: { length: 0 },
36
+ classList: {
37
+ _s: new Set(),
38
+ add(c) { this._s.add(c); }, remove(c) { this._s.delete(c); },
39
+ toggle(c, v) { v ? this._s.add(c) : this._s.delete(c); }, contains(c) { return this._s.has(c); }
40
+ },
41
+ _html: "", textContent: "", id: "", className: "", title: "",
42
+ getAttribute(k) { return this[k] == null ? null : this[k]; },
43
+ setAttribute(k, v) { if (k === "id") { this.id = v; registry[v] = this; } this[k] = v; },
44
+ removeAttribute(k) { delete this[k]; }, hasAttribute(k) { return this[k] != null; },
45
+ appendChild(c) { this.children.push(c); if (c && c.id) registry[c.id] = c; return c; },
46
+ insertBefore(c) { this.children.push(c); if (c && c.id) registry[c.id] = c; return c; },
47
+ removeChild(c) { const i = this.children.indexOf(c); if (i >= 0) this.children.splice(i, 1); return c; },
48
+ remove() { }, addEventListener(t, f) { (this.__ev[t] = this.__ev[t] || []).push(f); }, removeEventListener() { },
49
+ querySelector() { return null; }, querySelectorAll() { return []; }, closest() { return null; },
50
+ focus() { }, blur() { }, click() { },
51
+ getBoundingClientRect() { return { left: 10, top: 10, right: 110, bottom: 40, width: 100, height: 30 }; },
52
+ contains() { return false; }
53
+ };
54
+ Object.defineProperty(el, "innerHTML", {
55
+ get() { return this._html; },
56
+ set(v) {
57
+ this._html = String(v);
58
+ const re = /id="([A-Za-z0-9_-]+)"/g; let m;
59
+ while ((m = re.exec(this._html))) if (!registry[m[1]]) registry[m[1]] = mkEl("div");
60
+ }
61
+ });
62
+ return el;
63
+ }
64
+
65
+ const sandbox = {
66
+ console: { log() { }, warn() { }, error() { } }, // 屏蔽 client.js 的 mounted 日志
67
+ JSON, Math, Date, String, Number, Object, Array, RegExp, parseInt, parseFloat, isNaN, Boolean,
68
+ setTimeout: (f) => { try { f(); } catch (e) { } }, clearTimeout() { },
69
+ setInterval: () => 0, clearInterval() { },
70
+ fetch: () => Promise.reject(new Error("audit: no network")),
71
+ // 必须可读写:SKIN.setSkin(id) 靠 localStorage 记住选择,写不进去就会一直用 01 号皮肤,
72
+ // 导致 128 款皮肤全按深色算 → 20 款浅色皮肤集体假报不达标(踩过)。
73
+ localStorage: {
74
+ _d: {},
75
+ getItem(k) { return this._d[k] ?? null; },
76
+ setItem(k, v) { this._d[k] = String(v); },
77
+ removeItem(k) { delete this._d[k]; }
78
+ },
79
+ CustomEvent: function (n, o) { this.type = n; this.detail = o && o.detail; },
80
+ document: {
81
+ head: mkEl("head"), body: mkEl("body"), documentElement: mkEl("html"),
82
+ createElement: mkEl,
83
+ getElementById: (i) => registry[i] || null,
84
+ querySelector: (s) => (s && s.startsWith("#") ? registry[s.slice(1)] || null : null),
85
+ querySelectorAll: () => [], addEventListener() { }, removeEventListener() { }
86
+ },
87
+ location: { reload() { }, href: "http://127.0.0.1/" },
88
+ navigator: { userAgent: "node" }
89
+ };
90
+ sandbox.window = sandbox; sandbox.self = sandbox; sandbox.globalThis = sandbox;
91
+ sandbox.dispatchEvent = function () { };
92
+ vm.createContext(sandbox);
93
+
94
+ const mod = { id: "dsh-skill-browser", exports: {} };
95
+ sandbox.__ModuleLoader__ = { load(o) { mod.exports = o.factory((n) => require(n)); } };
96
+ vm.runInContext(fs.readFileSync(CLIENT, "utf8"), sandbox);
97
+ mod.exports.apply({
98
+ slots: { register() { }, inject() { }, slot() { } },
99
+ locale: { register() { }, t: (k) => k },
100
+ settings: { register() { } },
101
+ effect(f) { return f && f(); },
102
+ logger: { info() { }, warn() { }, error() { } },
103
+ get: () => null, on() { }
104
+ });
105
+
106
+ const SKIN = sandbox.window.__DSH_BALL_SKINS;
107
+ if (!SKIN) { console.error("FATAL: window.__DSH_BALL_SKINS 未挂载,皮肤目录没加载成功"); process.exit(2); }
108
+ const ids = Object.keys(SKIN.BY_ID);
109
+
110
+ // ---------- 颜色工具(与 lib/client.js 的 skinRgb/skinCr 同口径,但独立实现以交叉校验) ----------
111
+ function rgb(c) {
112
+ if (c == null) return null;
113
+ const s = String(c).trim(); let m;
114
+ if ((m = /^#([0-9a-f]{3})$/i.exec(s))) return [parseInt(m[1][0] + m[1][0], 16), parseInt(m[1][1] + m[1][1], 16), parseInt(m[1][2] + m[1][2], 16), 1];
115
+ if ((m = /^#([0-9a-f]{6})$/i.exec(s))) return [parseInt(s.slice(1, 3), 16), parseInt(s.slice(3, 5), 16), parseInt(s.slice(5, 7), 16), 1];
116
+ // 100 款派生皮肤的颜色是 hsl()/hsla() 字符串,不解析 hsl 会全部落空 → 假绿/假红。
117
+ if ((m = /^hsla?\(\s*(-?[\d.]+)(?:deg)?[,\s]+([\d.]+)%[,\s]+([\d.]+)%(?:[,\s/]+([\d.]+))?/i.exec(s))) {
118
+ const h = ((parseFloat(m[1]) % 360) + 360) % 360, sat = parseFloat(m[2]) / 100, l = parseFloat(m[3]) / 100;
119
+ const a = m[4] == null ? 1 : parseFloat(m[4]);
120
+ const cc = (1 - Math.abs(2 * l - 1)) * sat, x = cc * (1 - Math.abs(((h / 60) % 2) - 1)), mm = l - cc / 2;
121
+ let r = 0, g = 0, b = 0;
122
+ if (h < 60) { r = cc; g = x; } else if (h < 120) { r = x; g = cc; } else if (h < 180) { g = cc; b = x; }
123
+ else if (h < 240) { g = x; b = cc; } else if (h < 300) { r = x; b = cc; } else { r = cc; b = x; }
124
+ return [Math.round((r + mm) * 255), Math.round((g + mm) * 255), Math.round((b + mm) * 255), a];
125
+ }
126
+ if ((m = /^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s/]+([\d.]+))?/i.exec(s))) return [+m[1], +m[2], +m[3], m[4] == null ? 1 : +m[4]];
127
+ return null;
128
+ }
129
+ const lum = (r) => {
130
+ const f = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
131
+ return 0.2126 * f(r[0]) + 0.7152 * f(r[1]) + 0.0722 * f(r[2]);
132
+ };
133
+ const cr = (a, b) => { const l1 = lum(a), l2 = lum(b); return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05); };
134
+ const over = (fg, bg) => { const a = fg[3]; return [fg[0] * a + bg[0] * (1 - a), fg[1] * a + bg[1] * (1 - a), fg[2] * a + bg[2] * (1 - a), 1]; };
135
+
136
+ function varsOf(id) {
137
+ SKIN.setSkin(id); // 触发 __dshBallSkinHooks → applySkinNow() 重算 style
138
+ const css = (registry["dsb-skin-style"] || {}).textContent || "";
139
+ const out = {}; let m;
140
+ const re = /--dsb-([a-z0-9-]+):([^;}]+)/g;
141
+ while ((m = re.exec(css))) out["--dsb-" + m[1]] = m[2].trim();
142
+ return out;
143
+ }
144
+
145
+ // 检查表:[说明, 前景来源(变量名或皮肤原生 key), 背景 key, 最低对比度]
146
+ // 4.5 = WCAG AA 正常文字;3.0 = 大号文字 / 非文字图形(图标、趋势柱、迷你条)
147
+ const CHECKS = [
148
+ ["正文 ptx / 面板", "ptx", "panel", 4.5],
149
+ ["卡片名 dsb-brand / 卡片底", "--dsb-brand", "card", 4.5],
150
+ ["排行名 dsb-brand / 行底", "--dsb-brand", "row", 4.5],
151
+ ["指标大数字 brand-strong / 卡片底", "--dsb-brand-strong", "card", 3.0],
152
+ ["次要小字 dsb-dim / 面板", "--dsb-dim", "panel", 4.5],
153
+ ["次要小字 dsb-dim / 卡片底", "--dsb-dim", "card", 4.5],
154
+ ["吸顶表头 dsb-dim / 不透明表头底", "--dsb-dim", "surface", 4.5],
155
+ ["成功绿 dsb-ok / 卡片底", "--dsb-ok", "card", 3.0],
156
+ ["失败红 dsb-fail / 卡片底", "--dsb-fail", "card", 3.0],
157
+ ["警告黄 dsb-warn / 卡片底", "--dsb-warn", "card", 3.0],
158
+ ["激活态文字 onbrand / 品牌渐变起点", "--dsb-onbrand", "obg1", 4.5],
159
+ ["激活态文字 onbrand / 品牌渐变终点", "--dsb-onbrand", "obg2", 4.5],
160
+ ["分组卡名 oncard / 分组卡底(渐变起点)", "--dsb-oncard", "gtile1", 4.5],
161
+ ["分组卡名 oncard / 分组卡底(渐变终点)", "--dsb-oncard", "gtile2", 4.5],
162
+ ["分组计数 oncard-dim / 分组卡底(起点)", "--dsb-oncard-dim", "gtile1", 3.0],
163
+ ["分组计数 oncard-dim / 分组卡底(终点)", "--dsb-oncard-dim", "gtile2", 3.0],
164
+ ["趋势柱 bar1 / 面板", "--dsb-bar1", "panel", 3.0],
165
+ ["趋势柱 bar2 / 面板", "--dsb-bar2", "panel", 3.0]
166
+ ];
167
+
168
+ const fails = [];
169
+ for (const id of ids) {
170
+ const V = varsOf(id);
171
+ const base = SKIN.BY_ID[id].vars;
172
+ const panel = rgb(base.pbgA || base.bgA);
173
+ const chip = rgb(base.chipBg);
174
+ const card = chip ? over(chip, panel) : panel;
175
+ const surface = rgb(V["--dsb-surface"]) || card;
176
+ const gt1 = rgb(V["--dsb-tile1"]) || [58, 67, 112, 1];
177
+ const gt2 = rgb(V["--dsb-tile2"]) || [35, 42, 69, 1];
178
+ const gshade = rgb(V["--dsb-shade"]) || [8, 10, 18, 0.42];
179
+ const BG = {
180
+ panel, card, row: card, surface,
181
+ gtile1: over(gshade, gt1), gtile2: over(gshade, gt2),
182
+ obg1: rgb(V["--dsb-onbrand-bg1"]), obg2: rgb(V["--dsb-onbrand-bg2"])
183
+ };
184
+
185
+ for (const [name, key, bgKey, min] of CHECKS) {
186
+ const fgStr = key.startsWith("--") ? V[key] : base[key];
187
+ if (!fgStr) { fails.push({ id, name, why: "变量缺失 " + key }); continue; }
188
+ const fg = rgb(fgStr), bg = BG[bgKey];
189
+ if (!fg || !bg) { fails.push({ id, name, why: "无法解析 " + fgStr + " / " + bgKey }); continue; }
190
+ const eff = fg[3] < 1 ? over(fg, bg) : fg;
191
+ const v = cr(eff, bg);
192
+ if (v < min) fails.push({ id, name, v: +v.toFixed(2), min, fg: fgStr, bgKey });
193
+ else if (VERBOSE) console.log(`ok ${id} ${SKIN.BY_ID[id].name} ${name} ${v.toFixed(2)}:1`);
194
+ }
195
+ }
196
+
197
+ const light = ids.filter((i) => {
198
+ const b = SKIN.BY_ID[i].vars;
199
+ const p = rgb(b.pbgA || b.bgA);
200
+ return p && lum(p) > 0.45;
201
+ }).length;
202
+
203
+ console.log(`皮肤: ${ids.length}(浅色 ${light}) 检查组: ${CHECKS.length} 总项: ${ids.length * CHECKS.length}`);
204
+ console.log(`不达标: ${fails.length}`);
205
+ if (fails.length) {
206
+ const byName = {};
207
+ for (const f of fails) (byName[f.name] = byName[f.name] || []).push(f);
208
+ for (const n of Object.keys(byName)) {
209
+ console.log(`\n[${n}] ${byName[n].length} 款`);
210
+ for (const f of byName[n].slice(0, 8)) {
211
+ console.log(` ${f.id} ${SKIN.BY_ID[f.id].name} ${f.v ?? f.why}${f.min ? " < " + f.min : ""} (fg=${f.fg || "-"} bg=${f.bgKey || "-"})`);
212
+ }
213
+ }
214
+ process.exit(1);
215
+ }
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ // 防 BOM 回归:扫描仓库内所有文本文件,发现 UTF-8 BOM 立即报错退出。
3
+ // 背景:v1.9.2 改造时 package.json / lib/index.js 被写入 BOM,导致宿主 JSON.parse 失败、
4
+ // 市场判定「该包不可用」、整包不激活、悬浮球消失。此脚本作为发布前硬门禁。
5
+ // 用法:node scripts/check-bom.mjs [--fix]
6
+
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+
10
+ const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")), "..");
11
+ const FIX = process.argv.includes("--fix");
12
+ const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "coverage"]);
13
+
14
+ function walk(dir, out) {
15
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
16
+ if (SKIP_DIRS.has(e.name)) continue;
17
+ const p = path.join(dir, e.name);
18
+ if (e.isDirectory()) walk(p, out);
19
+ else out.push(p);
20
+ }
21
+ return out;
22
+ }
23
+
24
+ const files = walk(ROOT, []).filter((p) =>
25
+ /\.(json|js|mjs|cjs|ts|yml|yaml|md|txt|ps1|html|css|svg)$/i.test(p)
26
+ );
27
+
28
+ const bad = [];
29
+ for (const p of files) {
30
+ const b = fs.readFileSync(p);
31
+ if (b.length >= 3 && b[0] === 0xef && b[1] === 0xbb && b[2] === 0xbf) {
32
+ bad.push(p);
33
+ if (FIX) {
34
+ fs.writeFileSync(p, b.subarray(3));
35
+ console.log("FIXED " + path.relative(ROOT, p));
36
+ }
37
+ }
38
+ }
39
+
40
+ if (bad.length === 0) {
41
+ console.log("OK: " + files.length + " 个文本文件,0 个带 UTF-8 BOM");
42
+ process.exit(0);
43
+ }
44
+
45
+ if (!FIX) {
46
+ console.error("FAIL: 发现 " + bad.length + " 个带 UTF-8 BOM 的文件(会导致宿主 JSON.parse 失败、插件被判不可用):");
47
+ for (const p of bad) console.error(" " + path.relative(ROOT, p));
48
+ console.error("修复:node scripts/check-bom.mjs --fix");
49
+ process.exit(1);
50
+ }
51
+ console.log("已修复 " + bad.length + " 个文件");
Binary file