dsh-background-by-model 0.3.2 → 0.4.1

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/lib/index.js CHANGED
@@ -1,75 +1,67 @@
1
1
  import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
2
2
  import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
3
- //#region src/index.ts
3
+ //#region src/schema.ts
4
+ /** Every accepted `bgMode`, in UI order. */
5
+ const BG_MODES = [
6
+ "fit",
7
+ "fill",
8
+ "stretch",
9
+ "tile",
10
+ "center"
11
+ ];
4
12
  /**
5
- * Node half of dsh-background-by-model: file-backed rule persistence.
6
- *
7
- * Owns the `~/.dsh/.dsh-background-by-model-data/` store and exposes a small RPC
8
- * surface on the dedicated `/dsh-background-by-model` channel (never the shared
9
- * `/api`, so slash commands stay intact).
10
- *
11
- * theme-config.json the ordered rule list + the global interface settings
12
- * modelbg-<slot> one background image per rule (raw bytes, no extension;
13
- * the MIME is sniffed from the magic bytes when served)
14
- *
15
- * Images travel as data URLs over the RPC channel — there is no separate HTTP
16
- * image route. A legacy single-wallpaper / video-background store is migrated
17
- * to rule 1 on first read.
13
+ * Main interface opacities (0..1), global (Interface page):
14
+ * `bg` main background (`--dsw-alias-bg-base`)
15
+ * `sidebar` sidebar (`--dsw-specific-sidebar-fill`)
16
+ * `card` cards/panels (`--dsw-alias-bg-layer-1/2/3`, `--dsw-specific-menu`
17
+ * and its 0.1.7 alias `--dsw-menu-surface-fill`)
18
+ * `input` input/control surfaces (`--dsw-specific-input-major`)
18
19
  */
19
- const name = "dsh-background-by-model";
20
- const inject = ["connection", "webServer"];
21
- const DATA_DIR = ".dsh-background-by-model-data";
22
- const CONFIG_FILE = "theme-config.json";
23
- const IMAGE_PREFIX = "modelbg-";
24
- const LEGACY_WALLPAPER = "wallpaper.jpg";
25
- const LEGACY_FILES = [
26
- "wallpaper.mp4",
27
- "wallpaper.webm",
28
- "wallpaper.ogv",
29
- "wallpaper.mov",
30
- "wallpaper.mkv",
31
- "wallpaper.video",
32
- "wallpaper.upload.tmp"
20
+ const PART_OPACITY_KEYS = [
21
+ "bg",
22
+ "sidebar",
23
+ "card",
24
+ "input"
33
25
  ];
34
- /** Slot names reach the filesystem, so they are strictly whitelisted. */
35
- const SLOT_RE = /^[A-Za-z0-9_-]{1,32}$/;
36
- const WALLPAPER_FETCH_MAX = 26214400;
37
- const WALLPAPER_FETCH_TIMEOUT = 2e4;
38
- const DEFAULT_CONFIG = {
39
- rules: [],
40
- opacities: {
41
- bg: .85,
42
- sidebar: .93,
43
- card: 1,
44
- input: 1
45
- },
46
- blurs: {
47
- bg: 0,
48
- sidebar: 0,
49
- card: 0,
50
- settings: 0,
51
- chat: 0,
52
- trajectory: 0,
53
- rightbar: 0,
54
- input: 0
55
- },
56
- settingsOpacity: 1,
57
- chatTextOpacity: 0,
58
- trajectoryOpacity: 1,
59
- rightbarOpacity: null
26
+ const DEFAULT_PART_OPACITIES = {
27
+ bg: .85,
28
+ sidebar: .93,
29
+ card: 1,
30
+ input: 1
60
31
  };
61
- const dataDir = () => dshHomePath(DATA_DIR);
62
- const configPath = () => dshHomePath(DATA_DIR, CONFIG_FILE);
63
- const legacyWallpaperPath = () => dshHomePath(DATA_DIR, LEGACY_WALLPAPER);
64
- const imagePath = (slot) => dshHomePath(DATA_DIR, `${IMAGE_PREFIX}${slot}`);
65
- const exists = async (p) => {
66
- try {
67
- await access(p);
68
- return true;
69
- } catch {
70
- return false;
71
- }
32
+ /**
33
+ * Interface blur (px, 0..60), global (Interface page):
34
+ * `bg` main background (AppFrame grid)
35
+ * `sidebar` sidebar column
36
+ * `card` cards/panels (center + details columns, dialog option panels)
37
+ * `settings` settings panel
38
+ * `chat` conversation text region (message column of the chat view)
39
+ * `trajectory` trajectory view surface
40
+ * `rightbar` file-preview panel — the right sidebar a file click opens
41
+ * `input` input/control surfaces ([data-composer-card], [data-cordis-panel])
42
+ */
43
+ const PART_BLUR_KEYS = [
44
+ "bg",
45
+ "sidebar",
46
+ "card",
47
+ "settings",
48
+ "chat",
49
+ "trajectory",
50
+ "rightbar",
51
+ "input"
52
+ ];
53
+ const DEFAULT_PART_BLURS = {
54
+ bg: 0,
55
+ sidebar: 0,
56
+ card: 0,
57
+ settings: 0,
58
+ chat: 0,
59
+ trajectory: 0,
60
+ rightbar: 0,
61
+ input: 0
72
62
  };
63
+ /** Slot names reach the filesystem, so they are strictly whitelisted. */
64
+ const SLOT_RE = /^[A-Za-z0-9_-]{1,32}$/;
73
65
  function clamp(n, lo, hi, def) {
74
66
  return typeof n === "number" && isFinite(n) ? Math.min(hi, Math.max(lo, n)) : def;
75
67
  }
@@ -84,31 +76,29 @@ function normalizeBgState(s) {
84
76
  ih: typeof v.ih === "number" && v.ih > 0 ? v.ih : 0
85
77
  };
86
78
  }
79
+ /** One `[h, s, l]` triple, or null when the value is not a complete finite triple. */
80
+ function normalizeHsl(raw) {
81
+ if (!Array.isArray(raw) || raw.length !== 3) return null;
82
+ if (!raw.every((n) => typeof n === "number" && isFinite(n))) return null;
83
+ return [
84
+ clamp(raw[0], 0, 360, 220),
85
+ clamp(raw[1], 0, 1, .55),
86
+ clamp(raw[2], 0, 1, .25)
87
+ ];
88
+ }
87
89
  /** Coerce one persisted rule, or null when it lacks a usable id/slot. */
88
90
  function normalizeRule(raw) {
89
91
  const r = raw ?? {};
90
92
  const id = typeof r.id === "string" && r.id !== "" ? r.id : null;
91
93
  const slot = typeof r.slot === "string" && SLOT_RE.test(r.slot) ? r.slot : null;
92
94
  if (id === null || slot === null) return null;
93
- const c = r.color;
94
- const color = Array.isArray(c) && c.length === 3 && c.every((n) => typeof n === "number" && isFinite(n)) ? [
95
- clamp(c[0], 0, 360, 220),
96
- clamp(c[1], 0, 1, .55),
97
- clamp(c[2], 0, 1, .25)
98
- ] : null;
99
- const mode = [
100
- "fit",
101
- "fill",
102
- "stretch",
103
- "tile",
104
- "center"
105
- ].includes(r.bgMode) ? r.bgMode : "fit";
95
+ const mode = BG_MODES.includes(r.bgMode) ? r.bgMode : "fit";
106
96
  return {
107
97
  id,
108
98
  slot,
109
99
  match: typeof r.match === "string" ? r.match : "",
110
100
  enabled: r.enabled !== false,
111
- color,
101
+ color: normalizeHsl(r.color),
112
102
  bgMode: mode,
113
103
  wallpaperOpacity: clamp01(r.wallpaperOpacity, 1),
114
104
  blur: clamp(r.blur, 0, 60, 0),
@@ -122,31 +112,66 @@ function normalizeConfig(raw) {
122
112
  const ops = r.opacities ?? {};
123
113
  const bl = r.blurs ?? {};
124
114
  const blurs = {};
125
- for (const k of [
126
- "bg",
127
- "sidebar",
128
- "card",
129
- "settings",
130
- "chat",
131
- "trajectory",
132
- "rightbar",
133
- "input"
134
- ]) blurs[k] = clamp(bl[k], 0, 60, DEFAULT_CONFIG.blurs[k]);
115
+ for (const k of PART_BLUR_KEYS) blurs[k] = clamp(bl[k], 0, 60, DEFAULT_PART_BLURS[k]);
116
+ const opacities = {};
117
+ for (const k of PART_OPACITY_KEYS) opacities[k] = clamp01(ops[k], DEFAULT_PART_OPACITIES[k]);
135
118
  return {
136
119
  rules,
137
- opacities: {
138
- bg: clamp01(ops.bg, DEFAULT_CONFIG.opacities.bg),
139
- sidebar: clamp01(ops.sidebar, DEFAULT_CONFIG.opacities.sidebar),
140
- card: clamp01(ops.card, DEFAULT_CONFIG.opacities.card),
141
- input: clamp01(ops.input, DEFAULT_CONFIG.opacities.input)
142
- },
120
+ opacities,
143
121
  blurs,
144
- settingsOpacity: clamp01(r.settingsOpacity, DEFAULT_CONFIG.settingsOpacity),
145
- chatTextOpacity: clamp01(r.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
146
- trajectoryOpacity: clamp01(r.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
147
- rightbarOpacity: typeof r.rightbarOpacity === "number" && isFinite(r.rightbarOpacity) ? clamp01(r.rightbarOpacity, 1) : null
122
+ settingsOpacity: clamp01(r.settingsOpacity, 1),
123
+ chatTextOpacity: clamp01(r.chatTextOpacity, 0),
124
+ trajectoryOpacity: clamp01(r.trajectoryOpacity, 1),
125
+ rightbarOpacity: typeof r.rightbarOpacity === "number" && isFinite(r.rightbarOpacity) ? clamp01(r.rightbarOpacity, 1) : null,
126
+ autoExtract: r.autoExtract !== false
148
127
  };
149
128
  }
129
+ //#endregion
130
+ //#region src/index.ts
131
+ /**
132
+ * Node half of dsh-background-by-model: file-backed rule persistence.
133
+ *
134
+ * Owns the `~/.dsh/.dsh-background-by-model-data/` store and exposes a small RPC
135
+ * surface on the dedicated `/dsh-background-by-model` channel (never the shared
136
+ * `/api`, so slash commands stay intact).
137
+ *
138
+ * theme-config.json the ordered rule list + the global interface settings
139
+ * modelbg-<slot> one background image per rule (raw bytes, no extension;
140
+ * the MIME is sniffed from the magic bytes when served)
141
+ *
142
+ * Images travel as data URLs over the RPC channel — there is no separate HTTP
143
+ * image route. A legacy single-wallpaper / video-background store is migrated
144
+ * to rule 1 on first read.
145
+ */
146
+ const name = "dsh-background-by-model";
147
+ const inject = ["connection", "webServer"];
148
+ const DATA_DIR = ".dsh-background-by-model-data";
149
+ const CONFIG_FILE = "theme-config.json";
150
+ const IMAGE_PREFIX = "modelbg-";
151
+ const LEGACY_WALLPAPER = "wallpaper.jpg";
152
+ const LEGACY_FILES = [
153
+ "wallpaper.mp4",
154
+ "wallpaper.webm",
155
+ "wallpaper.ogv",
156
+ "wallpaper.mov",
157
+ "wallpaper.mkv",
158
+ "wallpaper.video",
159
+ "wallpaper.upload.tmp"
160
+ ];
161
+ const WALLPAPER_FETCH_MAX = 26214400;
162
+ const WALLPAPER_FETCH_TIMEOUT = 2e4;
163
+ const dataDir = () => dshHomePath(DATA_DIR);
164
+ const configPath = () => dshHomePath(DATA_DIR, CONFIG_FILE);
165
+ const legacyWallpaperPath = () => dshHomePath(DATA_DIR, LEGACY_WALLPAPER);
166
+ const imagePath = (slot) => dshHomePath(DATA_DIR, `${IMAGE_PREFIX}${slot}`);
167
+ const exists = async (p) => {
168
+ try {
169
+ await access(p);
170
+ return true;
171
+ } catch {
172
+ return false;
173
+ }
174
+ };
150
175
  /**
151
176
  * One-shot migration of the pre-0.3 store.
152
177
  *
@@ -216,10 +241,44 @@ async function readConfig() {
216
241
  }
217
242
  return normalizeConfig(await migrateLegacy(raw));
218
243
  }
244
+ const LEGACY_CONFIG_KEYS = /* @__PURE__ */ new Set([
245
+ "color",
246
+ "bgMode",
247
+ "wallpaperOpacity",
248
+ "blur",
249
+ "bgState"
250
+ ]);
251
+ const warnedConfigKeys = /* @__PURE__ */ new Set();
252
+ function warnUnknownConfigKeys(raw, normalized) {
253
+ if (raw === null || typeof raw !== "object") return;
254
+ const r = raw;
255
+ const warned = (id) => {
256
+ if (warnedConfigKeys.has(id)) return;
257
+ warnedConfigKeys.add(id);
258
+ console.warn(`dsh-background-by-model: ignoring unknown config field "${id}" (declared in one half only?)`);
259
+ };
260
+ const known = new Set(Object.keys(normalized));
261
+ for (const key of Object.keys(r)) if (!known.has(key) && !LEGACY_CONFIG_KEYS.has(key)) warned(key);
262
+ const groups = [["blurs", normalized.blurs], ["opacities", normalized.opacities]];
263
+ for (const [group, have] of groups) {
264
+ const got = r[group];
265
+ if (got === null || typeof got !== "object") continue;
266
+ const keys = new Set(Object.keys(have));
267
+ for (const key of Object.keys(got)) if (!keys.has(key)) warned(`${group}.${key}`);
268
+ }
269
+ const sent = Array.isArray(r.rules) ? r.rules[0] : void 0;
270
+ const have = normalized.rules[0];
271
+ if (sent !== null && typeof sent === "object" && have !== void 0) {
272
+ const keys = new Set(Object.keys(have));
273
+ for (const key of Object.keys(sent)) if (!keys.has(key)) warned(`rules[].${key}`);
274
+ }
275
+ }
219
276
  async function writeConfig(config) {
220
277
  await ensureDir();
221
278
  try {
222
- await writeFile(configPath(), JSON.stringify(normalizeConfig(config), null, 2), "utf8");
279
+ const normalized = normalizeConfig(config);
280
+ warnUnknownConfigKeys(config, normalized);
281
+ await writeFile(configPath(), JSON.stringify(normalized, null, 2), "utf8");
223
282
  return true;
224
283
  } catch (e) {
225
284
  console.error(`dsh-background-by-model: failed to write "${CONFIG_FILE}"`, e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-background-by-model",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "Appearance plugin for DeepSeek Harness: an ordered list of model rules, each carrying its own wallpaper, theme color, layout mode, opacity and blur — the background follows the model you switch to.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -23,7 +23,7 @@
23
23
  "author": "Tkingxiao",
24
24
  "type": "module",
25
25
  "engines": {
26
- "dsh": ">=0.1.2-rc.1"
26
+ "dsh": ">=0.1.7-rc.2"
27
27
  },
28
28
  "exports": {
29
29
  ".": {
@@ -68,12 +68,12 @@
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@deepseek-ai/cordis": "^4.0.2",
71
- "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
72
- "@deepseek-ai/dsh-client-store": "^0.1.2-alpha.2",
73
- "@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.2",
74
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
75
- "@deepseek-ai/dsh-client-ui-theme": "^0.1.2-alpha.2",
76
- "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
71
+ "@deepseek-ai/dsh-client-locale": ">=0.1.7-rc.2",
72
+ "@deepseek-ai/dsh-client-store": ">=0.1.7-rc.2",
73
+ "@deepseek-ai/dsh-client-connection": ">=0.1.7-rc.2",
74
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-rc.2",
75
+ "@deepseek-ai/dsh-client-ui-theme": ">=0.1.7-rc.2",
76
+ "@deepseek-ai/dsh-invariants": ">=0.1.7-rc.2",
77
77
  "react": "^18.2.0"
78
78
  },
79
79
  "peerDependenciesMeta": {