beauticode-dsh 1.0.2 → 1.0.4

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.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  该 Cordis 插件向 DSH Web 注入 beautiCode 浏览器客户端,并提供本机鉴权接口。支持图片、MP4、播放位置、静音、摸鱼模式以及清除背景。
4
4
 
5
- 装好插件并运行 `dsh web` 后,侧栏「设置」上方会出现「背景」:可从文件夹选图片或 MP4、清除、开关声音、切换已保存主题。网页控制台没有摸鱼;浅色/深色跟 DSH 自己的外观走。下次打开会恢复上次背景。
5
+ 装好插件并运行 `dsh web` 后,侧栏「设置」上方会出现「背景」:可从文件夹选图片或 MP4、清除、开关声音、切换已保存主题,以及打开「皮肤中心」安装已审核的社区皮肤。网页控制台没有摸鱼;浅色/深色跟 DSH 自己的外观走。下次打开会恢复上次背景。设置 `BEAUTICODE_SKIN_CENTER` 或填写 `skin-center.json` 后,皮肤中心才会显示远程目录。
6
6
 
7
7
  插件也会注册对话工具(`beauticode_*`)和斜杠命令(`/bg`、`/bg-theme`、`/bg-clear`)。不需要托盘;托盘若已在跑则复用它。
8
8
 
package/agent.mjs CHANGED
@@ -240,6 +240,39 @@ export function createBeauticodeActions(dataRootOrOptions) {
240
240
  });
241
241
  },
242
242
 
243
+ async importTheme(input, signal) {
244
+ const name = String(input?.name ?? "").trim();
245
+ const imagePath = String(input?.imagePath ?? "").trim();
246
+ if (!name || !imagePath) fail("导入皮肤必须提供名称和图片。");
247
+ const body = {
248
+ name,
249
+ imagePath,
250
+ };
251
+ if (typeof input.videoPath === "string" && input.videoPath.trim()) {
252
+ body.videoPath = input.videoPath.trim();
253
+ }
254
+ if (input.effects) body.effects = input.effects;
255
+ if (input.source) body.source = input.source;
256
+ const resolved = await backend();
257
+ if (resolved.kind === "tray") {
258
+ const result = await request({
259
+ method: "POST",
260
+ path: "/theme/import",
261
+ body,
262
+ signal,
263
+ timeoutMs: 30 * 60 * 1000,
264
+ });
265
+ if (!result || result.ok === false) fail(result?.error || "导入皮肤失败。");
266
+ return {
267
+ ok: true,
268
+ theme: result.theme,
269
+ message: `已保存皮肤「${result.theme.name}」。`,
270
+ };
271
+ }
272
+ const theme = await resolved.session.importSavedTheme(body);
273
+ return { ok: true, theme, message: `已保存皮肤「${theme.name}」。` };
274
+ },
275
+
243
276
  async listThemes(signal) {
244
277
  const resolved = await backend();
245
278
  if (resolved.kind === "tray") {
@@ -1,252 +1,7 @@
1
- #!/usr/bin/env node
2
- /**
3
- * One-line installer: npx beauticode-dsh
4
- *
5
- * Copies this package to a stable local folder and writes the DSH patch so
6
- * `dsh web` loads beautiCode. Does not start DeepSeek Harness.
7
- */
8
- import fs from "node:fs";
9
- import fsp from "node:fs/promises";
10
- import os from "node:os";
11
- import path from "node:path";
12
- import { fileURLToPath, pathToFileURL } from "node:url";
13
-
14
- function findPackageRoot(startDir) {
15
- let dir = startDir;
16
- for (let i = 0; i < 5; i += 1) {
17
- if (
18
- fs.existsSync(path.join(dir, "package.json")) &&
19
- fs.existsSync(path.join(dir, "index.mjs"))
20
- ) {
21
- return dir;
22
- }
23
- const parent = path.dirname(dir);
24
- if (parent === dir) break;
25
- dir = parent;
26
- }
27
- return startDir;
28
- }
29
-
30
- const here = findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
31
- const pluginName = "beauticode-dsh";
32
- const bridgeId = "beauticode-bridge";
33
-
34
- function argValue(argv, name) {
35
- const idx = argv.indexOf(name);
36
- if (idx === -1) return null;
37
- const value = argv[idx + 1];
38
- return value && !value.startsWith("--") ? value : null;
39
- }
40
-
41
- function defaultDataRoot() {
42
- if (process.env.BEAUTICODE_DATA_ROOT) return process.env.BEAUTICODE_DATA_ROOT;
43
- if (process.env.LOCALAPPDATA) {
44
- return path.join(process.env.LOCALAPPDATA, "beautiCode");
45
- }
46
- return path.join(os.homedir(), ".beauticode");
47
- }
48
-
49
- function defaultDshHome() {
50
- return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
51
- }
52
-
53
- function toFileUri(filePath) {
54
- const full = path.resolve(filePath).replaceAll("\\", "/");
55
- if (/^[A-Za-z]:/.test(full)) return `file:///${full}`;
56
- return pathToFileURL(full).href;
57
- }
58
-
59
- function fileUriInsert(uri) {
60
- return [
61
- "# beauticode-bridge (installer)",
62
- "- insert:",
63
- " - id: beauticode-bridge",
64
- ` name: '${uri}'`,
65
- " inject: [webServer]",
66
- "",
67
- ].join("\n");
68
- }
69
-
70
- function packageInsert() {
71
- return [
72
- "# beauticode-bridge (installer)",
73
- "- insert:",
74
- " - id: beauticode-bridge",
75
- ` name: '${pluginName}'`,
76
- " inject: [webServer]",
77
- "",
78
- ].join("\n");
79
- }
80
-
81
- function hasBridge(text) {
82
- return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
83
- }
84
-
85
- function stripBridge(text) {
86
- const patterns = [
87
- /(?:^|\r?\n)# beauticode-bridge \(installer\)\r?\n- insert:\r?\n(?:[ \t]+.*\r?\n)*/g,
88
- /(?:^|\r?\n)- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/g,
89
- ];
90
- let next = text;
91
- for (const pattern of patterns) next = next.replace(pattern, "\n");
92
- return next;
93
- }
94
-
95
- async function writePatch(filePath, body) {
96
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
97
- if (!fs.existsSync(filePath)) {
98
- await fsp.writeFile(filePath, body, "utf8");
99
- return;
100
- }
101
- const raw = await fsp.readFile(filePath, "utf8");
102
- if (hasBridge(raw)) {
103
- const replaced = raw.replace(
104
- /(?:# beauticode-bridge \(installer\)\r?\n)?- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/,
105
- body,
106
- );
107
- await fsp.writeFile(filePath, replaced.endsWith("\n") ? replaced : `${replaced}\n`, "utf8");
108
- return;
109
- }
110
- const stripped = raw.trim();
111
- if (stripped === "" || stripped === "[]") {
112
- await fsp.writeFile(filePath, body, "utf8");
113
- return;
114
- }
115
- await fsp.writeFile(filePath, `${stripped}\n\n${body}`, "utf8");
116
- }
117
-
118
- async function removePatch(filePath) {
119
- if (!fs.existsSync(filePath)) return false;
120
- const raw = await fsp.readFile(filePath, "utf8");
121
- if (!hasBridge(raw)) return false;
122
- const cleaned = stripBridge(raw).trim();
123
- if (cleaned === "" || cleaned === "[]") {
124
- await fsp.writeFile(
125
- filePath,
126
- "# Your patch layer for this dsh profile.\n[]\n",
127
- "utf8",
128
- );
129
- return true;
130
- }
131
- await fsp.writeFile(filePath, `${cleaned}\n`, "utf8");
132
- return true;
133
- }
134
-
135
- function shouldCopy(source) {
136
- const relative = path.relative(here, source);
137
- if (relative.startsWith("test") || relative.includes(`${path.sep}test${path.sep}`)) {
138
- return false;
139
- }
140
- if (relative.endsWith(".test.mjs")) return false;
141
- return true;
142
- }
143
-
144
- async function sameInstalledVersion(dest) {
145
- try {
146
- const incoming = JSON.parse(await fsp.readFile(path.join(here, "package.json"), "utf8"));
147
- const installed = JSON.parse(await fsp.readFile(path.join(dest, "package.json"), "utf8"));
148
- return (
149
- incoming.name === installed.name &&
150
- incoming.version === installed.version &&
151
- fs.existsSync(path.join(dest, "index.mjs")) &&
152
- fs.existsSync(path.join(dest, "vendor", "adapter-dsh", "index.js"))
153
- );
154
- } catch {
155
- return false;
156
- }
157
- }
158
-
159
- async function copyPackage(dest) {
160
- if (await sameInstalledVersion(dest)) return false;
161
- await fsp.rm(dest, { recursive: true, force: true });
162
- await fsp.cp(here, dest, {
163
- recursive: true,
164
- filter: (source) => shouldCopy(source),
165
- });
166
- return true;
167
- }
168
-
169
- async function ensureEngine(dest) {
170
- const vendor = path.join(dest, "vendor", "adapter-dsh", "index.js");
171
- if (fs.existsSync(vendor)) return;
172
- const packPath = path.resolve(here, "../../scripts/pack-dsh-plugin.mjs");
173
- if (!fs.existsSync(packPath)) {
174
- throw new Error("插件包不完整:缺少本机导入引擎。请重新执行 npx beauticode-dsh。");
175
- }
176
- const { stageEngineInto } = await import(pathToFileURL(packPath).href);
177
- await stageEngineInto(dest);
178
- }
179
-
180
- async function install(opts) {
181
- const dest = path.resolve(opts.pluginHome);
182
- const dshHome = path.resolve(opts.dshHome);
183
- const webProfile = path.join(dshHome, "profiles", "web");
184
- const webPatch = path.join(webProfile, "cordis.patch.yml");
185
- const webPackage = path.join(webProfile, "package.json");
186
- const homePatch = path.join(dshHome, "cordis.patch.yml");
187
-
188
- await fsp.mkdir(dest, { recursive: true });
189
- const copied = await copyPackage(dest);
190
- await ensureEngine(dest);
191
- const indexFile = path.join(dest, "index.mjs");
192
- if (!fs.existsSync(indexFile)) {
193
- throw new Error(`缺少插件入口:${indexFile}`);
194
- }
195
-
196
- if (fs.existsSync(webPackage)) {
197
- await writePatch(webPatch, fileUriInsert(toFileUri(indexFile)));
198
- if (fs.existsSync(homePatch)) {
199
- const homeRaw = await fsp.readFile(homePatch, "utf8");
200
- if (hasBridge(homeRaw)) await removePatch(homePatch);
201
- }
202
- console.log(`已写入 ${webPatch}`);
203
- console.log(copied ? `插件已安装到 ${dest}` : `已复用已安装的插件 ${dest}`);
204
- console.log("请自己运行:npx @deepseek-ai/dsh web");
205
- return { dest, patch: webPatch };
206
- }
207
-
208
- await fsp.mkdir(dshHome, { recursive: true });
209
- await writePatch(homePatch, fileUriInsert(toFileUri(indexFile)));
210
- console.log(`DSH web profile 还不存在,已写入 ${homePatch}`);
211
- console.log(copied ? `插件已安装到 ${dest}` : `已复用已安装的插件 ${dest}`);
212
- console.log("请自己运行:npx @deepseek-ai/dsh web");
213
- return { dest, patch: homePatch };
214
- }
215
-
216
- async function uninstall(opts) {
217
- const dshHome = path.resolve(opts.dshHome);
218
- const removed = [];
219
- if (await removePatch(path.join(dshHome, "profiles", "web", "cordis.patch.yml"))) {
220
- removed.push("web patch");
221
- }
222
- if (await removePatch(path.join(dshHome, "cordis.patch.yml"))) {
223
- removed.push("home patch");
224
- }
225
- const dest = path.resolve(opts.pluginHome);
226
- if (fs.existsSync(dest)) {
227
- await fsp.rm(dest, { recursive: true, force: true });
228
- removed.push(dest);
229
- }
230
- console.log(removed.length ? `已移除:${removed.join("、")}` : "没有可移除的 beautiCode 插件接线。");
231
- return { removed };
232
- }
233
-
234
- export async function runCli(argv = process.argv.slice(2)) {
235
- const dshHome = argValue(argv, "--dsh-home") || defaultDshHome();
236
- const pluginHome =
237
- argValue(argv, "--plugin-home") || path.join(defaultDataRoot(), "plugin");
238
- const opts = { dshHome, pluginHome };
239
- if (argv.includes("--remove")) return uninstall(opts);
240
- return install(opts);
241
- }
242
-
243
- const launchedDirectly =
244
- Boolean(process.argv[1]) &&
245
- pathToFileURL(path.resolve(process.argv[1])).href.toLowerCase() ===
246
- import.meta.url.toLowerCase();
247
- if (launchedDirectly) {
248
- runCli().catch((error) => {
249
- console.error(error instanceof Error ? error.message : String(error));
250
- process.exit(1);
251
- });
252
- }
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../cli.js";
3
+
4
+ runCli().catch((error) => {
5
+ console.error(error instanceof Error ? error.message : String(error));
6
+ process.exit(1);
7
+ });
package/cli.js CHANGED
@@ -78,49 +78,115 @@ function packageInsert() {
78
78
  ].join("\n");
79
79
  }
80
80
 
81
- function hasBridge(text) {
82
- return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
81
+ function hasBridge(text) {
82
+ return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
83
+ }
84
+
85
+ function stripBridge(text) {
86
+ const lines = text.split(/\r?\n/);
87
+ const removed = new Array(lines.length).fill(false);
88
+ const indentOf = (line) => line.match(/^[ \t]*/)?.[0].length ?? 0;
89
+
90
+ for (let i = 0; i < lines.length; i += 1) {
91
+ const insert = lines[i].match(/^([ \t]*)-\s*insert:\s*(?:#.*)?$/);
92
+ if (!insert) continue;
93
+ const insertIndent = insert[1].length;
94
+ let blockEnd = i + 1;
95
+ while (blockEnd < lines.length) {
96
+ const line = lines[blockEnd];
97
+ if (line.trim() && indentOf(line) <= insertIndent) break;
98
+ blockEnd += 1;
99
+ }
100
+
101
+ let foundBridge = false;
102
+ for (let j = i + 1; j < blockEnd; ) {
103
+ const bridge = lines[j].match(
104
+ /^([ \t]*)-\s*id:\s*beauticode-bridge\s*(?:#.*)?$/,
105
+ );
106
+ if (!bridge || bridge[1].length <= insertIndent) {
107
+ j += 1;
108
+ continue;
109
+ }
110
+ foundBridge = true;
111
+ const itemIndent = bridge[1].length;
112
+ let itemEnd = j + 1;
113
+ while (itemEnd < blockEnd) {
114
+ const line = lines[itemEnd];
115
+ if (line.trim() && indentOf(line) <= itemIndent) break;
116
+ itemEnd += 1;
117
+ }
118
+ for (let k = j; k < itemEnd; k += 1) removed[k] = true;
119
+ j = itemEnd;
120
+ }
121
+
122
+ if (!foundBridge) continue;
123
+ const marker = `${insert[1]}# beauticode-bridge (installer)`;
124
+ if (i > 0 && lines[i - 1].trimEnd() === marker) removed[i - 1] = true;
125
+ const hasSibling = lines
126
+ .slice(i + 1, blockEnd)
127
+ .some(
128
+ (line, offset) =>
129
+ !removed[i + 1 + offset] &&
130
+ line.trim() !== "" &&
131
+ !line.trimStart().startsWith("#"),
132
+ );
133
+ if (!hasSibling) {
134
+ for (let k = i; k < blockEnd; k += 1) removed[k] = true;
135
+ }
136
+ i = blockEnd - 1;
137
+ }
138
+
139
+ return lines.filter((_, index) => !removed[index]).join("\n");
140
+ }
141
+
142
+ function overlayPayload(text) {
143
+ return text
144
+ .split(/\r?\n/)
145
+ .map((line) => line.trim())
146
+ .filter((line) => line && !line.startsWith("#"))
147
+ .join("\n");
148
+ }
149
+
150
+ function isEmptyOverlay(text) {
151
+ const payload = overlayPayload(text);
152
+ return payload === "" || payload === "[]";
153
+ }
154
+
155
+ // DSH seeds overlays as `# comment\n[]`. `[]` is already a complete YAML
156
+ // document, so appending `- insert:` makes js-yaml throw.
157
+ function keptOverlay(text) {
158
+ const withoutFlowEmpty = text
159
+ .split(/\r?\n/)
160
+ .filter((line) => line.trim() !== "[]")
161
+ .join("\n");
162
+ if (isEmptyOverlay(withoutFlowEmpty)) return "";
163
+ return withoutFlowEmpty.trim();
83
164
  }
84
165
 
85
- function stripBridge(text) {
86
- const patterns = [
87
- /(?:^|\r?\n)# beauticode-bridge \(installer\)\r?\n- insert:\r?\n(?:[ \t]+.*\r?\n)*/g,
88
- /(?:^|\r?\n)- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/g,
89
- ];
90
- let next = text;
91
- for (const pattern of patterns) next = next.replace(pattern, "\n");
92
- return next;
166
+ function withTrailingNewline(text) {
167
+ return text.endsWith("\n") ? text : `${text}\n`;
93
168
  }
94
169
 
95
170
  async function writePatch(filePath, body) {
96
171
  await fsp.mkdir(path.dirname(filePath), { recursive: true });
172
+ const insert = withTrailingNewline(body);
97
173
  if (!fs.existsSync(filePath)) {
98
- await fsp.writeFile(filePath, body, "utf8");
174
+ await fsp.writeFile(filePath, insert, "utf8");
99
175
  return;
100
176
  }
101
177
  const raw = await fsp.readFile(filePath, "utf8");
102
- if (hasBridge(raw)) {
103
- const replaced = raw.replace(
104
- /(?:# beauticode-bridge \(installer\)\r?\n)?- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/,
105
- body,
106
- );
107
- await fsp.writeFile(filePath, replaced.endsWith("\n") ? replaced : `${replaced}\n`, "utf8");
108
- return;
109
- }
110
- const stripped = raw.trim();
111
- if (stripped === "" || stripped === "[]") {
112
- await fsp.writeFile(filePath, body, "utf8");
113
- return;
114
- }
115
- await fsp.writeFile(filePath, `${stripped}\n\n${body}`, "utf8");
178
+ const remainder = hasBridge(raw) ? stripBridge(raw) : raw;
179
+ const kept = keptOverlay(remainder);
180
+ const next = kept ? `${kept}\n\n${insert}` : insert;
181
+ await fsp.writeFile(filePath, withTrailingNewline(next), "utf8");
116
182
  }
117
183
 
118
184
  async function removePatch(filePath) {
119
185
  if (!fs.existsSync(filePath)) return false;
120
186
  const raw = await fsp.readFile(filePath, "utf8");
121
187
  if (!hasBridge(raw)) return false;
122
- const cleaned = stripBridge(raw).trim();
123
- if (cleaned === "" || cleaned === "[]") {
188
+ const kept = keptOverlay(stripBridge(raw));
189
+ if (!kept) {
124
190
  await fsp.writeFile(
125
191
  filePath,
126
192
  "# Your patch layer for this dsh profile.\n[]\n",
@@ -128,7 +194,7 @@ async function removePatch(filePath) {
128
194
  );
129
195
  return true;
130
196
  }
131
- await fsp.writeFile(filePath, `${cleaned}\n`, "utf8");
197
+ await fsp.writeFile(filePath, withTrailingNewline(kept), "utf8");
132
198
  return true;
133
199
  }
134
200
 
@@ -177,6 +243,83 @@ async function ensureEngine(dest) {
177
243
  await stageEngineInto(dest);
178
244
  }
179
245
 
246
+ function pluginLinkPath(webProfile) {
247
+ return path.join(webProfile, "node_modules", pluginName);
248
+ }
249
+
250
+ function legacyPluginLinkPath(webProfile) {
251
+ return path.join(webProfile, "node_modules", "@beauticode", "dsh-plugin");
252
+ }
253
+
254
+ function linkSpecFor(pluginHome) {
255
+ return `link:${path.resolve(pluginHome).replaceAll("\\", "/")}`;
256
+ }
257
+
258
+ async function sameLinkTarget(link, dest) {
259
+ try {
260
+ const stat = await fsp.lstat(link);
261
+ if (stat.isSymbolicLink()) {
262
+ const target = await fsp.readlink(link);
263
+ return path.resolve(path.dirname(link), target) === path.resolve(dest);
264
+ }
265
+ if (stat.isDirectory()) {
266
+ return path.resolve(link) === path.resolve(dest);
267
+ }
268
+ } catch {
269
+ return false;
270
+ }
271
+ return false;
272
+ }
273
+
274
+ async function linkPluginIntoProfile(webProfile, dest) {
275
+ const link = pluginLinkPath(webProfile);
276
+ const legacy = legacyPluginLinkPath(webProfile);
277
+ if (fs.existsSync(legacy)) {
278
+ await fsp.rm(legacy, { recursive: true, force: true });
279
+ }
280
+ await fsp.mkdir(path.dirname(link), { recursive: true });
281
+ if (fs.existsSync(link)) {
282
+ if (await sameLinkTarget(link, dest)) return;
283
+ await fsp.rm(link, { recursive: true, force: true });
284
+ }
285
+ const type = process.platform === "win32" ? "junction" : "dir";
286
+ await fsp.symlink(path.resolve(dest), link, type);
287
+ }
288
+
289
+ async function ensureWebPackageDep(webPackage, pluginHome) {
290
+ const raw = await fsp.readFile(webPackage, "utf8");
291
+ const json = JSON.parse(raw);
292
+ if (!json.dependencies || typeof json.dependencies !== "object" || Array.isArray(json.dependencies)) {
293
+ json.dependencies = {};
294
+ }
295
+ const spec = linkSpecFor(pluginHome);
296
+ const hadLegacy = Object.prototype.hasOwnProperty.call(
297
+ json.dependencies,
298
+ "@beauticode/dsh-plugin",
299
+ );
300
+ delete json.dependencies["@beauticode/dsh-plugin"];
301
+ const current = json.dependencies[pluginName];
302
+ if (current === spec && !hadLegacy) return;
303
+ json.dependencies[pluginName] = spec;
304
+ await fsp.writeFile(webPackage, `${JSON.stringify(json, null, 2)}\n`, "utf8");
305
+ }
306
+
307
+ async function removeWebPackageDep(webPackage) {
308
+ if (!fs.existsSync(webPackage)) return false;
309
+ const json = JSON.parse(await fsp.readFile(webPackage, "utf8"));
310
+ if (!json.dependencies || typeof json.dependencies !== "object") return false;
311
+ let changed = false;
312
+ for (const name of [pluginName, "@beauticode/dsh-plugin"]) {
313
+ if (Object.prototype.hasOwnProperty.call(json.dependencies, name)) {
314
+ delete json.dependencies[name];
315
+ changed = true;
316
+ }
317
+ }
318
+ if (!changed) return false;
319
+ await fsp.writeFile(webPackage, `${JSON.stringify(json, null, 2)}\n`, "utf8");
320
+ return true;
321
+ }
322
+
180
323
  async function install(opts) {
181
324
  const dest = path.resolve(opts.pluginHome);
182
325
  const dshHome = path.resolve(opts.dshHome);
@@ -194,7 +337,9 @@ async function install(opts) {
194
337
  }
195
338
 
196
339
  if (fs.existsSync(webPackage)) {
197
- await writePatch(webPatch, fileUriInsert(toFileUri(indexFile)));
340
+ await linkPluginIntoProfile(webProfile, dest);
341
+ await ensureWebPackageDep(webPackage, dest);
342
+ await writePatch(webPatch, packageInsert());
198
343
  if (fs.existsSync(homePatch)) {
199
344
  const homeRaw = await fsp.readFile(homePatch, "utf8");
200
345
  if (hasBridge(homeRaw)) await removePatch(homePatch);
@@ -215,13 +360,23 @@ async function install(opts) {
215
360
 
216
361
  async function uninstall(opts) {
217
362
  const dshHome = path.resolve(opts.dshHome);
363
+ const webProfile = path.join(dshHome, "profiles", "web");
218
364
  const removed = [];
219
- if (await removePatch(path.join(dshHome, "profiles", "web", "cordis.patch.yml"))) {
365
+ if (await removePatch(path.join(webProfile, "cordis.patch.yml"))) {
220
366
  removed.push("web patch");
221
367
  }
222
368
  if (await removePatch(path.join(dshHome, "cordis.patch.yml"))) {
223
369
  removed.push("home patch");
224
370
  }
371
+ if (await removeWebPackageDep(path.join(webProfile, "package.json"))) {
372
+ removed.push("web package.json");
373
+ }
374
+ for (const link of [pluginLinkPath(webProfile), legacyPluginLinkPath(webProfile)]) {
375
+ if (fs.existsSync(link)) {
376
+ await fsp.rm(link, { recursive: true, force: true });
377
+ removed.push(link);
378
+ }
379
+ }
225
380
  const dest = path.resolve(opts.pluginHome);
226
381
  if (fs.existsSync(dest)) {
227
382
  await fsp.rm(dest, { recursive: true, force: true });
package/console.js CHANGED
@@ -64,6 +64,9 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
64
64
  '<button type="button" class="bc-btn" data-act="clear">清除</button>' +
65
65
  '<button type="button" class="bc-btn" data-act="sound">声音</button>' +
66
66
  "</div>" +
67
+ '<div class="bc-row">' +
68
+ '<button type="button" class="bc-btn" data-act="gallery">皮肤中心</button>' +
69
+ "</div>" +
67
70
  '<div class="bc-themes" hidden>' +
68
71
  '<button type="button" class="bc-theme-toggle" aria-expanded="false">已保存主题</button>' +
69
72
  '<div class="bc-theme-list" hidden></div>' +
@@ -236,6 +239,15 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
236
239
  fileInput.accept = VIDEO_ACCEPT;
237
240
  fileInput.click();
238
241
  });
242
+ pop.querySelector('[data-act="gallery"]').addEventListener("click", (event) => {
243
+ event.stopPropagation();
244
+ setOpen(false);
245
+ if (window.BeauticodeGallery) {
246
+ void window.BeauticodeGallery.open();
247
+ return;
248
+ }
249
+ showMessage("皮肤中心脚本尚未加载。");
250
+ });
239
251
  pop.querySelector('[data-act="clear"]').addEventListener("click", () => {
240
252
  currentThemeId = "";
241
253
  void run(() => request("/__beauticode/ui/clear", { method: "POST" }));
@@ -294,6 +306,9 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
294
306
  document.addEventListener("keydown", (event) => {
295
307
  if (event.key === "Escape" && !pop.hidden) setOpen(false);
296
308
  });
309
+ document.addEventListener("beauticode-gallery-installed", () => {
310
+ void refresh();
311
+ });
297
312
 
298
313
  const observer = new MutationObserver(() => place());
299
314
  observer.observe(document.documentElement, { childList: true, subtree: true });