🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

可视化换肤工作室 trrrrrryg/dsh-visual-skin

DeepSeek Harness
trrrrrryg65 天前有提交已提供中文文档扫描:含敏感能力

用图片设计并预览皮肤,确认后安全写入本机

29/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/9/17 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add trrrrrryg/dsh-visual-skin
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · vision
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 65 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包deepseek-harness-skin-studio(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/9/23 11:36:14

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 140 处,展示前 94 条)
  • 写入本地文件
    apps/controller/src/assets.ts:65
    await mkdir(dirname(target), { recursive: true });
  • 读取本地文件
    apps/controller/src/assets.ts:66
    try { await stat(target); } catch {
  • 写入本地文件
    apps/controller/src/assets.ts:68
    await writeFile(temp, canonical, { flag: "wx" });
  • 写入本地文件
    apps/controller/src/assets.ts:69
    await rename(temp, target);
  • 读取本地文件
    apps/controller/src/assets.ts:81
    const info = await stat(path);
  • 读取本地文件
    apps/controller/src/assets.ts:83
    return { record, bytes: await readFile(path) };
  • 读取本地文件
    apps/controller/src/dsh.ts:47
    try { detectedVersion = (JSON.parse(await readFile(packageJson, "utf8")) as { version?: string }).version ?? null; } catch {}
  • 删除本地文件递归
    apps/controller/src/dsh.ts:103
    await rm(stagePackage, { recursive: true, force: true });
  • 写入本地文件
    apps/controller/src/dsh.ts:104
    await mkdir(dirname(stagePackage), { recursive: true });
  • 写入本地文件
    apps/controller/src/dsh.ts:106
    if (snapshot.hadPlugin) { await rename(plan.packageRoot, rollbackPackage); movedOld = true; }
  • 写入本地文件
    apps/controller/src/dsh.ts:107
    await rename(stagePackage, plan.packageRoot); movedStage = true;
  • 删除本地文件递归
    apps/controller/src/dsh.ts:114
    if (movedOld) await rm(rollbackPackage, { recursive: true, force: true });
  • 写入本地文件
    apps/controller/src/dsh.ts:121
    try { if (movedStage) await rm(plan.packageRoot, { recursive: true, force: true }); if (movedOld) await rename(rollbackPackage, plan.packageRoot); com
  • 删除本地文件递归
    apps/controller/src/dsh.ts:121
    try { if (movedStage) await rm(plan.packageRoot, { recursive: true, force: true }); if (movedOld) await rename(rollbackPackage, plan.packageRoot); com
  • 删除本地文件递归
    apps/controller/src/dsh.ts:128
    } finally { await rm(stagePackage, { recursive: true, force: true }); }
  • 写入本地文件
    apps/controller/src/dsh.ts:152
    if (await exists(packageRoot)) await rename(packageRoot, stagedRemoval);
  • 删除本地文件递归
    apps/controller/src/dsh.ts:158
    await rm(stagedRemoval, { recursive: true, force: true });
  • 写入本地文件
    apps/controller/src/dsh.ts:163
    try { if (await exists(stagedRemoval)) await rename(stagedRemoval, packageRoot); } catch (rollback) { rollbackErrors.push(`plugin: ${String(rollback)}
  • 删除本地文件递归
    apps/controller/src/dsh.ts:207
    try { if (moved) await rm(plan.packageRoot, { recursive: true, force: true }); } catch {}
  • 读取本地文件
    apps/controller/src/dsh.ts:236
    async function readText(path: string, fallback: string): Promise<string> { try { return await readFile(path, "utf8"); } catch (error) { if ((error as
  • 读取本地文件
    apps/controller/src/dsh.ts:238
    async function exists(path: string): Promise<boolean> { try { await stat(path); return true; } catch { return false; } }
  • 读取本地文件
    apps/controller/src/dsh.ts:244
    if (!(await stat(source)).isDirectory()) continue;
  • 读取本地文件
    apps/controller/src/dsh.ts:245
    const manifest = JSON.parse(await readFile(join(source, "package.json"), "utf8")) as { name?: string };
  • 读取本地文件
    apps/controller/src/dsh.ts:247
    if (!(await stat(join(source, "dist", "host", "index.js"))).isFile()) continue;
  • 删除本地文件递归
    apps/controller/src/gc.ts:118
    await rm(core.store.path("isolated-preview-runtime", id), { recursive: true, force: true });
  • 读取本地文件
    apps/controller/src/gc.ts:129
    if (resolve(home).toLowerCase().startsWith(`${expected}${process.platform === "win32" ? "\\" : "/"}dsh-skin-isolated-`) && (await stat(home).catch(()
  • 删除本地文件递归
    apps/controller/src/gc.ts:130
    await rm(home, { recursive: true, force: true });
  • 读取本地文件
    apps/controller/src/gc.ts:155
    try { size = (await stat(journal)).size; } catch { return; }
  • 写入本地文件
    apps/controller/src/gc.ts:158
    await rename(journal, `${dirname(journal)}${process.platform === "win32" ? "\\" : "/"}journal-${stamp}.jsonl`);
  • 读取本地文件
    apps/controller/src/index.ts:471
    try { if (!(await stat(file)).isFile()) throw new Error("not-file"); }
  • 读取本地文件
    apps/controller/src/index.ts:474
    const content = await readFile(file);
  • 读取本地文件
    apps/controller/src/index.ts:536
    async function managedPluginExists(target: DshTarget): Promise<boolean> { try { return (await stat(join(target.dshHome, "profiles", "node_modules", "@
  • 执行外部命令来自配置/变量
    apps/controller/src/open.ts:11
    const child = spawn(executable, [url], { detached: true, stdio: "ignore", shell: false, windowsHide: true });
  • 读取本地文件
    apps/controller/src/open.ts:30
    for (const path of paths) { try { await access(path); return path; } catch {} }
  • 写入本地文件
    apps/controller/src/preview-runtime.ts:193
    await mkdir(target.dshHome, { recursive: true });
  • 执行外部命令来自配置/变量
    apps/controller/src/preview-runtime.ts:200
    const child = spawn(process.execPath, [runtime.bin, "web", "--host", "127.0.0.1", "--port", String(port)], { cwd: target.dshHome, shell: false, window
  • 发起网络请求
    apps/controller/src/preview-runtime.ts:221
    const response = await fetch(`${url}/dsh-skin/health`, { signal: AbortSignal.timeout(750) });
  • 删除本地文件递归
    apps/controller/src/preview-runtime.ts:396
    async function removeTempHome(id: string): Promise<void> { const home = tempHome(id); const expected = resolve(tmpdir()).toLowerCase(); if (!resolve(h
  • 删除本地文件递归
    apps/controller/src/preview-runtime.ts:397
    async function removeSessionRuntimeData(dataDir: string, id: string): Promise<void> { const root = resolve(dataDir, "isolated-preview-runtime"); const
  • 读取本地文件
    apps/controller/src/preview-runtime.ts:442
    const patch = await readFile(join(meta.home, "profiles", "web", "cordis.patch.yml"), "utf8");
  • 读取本地文件
    apps/controller/src/preview-runtime.ts:452
    for (const candidate of candidates) try { if (!(await stat(candidate)).isFile()) continue; const packageRoot = resolve(candidate, "..", ".."); if (!aw
  • 写入本地文件
    apps/controller/src/preview-runtime.ts:459
    await mkdir(targetScope, { recursive: true });
  • 读取本地文件
    apps/controller/src/preview-runtime.ts:469
    try { await stat(target); throw new AppError(ERROR_CODES.unavailable, `Temporary DSH package is incompatible: @deepseek-ai/${name}`, 503); } catch (er
  • 读取本地文件
    apps/controller/src/preview-runtime.ts:479
    const addScope = async (candidate: string) => { try { if ((await stat(candidate)).isDirectory()) scopes.add(resolve(candidate)); } catch {} };
  • 读取本地文件
    apps/controller/src/preview-runtime.ts:481
    try { await addScope(resolve(await realpath(sourcePackageRoot), "..")); } catch {}
  • 读取本地文件
    apps/controller/src/preview-runtime.ts:488
    const entries = await readdir(store, { withFileTypes: true });
  • 发起网络请求
    apps/controller/src/runtime.ts:67
    try { const response = await fetch(`${base}${path}`, { signal: AbortSignal.timeout(700) }); return response.ok; } catch { return false; }
  • 发起网络请求
    apps/controller/src/runtime.ts:71
    const response = await fetch(`${base}/dsh-skin/health`, { signal: AbortSignal.timeout(700), headers: { accept: "application/json" } });
  • 读取本地文件
    apps/controller/src/runtime.ts:87
    async function exists(path: string): Promise<boolean> { try { await access(path); return true; } catch { return false; } }
  • 写入本地文件
    apps/controller/src/selfcheck.ts:21
    await mkdir(join(dshHome, "profiles", "node_modules", "@deepseek-ai", "dsh"), { recursive: true });
  • 写入本地文件
    apps/controller/src/selfcheck.ts:22
    await mkdir(profileRoot, { recursive: true });
  • 写入本地文件
    apps/controller/src/selfcheck.ts:23
    await writeFile(join(dshHome, "profiles", "node_modules", "@deepseek-ai", "dsh", "package.json"), JSON.stringify({ name: "@deepseek-ai/dsh", version:
  • 写入本地文件
    apps/controller/src/selfcheck.ts:24
    await writeFile(join(profileRoot, "package.json"), `${JSON.stringify({ name: "fixture-profile", dependencies: { keep: "1.0.0" } }, null, 2)}\n`);
  • 写入本地文件
    apps/controller/src/selfcheck.ts:25
    await writeFile(join(profileRoot, "cordis.patch.yml"), "[]\n");
  • 执行外部命令来自配置/变量
    apps/controller/src/selfcheck.ts:28
    controller = spawn(process.execPath, [controllerEntry], {
  • 发起网络请求
    apps/controller/src/selfcheck.ts:39
    const landing = await fetch(discovery.url);
  • 读取本地文件
    apps/controller/src/selfcheck.ts:59
    const storedPreview = JSON.parse(await readFile(join(controllerData, "isolated-preview-sessions", `${preview.session.id}.json`), "utf8")) as Record<st
  • 删除本地文件递归
    apps/controller/src/selfcheck.ts:99
    if (dirname(root) === tmpdir()) await rm(root, { recursive: true, force: true });
  • 读取本地文件
    apps/controller/src/selfcheck.ts:105
    try { return JSON.parse(await readFile(path, "utf8")) as T; } catch { await new Promise((resolveWait) => setTimeout(resolveWait, 80)); }
  • 发起网络请求
    apps/controller/src/selfcheck.ts:112
    const response = await fetch(`${base}${path}`, init);
  • 发起网络请求
    apps/studio/src/api.ts:46
    return this.request(`/api/v1/design/${encodeURIComponent(id)}`, { method: "DELETE", body: {} });
  • 发起网络请求
    apps/studio/src/api.ts:50
    return this.request("/api/v1/theme/validate", { method: "POST", body: { theme } });
  • 发起网络请求
    apps/studio/src/api.ts:85
    return this.request("/api/v1/theme/apply-plan", { method: "POST", body: { designId: design.id, revision: design.revision, target: { profile: "web" },
  • 发起网络请求
    apps/studio/src/api.ts:89
    return this.request("/api/v1/theme/restore-plan", { method: "POST", body: { designId: design.id, revision: design.revision, target: { profile: "web" }
  • 发起网络请求
    apps/studio/src/api.ts:93
    return this.request("/api/v1/theme/confirm-and-apply", {
  • 发起网络请求
    apps/studio/src/api.ts:107
    return this.request("/api/v1/theme/confirm-and-restore", {
  • 执行外部命令来自配置/变量
    packages/agent-cli/src/client.ts:55
    const child = spawn(process.execPath, [controllerEntry], { detached: true, stdio: "ignore", shell: false, windowsHide: true, env: { ...process.env, ..
  • 读取本地文件
    packages/agent-cli/src/client.ts:71
    const discovery = JSON.parse(await readFile(path, "utf8")) as Discovery;
  • 读取本地文件
    packages/agent-cli/src/client.ts:82
    await access(entry);
  • 读取本地文件
    packages/agent-cli/src/client.ts:83
    if (!(await stat(entry)).isFile() || !entry.toLowerCase().endsWith(".js")) throw new Error("DSH_SKIN_CONTROLLER_ENTRY must identify a JavaScript file"
  • 发起网络请求
    packages/agent-cli/src/index.ts:10
    else if (group === "studio" && action === "start") { await client.ensureStarted(); result = await client.request("/api/v1/studio/start"); }
  • 发起网络请求
    packages/agent-cli/src/index.ts:11
    else if (group === "studio" && action === "open") { await client.ensureStarted(); result = await client.request("/api/v1/studio/open"); }
  • 发起网络请求
    packages/agent-cli/src/index.ts:12
    else if (group === "design" && action === "create") result = await client.request("/api/v1/session/design", "POST", parseJson(rest));
  • 发起网络请求
    packages/agent-cli/src/index.ts:13
    else if (group === "design" && action === "list") result = await client.request("/api/v1/designs", "GET");
  • 发起网络请求
    packages/agent-cli/src/index.ts:14
    else if (group === "design" && action === "get") result = await client.request(`/api/v1/design/${required(rest[0], "design id")}`, "GET");
  • 发起网络请求
    packages/agent-cli/src/index.ts:15
    else if (group === "design" && action === "patch") result = await client.request(`/api/v1/design/${required(rest[0], "design id")}`, "PATCH", parseJso
  • 读取本地文件
    packages/design-session-core/src/store.ts:14
    return JSON.parse(await readFile(this.path(relative), "utf8")) as T;
  • 写入本地文件
    packages/design-session-core/src/store.ts:23
    await mkdir(dirname(target), { recursive: true });
  • 写入本地文件
    packages/design-session-core/src/store.ts:25
    await writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
  • 写入本地文件
    packages/design-session-core/src/store.ts:26
    await rename(temp, target);
  • 写入本地文件
    packages/design-session-core/src/store.ts:31
    await mkdir(dirname(target), { recursive: true });
  • 写入本地文件
    packages/design-session-core/src/store.ts:32
    await appendFile(target, `${JSON.stringify(value)}\n`, "utf8");
  • 读取本地文件
    packages/design-session-core/src/store.ts:37
    return (await readdir(this.path(relative))).filter((name) => name.endsWith(suffix)).sort();
  • 删除本地文件
    packages/design-session-core/src/store.ts:45
    try { await unlink(this.path(relative)); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
  • 写入本地文件
    packages/design-session-core/src/store.ts:50
    await mkdir(dirname(lock), { recursive: true });
  • 删除本地文件
    packages/design-session-core/src/store.ts:60
    try { await unlink(lock); } catch {}
  • 读取本地文件
    packages/design-session-core/src/store.ts:64
    try { if (Date.now() - (await stat(lock)).mtimeMs > 30_000) { await unlink(lock); continue; } } catch {}
  • 删除本地文件
    packages/design-session-core/src/store.ts:64
    try { if (Date.now() - (await stat(lock)).mtimeMs > 30_000) { await unlink(lock); continue; } } catch {}
  • 发起网络请求
    packages/mcp-server/src/index.ts:17
    tool("studio_open", "Open the independent Studio web UI; returns a copyable URL if opening fails.", empty, async () => { await client.ensureStarted();
  • 发起网络请求
    packages/mcp-server/src/index.ts:18
    tool("design_create", "Create a shared revisioned design session.", { name: z.string().max(80).optional(), theme: z.unknown().optional() }, async (arg
  • 发起网络请求
    packages/mcp-server/src/index.ts:19
    tool("design_list", "List shared design sessions.", empty, async () => client.request("/api/v1/designs", "GET"));
  • 发起网络请求
    packages/mcp-server/src/index.ts:20
    tool("theme_get", "Read one shared design session.", { designId: z.string().uuid() }, async ({ designId }) => client.request(`/api/v1/design/${designI
  • 发起网络请求
    packages/mcp-server/src/index.ts:21
    tool("theme_patch", "Patch a design with optimistic baseRevision conflict protection.", { designId: z.string().uuid(), baseRevision: z.number().int().
  • 发起网络请求
    packages/mcp-server/src/index.ts:22
    tool("design_duplicate", "Duplicate a shared design.", { designId: z.string().uuid(), name: z.string().max(80).optional(), actor, patchId: z.string().
扫描于 2026/11/3(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/dsh
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 图片设计皮肤
  • 隔离预览
  • 确认后写入本机

README

由 DeepSeek 最新模型翻译生成
dsh-visual-skin DeepSeek Harness 皮肤工作室 为 DeepSeek Harness(DSH)制作、预览并安全写入皮肤的本地可视化工作台。 Target DSH Windows Local first License 点击演示图播放 MP4 · 查看动画源文件 · 查看分镜 先说结论 给一张图片,或直接在 Studio 里调色、调背景和区域效果;Skin Studio 会在隔离的临时 DSH中渲染结果。只有你在可见网页中审阅并确认后,主题才会写入本机 DSH。 | 你要做的事 | 得到什么 | | 用图片做皮肤 | Agent Skill / MCP 自动创建设计、上传资产并打开预览 | | 精细调整两块背景 | 侧边栏、主工作区、图片位置、遮罩和分隔线的即时预览 | | 安全写入本机 DSH | 不可变计划、可见确认、可恢复的安装记录 | 10 秒看懂流程 flowchart LR A[图片 / 你的描述] --> B[Skin Studio 设计] B --> C[隔离 DSH 真实预览] C --> D{你在 Studio 中确认} D -- 确认 --> E[写入你的 DSH] D -- 继续修改 --> B Agent 和 MCP 可以帮你完成设计与预览;不能伪造最后的写入确认。
展开完整文档(README 剩余 1 节)
dsh-visual-skin

DeepSeek Harness 皮肤工作室

为 DeepSeek Harness(DSH)制作、预览并安全写入皮肤的本地可视化工作台。

Target DSH
Windows
Local first
License

点击演示图播放 MP4 · 查看动画源文件 · 查看分镜

先说结论

给一张图片,或直接在 Studio 里调色、调背景和区域效果;Skin Studio 会在隔离的临时 DSH中渲染结果。只有你在可见网页中审阅并确认后,主题才会写入本机 DSH。

| 你要做的事 | 得到什么 |
| 用图片做皮肤 | Agent Skill / MCP 自动创建设计、上传资产并打开预览 |
| 精细调整两块背景 | 侧边栏、主工作区、图片位置、遮罩和分隔线的即时预览 |
| 安全写入本机 DSH | 不可变计划、可见确认、可恢复的安装记录 |

10 秒看懂流程

flowchart LR
A[图片 / 你的描述] --> B[Skin Studio 设计]
B --> C[隔离 DSH 真实预览]
C --> D{你在 Studio 中确认}
D -- 确认 --> E[写入你的 DSH]
D -- 继续修改 --> B

Agent 和 MCP 可以帮你完成设计与预览;不能伪造最后的写入确认。

功能一览

| 模块 | 做什么 | 为什么有用 |
| Skin Studio | 图片、纯色、渐变;双区域背景;遮罩、玻璃拟态、token 调色与分隔线 | 所见即所得,不必手改配置文件 |
| 隔离预览 | 使用 Controller 管理的临时 DSH 实例,并复用 warm runner | 预览不触碰正在使用的 DSH,也减少重复启动负载 |
| DSH 插件 | 常驻背景层、对话蒙版、侧边栏分隔与「皮肤设置」入口 | 切换对话后仍保持皮肤,不需要重新注入页面 |
| Agent Skill | 理解「换个皮肤」「用这张图做背景」等请求并编排流程 | DSH、Codex 等 agent 都能接入同一套设计能力 |
| MCP Server | design_create、asset_upload、theme_patch、preview_start、theme_apply 等工具 | 让 agent 有结构化、可审计的操作接口 |
| 安全写入 | 隔离预览回执 + 不可变 apply 计划 + 人工确认 | 写入前知道会改什么,失败时可恢复 |

安装

| 前置条件 | 版本 / 说明 |
| Windows | 10 或 11 |
| Node.js | 22+ |
| pnpm | 11+;首次构建便携运行时时需要 |
| DeepSeek Harness | 0.1.0-rc.6;版本不匹配时插件会 fail-closed |

git clone https://github.com/trrrrrryg/dsh-visual-skin.git
cd dsh-visual-skin
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1

安装器会构建便携运行时、安装 Skill、注册 DSH 插件与 MCP,并启动 Studio。默认不重启 DSH,避免打断正在进行的会话;完成后请自行重启一次 DSH。

常用安装参数

| 参数 | 作用 |
| -DshHome | 指定 DSH 根目录,默认 %USERPROFILE%\.dsh |
| -ProfileName | 指定 profile,默认 web |
| -DataDir | Studio 数据目录 |
| -ControllerPort | Studio Controller 端口,默认 11862 |
| -SkipBuild | 复用已构建 runtime |
| -SkipSkill / -SkipStudio / -SkipCodexMcp | 跳过可选组件 |
| -RestartDsh | 安装后自动重启 DSH |

.\install.ps1 -DshHome C:\Users\me\.dsh -ProfileName web -ControllerPort 12000 -RestartDsh

使用

在 Studio 中手动设计

1. 打开 http://127.0.0.1:11862,或从 DSH 的「设置 → 插件 → 皮肤设置」打开 Studio。
2. 上传图片,或调整背景、区域和 token,等待隔离预览就绪。
3. 点击 写入我的 DSH,审阅计划并亲自勾选确认;随后重启 DSH。

- 勾选整合两区域:背景会是一张随窗口缩放保持连续的画面。
- 取消勾选:可分别编辑侧边栏与主工作区,使用不同的颜色、渐变或图片。
- 所有变更先进入实时隔离预览;正式 DSH 在确认前不会被改动。

直接对 agent 说

- 「给我换个皮肤」
- 「用这张图片做背景」
- 「把现在的皮肤背景换掉」

Agent 会调用 mcp__skin-studio__* 完成设计与隔离预览,并把最终确认留在 Studio 网页中。

验证安装

DSH 插件健康检查(DSH 默认端口为 10402) Invoke-RestMethod http://127.0.0.1:10402/dsh-skin/health

Studio Controller 状态 Invoke-RestMethod http://127.0.0.1:11862/api/v1/status

开发

pnpm install
pnpm build # 构建全部包
pnpm test # 回归测试;会启动真实 rc.6 隔离预览
pnpm controller # 本地启动 Controller
pnpm dev # Studio Vite 开发模式

目录说明

apps/controller/ # API、隔离预览、apply/restore、GC apps/studio/ # React + Vite Studio packages/dsh-plugin/ # DSH host 路由与 client 皮肤渲染 packages/theme-schema/ # ThemeSpec v2 数据契约 packages/mcp-server/ # MCP stdio 服务 agents/codex-skill/ # 可安装 Agent Skill 与脚本 tests/ # 含真实 rc.6 Chromium 隔离预览的回归测试 docs/demo/ # README 动画、分镜与导出脚本 docx/ # 实施计划、需求分析、架构文档

安全与兼容

- ThemeSpec 只接受受控的颜色、内容寻址图片(sha256)和 token;不接收任意 CSS、JS 或 URL。
- 预览只在 Controller 拥有的临时 DSH 里运行;浏览器、MCP 和 agent 都拿不到写入确认凭据。
- 当前目标版本是 0.1.0-rc.6;不兼容时拒绝加载,而不是猜测性修改 DSH。

卸载

1. 从 %DSH_HOME%\profiles\\cordis.patch.yml 移除 # >>> dsh-skin-studio 到 # <<< dsh-skin-studio 之间的托管条目;
2. 删除 %DSH_HOME%\profiles\node_modules\@dsh-skin 和 %DSH_HOME%\skills\deepseek-harness-skin-studio;
3. 重启 DSH;如需清理 Studio 数据,再删除 %LOCALAPPDATA%\DeepSeekHarnessSkinStudio。

文档

- 变更记录
- 发布清单
- 项目文档
- MIT License

MIT © 2026 trrrrrryg
预览分享卡片长什么样 ↓trrrrrryg/dsh-visual-skin
分享 · DeepSeek Harness Hub
trrrrrryg/dsh-visual-skin

用图片设计并预览皮肤,确认后安全写入本机

这是你的仓库?认领条目

回到顶部