beauticode-dsh 1.0.5 → 1.0.7
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/cli.js +58 -23
- package/console.js +41 -13
- package/package.json +1 -1
- package/ui-host.mjs +38 -11
- package/vendor/adapter-dsh/session.js +4 -1
- package/vendor/core/paths.js +1 -0
package/cli.js
CHANGED
|
@@ -46,9 +46,17 @@ function defaultDataRoot() {
|
|
|
46
46
|
return path.join(os.homedir(), ".beauticode");
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function defaultDshHome() {
|
|
50
|
-
return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
51
|
-
}
|
|
49
|
+
function defaultDshHome() {
|
|
50
|
+
return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function defaultPluginHome(dshHome) {
|
|
54
|
+
return path.join(path.resolve(dshHome), "plugins", pluginName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function legacyDefaultPluginHome() {
|
|
58
|
+
return path.join(defaultDataRoot(), "plugin");
|
|
59
|
+
}
|
|
52
60
|
|
|
53
61
|
function toFileUri(filePath) {
|
|
54
62
|
const full = path.resolve(filePath).replaceAll("\\", "/");
|
|
@@ -222,15 +230,34 @@ async function sameInstalledVersion(dest) {
|
|
|
222
230
|
}
|
|
223
231
|
}
|
|
224
232
|
|
|
225
|
-
async function copyPackage(dest) {
|
|
233
|
+
async function copyPackage(dest) {
|
|
226
234
|
if (await sameInstalledVersion(dest)) return false;
|
|
227
235
|
await fsp.rm(dest, { recursive: true, force: true });
|
|
228
236
|
await fsp.cp(here, dest, {
|
|
229
237
|
recursive: true,
|
|
230
238
|
filter: (source) => shouldCopy(source),
|
|
231
239
|
});
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function removeLegacyManagedPlugin(legacyHome, currentHome) {
|
|
244
|
+
if (!legacyHome) return false;
|
|
245
|
+
const legacy = path.resolve(legacyHome);
|
|
246
|
+
if (legacy === path.resolve(currentHome) || !fs.existsSync(legacy)) return false;
|
|
247
|
+
try {
|
|
248
|
+
const pkg = JSON.parse(await fsp.readFile(path.join(legacy, "package.json"), "utf8"));
|
|
249
|
+
if (
|
|
250
|
+
![pluginName, "@beauticode/dsh-plugin"].includes(pkg.name) ||
|
|
251
|
+
!fs.existsSync(path.join(legacy, "index.mjs"))
|
|
252
|
+
) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
await fsp.rm(legacy, { recursive: true, force: true });
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
234
261
|
|
|
235
262
|
async function ensureEngine(dest) {
|
|
236
263
|
const vendor = path.join(dest, "vendor", "adapter-dsh", "index.js");
|
|
@@ -320,7 +347,7 @@ async function removeWebPackageDep(webPackage) {
|
|
|
320
347
|
return true;
|
|
321
348
|
}
|
|
322
349
|
|
|
323
|
-
async function install(opts) {
|
|
350
|
+
async function install(opts) {
|
|
324
351
|
const dest = path.resolve(opts.pluginHome);
|
|
325
352
|
const dshHome = path.resolve(opts.dshHome);
|
|
326
353
|
const webProfile = path.join(dshHome, "profiles", "web");
|
|
@@ -340,21 +367,25 @@ async function install(opts) {
|
|
|
340
367
|
await linkPluginIntoProfile(webProfile, dest);
|
|
341
368
|
await ensureWebPackageDep(webPackage, dest);
|
|
342
369
|
await writePatch(webPatch, packageInsert());
|
|
343
|
-
if (fs.existsSync(homePatch)) {
|
|
370
|
+
if (fs.existsSync(homePatch)) {
|
|
344
371
|
const homeRaw = await fsp.readFile(homePatch, "utf8");
|
|
345
|
-
if (hasBridge(homeRaw)) await removePatch(homePatch);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
console.log(
|
|
349
|
-
console.log(
|
|
372
|
+
if (hasBridge(homeRaw)) await removePatch(homePatch);
|
|
373
|
+
}
|
|
374
|
+
const migrated = await removeLegacyManagedPlugin(opts.legacyPluginHome, dest);
|
|
375
|
+
console.log(`已写入 ${webPatch}`);
|
|
376
|
+
console.log(copied ? `插件已安装到 ${dest}` : `已复用已安装的插件 ${dest}`);
|
|
377
|
+
if (migrated) console.log("已迁移 1.0.5 的旧插件目录,已保留主题数据。");
|
|
378
|
+
console.log("请自己运行:npx @deepseek-ai/dsh web");
|
|
350
379
|
return { dest, patch: webPatch };
|
|
351
380
|
}
|
|
352
381
|
|
|
353
|
-
await fsp.mkdir(dshHome, { recursive: true });
|
|
354
|
-
await writePatch(homePatch, fileUriInsert(toFileUri(indexFile)));
|
|
355
|
-
|
|
356
|
-
console.log(
|
|
357
|
-
console.log(
|
|
382
|
+
await fsp.mkdir(dshHome, { recursive: true });
|
|
383
|
+
await writePatch(homePatch, fileUriInsert(toFileUri(indexFile)));
|
|
384
|
+
const migrated = await removeLegacyManagedPlugin(opts.legacyPluginHome, dest);
|
|
385
|
+
console.log(`DSH web profile 还不存在,已写入 ${homePatch}`);
|
|
386
|
+
console.log(copied ? `插件已安装到 ${dest}` : `已复用已安装的插件 ${dest}`);
|
|
387
|
+
if (migrated) console.log("已迁移 1.0.5 的旧插件目录,已保留主题数据。");
|
|
388
|
+
console.log("请自己运行:npx @deepseek-ai/dsh web");
|
|
358
389
|
return { dest, patch: homePatch };
|
|
359
390
|
}
|
|
360
391
|
|
|
@@ -386,11 +417,15 @@ async function uninstall(opts) {
|
|
|
386
417
|
return { removed };
|
|
387
418
|
}
|
|
388
419
|
|
|
389
|
-
export async function runCli(argv = process.argv.slice(2)) {
|
|
390
|
-
const dshHome = argValue(argv, "--dsh-home") || defaultDshHome();
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
const opts = {
|
|
420
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
421
|
+
const dshHome = argValue(argv, "--dsh-home") || defaultDshHome();
|
|
422
|
+
const requestedPluginHome = argValue(argv, "--plugin-home");
|
|
423
|
+
const pluginHome = requestedPluginHome || defaultPluginHome(dshHome);
|
|
424
|
+
const opts = {
|
|
425
|
+
dshHome,
|
|
426
|
+
pluginHome,
|
|
427
|
+
legacyPluginHome: requestedPluginHome ? null : legacyDefaultPluginHome(),
|
|
428
|
+
};
|
|
394
429
|
if (argv.includes("--remove")) return uninstall(opts);
|
|
395
430
|
return install(opts);
|
|
396
431
|
}
|
package/console.js
CHANGED
|
@@ -221,7 +221,7 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-del:hover{backg
|
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
async function run(task) {
|
|
224
|
+
async function run(task) {
|
|
225
225
|
if (busy) return;
|
|
226
226
|
busy = true;
|
|
227
227
|
for (const button of pop.querySelectorAll(".bc-btn, .bc-theme-toggle, .bc-theme-item, .bc-theme-del")) button.disabled = true;
|
|
@@ -236,8 +236,31 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-del:hover{backg
|
|
|
236
236
|
} finally {
|
|
237
237
|
busy = false;
|
|
238
238
|
for (const button of pop.querySelectorAll(".bc-btn, .bc-theme-toggle, .bc-theme-item, .bc-theme-del")) button.disabled = false;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function askThemeName(fileName) {
|
|
243
|
+
const suggested = String(fileName || "")
|
|
244
|
+
.replace(/\.[^.]+$/, "")
|
|
245
|
+
.trim()
|
|
246
|
+
.slice(0, 80);
|
|
247
|
+
const entered = window.prompt("请给该主题取个名字(1–80 个字符):", suggested);
|
|
248
|
+
if (entered == null) return null;
|
|
249
|
+
const name = entered.trim();
|
|
250
|
+
if (!name) {
|
|
251
|
+
showMessage("主题名不能为空。");
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
if (name.length > 80) {
|
|
255
|
+
showMessage("主题名不能超过 80 个字符。");
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
if (/[<>:"/\\|?*]/.test(name) || /[\u0000-\u001f]/.test(name)) {
|
|
259
|
+
showMessage('主题名不能包含 < > : " / \\ | ? * 或控制字符。');
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return name;
|
|
263
|
+
}
|
|
241
264
|
|
|
242
265
|
trigger.addEventListener("click", (event) => {
|
|
243
266
|
event.stopPropagation();
|
|
@@ -313,16 +336,21 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-del:hover{backg
|
|
|
313
336
|
}),
|
|
314
337
|
);
|
|
315
338
|
});
|
|
316
|
-
fileInput.addEventListener("change", () => {
|
|
317
|
-
const file = fileInput.files?.[0];
|
|
318
|
-
fileInput.value = "";
|
|
319
|
-
if (!file) return;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
339
|
+
fileInput.addEventListener("change", () => {
|
|
340
|
+
const file = fileInput.files?.[0];
|
|
341
|
+
fileInput.value = "";
|
|
342
|
+
if (!file) return;
|
|
343
|
+
const themeName = askThemeName(file.name);
|
|
344
|
+
if (!themeName) return;
|
|
345
|
+
void run(() =>
|
|
346
|
+
request("/__beauticode/ui/import", {
|
|
347
|
+
method: "POST",
|
|
348
|
+
headers: {
|
|
349
|
+
"x-beauticode-filename": encodeURIComponent(file.name),
|
|
350
|
+
"x-beauticode-theme-name": encodeURIComponent(themeName),
|
|
351
|
+
},
|
|
352
|
+
body: file,
|
|
353
|
+
}),
|
|
326
354
|
);
|
|
327
355
|
});
|
|
328
356
|
|
package/package.json
CHANGED
package/ui-host.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
6
|
import { Transform } from "node:stream";
|
|
7
|
-
import { createBeauticodeActions
|
|
7
|
+
import { createBeauticodeActions } from "./agent.mjs";
|
|
8
8
|
import { callDshControl } from "./control-client.mjs";
|
|
9
9
|
import { createGalleryHandlers, resolveConfiguredSkinCenterUrl } from "./gallery-host.mjs";
|
|
10
10
|
import { hasLiveTray, resolveApplyBackend, stopInProcessSession } from "./host-apply.mjs";
|
|
@@ -13,7 +13,7 @@ const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".webp", ".avif"]);
|
|
|
13
13
|
const MAX_IMAGE_BYTES = 18 * 1024 * 1024;
|
|
14
14
|
const MAX_VIDEO_BYTES = 800 * 1024 * 1024;
|
|
15
15
|
|
|
16
|
-
export function parseImportFilename(raw) {
|
|
16
|
+
export function parseImportFilename(raw) {
|
|
17
17
|
if (typeof raw !== "string" || !raw.trim()) {
|
|
18
18
|
return { ok: false, error: "缺少文件名。" };
|
|
19
19
|
}
|
|
@@ -35,7 +35,29 @@ export function parseImportFilename(raw) {
|
|
|
35
35
|
return { ok: true, name, ext, kind: "image", maxBytes: MAX_IMAGE_BYTES };
|
|
36
36
|
}
|
|
37
37
|
return { ok: false, error: "只支持图片(jpg / jpeg / png / webp / avif)或 MP4 视频。" };
|
|
38
|
-
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseImportThemeName(raw) {
|
|
41
|
+
if (Array.isArray(raw)) raw = raw[0];
|
|
42
|
+
if (typeof raw !== "string" || !raw.trim()) {
|
|
43
|
+
return { ok: false, error: "请先给该主题取名。" };
|
|
44
|
+
}
|
|
45
|
+
let decoded = raw.trim();
|
|
46
|
+
try {
|
|
47
|
+
decoded = decodeURIComponent(decoded);
|
|
48
|
+
} catch {
|
|
49
|
+
return { ok: false, error: "主题名编码无效。" };
|
|
50
|
+
}
|
|
51
|
+
const name = decoded.trim();
|
|
52
|
+
if (!name) return { ok: false, error: "主题名不能为空。" };
|
|
53
|
+
if (name.length > 80) {
|
|
54
|
+
return { ok: false, error: "主题名不能超过 80 个字符。" };
|
|
55
|
+
}
|
|
56
|
+
if (/[<>:"/\\|?*]/.test(name) || /[\u0000-\u001f]/.test(name)) {
|
|
57
|
+
return { ok: false, error: "主题名包含非法字符。" };
|
|
58
|
+
}
|
|
59
|
+
return { ok: true, name };
|
|
60
|
+
}
|
|
39
61
|
|
|
40
62
|
function limitBytes(maxBytes) {
|
|
41
63
|
let size = 0;
|
|
@@ -168,13 +190,21 @@ export function createBeauticodeUi({ dataRoot, baseUrl, getBaseUrl, sendJson, is
|
|
|
168
190
|
res.writeHead(403).end();
|
|
169
191
|
return;
|
|
170
192
|
}
|
|
171
|
-
const parsed = parseImportFilename(req.headers["x-beauticode-filename"]);
|
|
193
|
+
const parsed = parseImportFilename(req.headers["x-beauticode-filename"]);
|
|
172
194
|
if (!parsed.ok) {
|
|
173
195
|
req.resume();
|
|
174
196
|
sendJson(res, 400, { ok: false, error: parsed.error });
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const parsedTheme = parseImportThemeName(
|
|
200
|
+
req.headers["x-beauticode-theme-name"],
|
|
201
|
+
);
|
|
202
|
+
if (!parsedTheme.ok) {
|
|
203
|
+
req.resume();
|
|
204
|
+
sendJson(res, 400, { ok: false, error: parsedTheme.error });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const length = Number(req.headers["content-length"]);
|
|
178
208
|
if (Number.isFinite(length) && length > parsed.maxBytes) {
|
|
179
209
|
req.resume();
|
|
180
210
|
sendJson(res, 413, { ok: false, error: "文件过大。" });
|
|
@@ -188,10 +218,7 @@ export function createBeauticodeUi({ dataRoot, baseUrl, getBaseUrl, sendJson, is
|
|
|
188
218
|
);
|
|
189
219
|
try {
|
|
190
220
|
await pipeline(req, limitBytes(parsed.maxBytes), fs.createWriteStream(tmpPath));
|
|
191
|
-
const themeName =
|
|
192
|
-
parsed.name,
|
|
193
|
-
parsed.kind === "video" ? "视频" : "图片",
|
|
194
|
-
);
|
|
221
|
+
const themeName = parsedTheme.name;
|
|
195
222
|
const result =
|
|
196
223
|
parsed.kind === "video"
|
|
197
224
|
? await actions.applyVideo({ path: tmpPath, themeName })
|
|
@@ -93,7 +93,10 @@ export class DshSession {
|
|
|
93
93
|
this.startHandoffLoop();
|
|
94
94
|
void this.yieldToTrayIfRequested();
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
// Finish the initial restore check before accepting the first user apply.
|
|
97
|
+
// Otherwise a click immediately after lazy session startup can race the
|
|
98
|
+
// background reapply and be rejected as a concurrent operation.
|
|
99
|
+
await this.watchOnce();
|
|
97
100
|
return { port: this.bridgePort() };
|
|
98
101
|
}
|
|
99
102
|
async apply(input) {
|
package/vendor/core/paths.js
CHANGED
|
@@ -68,6 +68,7 @@ export async function ensureDataLayout(paths) {
|
|
|
68
68
|
entry === SNAPSHOTS_DIR_NAME ||
|
|
69
69
|
entry === SAVED_DIR_NAME ||
|
|
70
70
|
entry === RUNTIME_MEDIA_DIR_NAME ||
|
|
71
|
+
entry === "tmp" ||
|
|
71
72
|
entry === "logs" ||
|
|
72
73
|
entry === "engine-launcher.log" ||
|
|
73
74
|
entry === "injector.lock" ||
|