Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,30 @@ beautiCode 可以让你:

### 一键安装插件(推荐)

只要给 DeepSeek Harness 加背景,装插件即可,不必下 Windows 安装包,也不必开托盘。请先自己装好 DSH。
只要给 DeepSeek Harness 加背景,装插件即可,不必 fork 仓库、不必下 Windows 安装包,也不必开托盘。请先自己装好 DSH 和 Node.js

在项目目录执行:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install-dsh-plugin.ps1
```sh
npx beauticode-dsh
npx @deepseek-ai/dsh web
```

这条脚本会把插件写进你的 DSH profile,**不需要 pnpm,也不需要再执行 `dsh plugin add`**。
`npx beauticode-dsh` 会从 npm 下载插件并写入你的 DSH profile,**不需要 pnpm,也不需要再执行 `dsh plugin add`**。已把 `dsh` 装到 PATH 时,第二行也可以写成 `dsh web`

然后自己启动:
打开网页后,侧栏「设置」上方有「背景」:可从文件夹选图片或 MP4、清除、开关声音、切换已保存主题。已保存主题里自带「画窗」。网页控制台没有摸鱼。外观浅色/深色仍用 DSH 自己的设置。下次启动会恢复上次背景。

也可以用 `/bg`、`/bg-theme`、`/bg-clear`,或直接跟 AI 说把本机图片/视频设成背景。

已有 pnpm 时,也可以让 DSH 自己装这个 npm 包:

```sh
dsh web
# 未把 dsh 装到 PATH 时:
npx @deepseek-ai/dsh plugin --profile web add beauticode-dsh
npx @deepseek-ai/dsh web
```

打开网页后,侧栏「设置」上方有「背景」:可从文件夹选图片或 MP4、清除、开关声音、切换已保存主题。网页控制台没有摸鱼。外观浅色/深色仍用 DSH 自己的设置。下次启动会恢复上次背景。

也可以用 `/bg`、`/bg-theme`、`/bg-clear`,或直接跟 AI 说把本机图片/视频设成背景。

若一键脚本不可用,再手动装(`dsh plugin add` 需要本机有 pnpm):
卸载:

```sh
dsh plugin --profile web add file:%CD%/integrations/deepseek-harness
# 或
npx @deepseek-ai/dsh plugin --profile web add file:%CD%/integrations/deepseek-harness
npx beauticode-dsh --remove
```
<img width="471" height="450" alt="aa80cb6fa224ff3bd45a2c43e3f9a7b6" src="https://github.com/user-attachments/assets/44ed8608-6a89-4308-b4a8-e721d33f4184" />

Expand Down Expand Up @@ -155,6 +151,13 @@ npx @deepseek-ai/dsh web
# npm run tray
```

或手动把本地插件目录加进 DSH(需要 pnpm):

```sh
npx @deepseek-ai/dsh plugin --profile web add file:%CD%/integrations/deepseek-harness
npx @deepseek-ai/dsh web
```

或打开宿主选择器:

```powershell
Expand Down
16 changes: 15 additions & 1 deletion docs/deepseek-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ Windows 安装包会在安装结束时(以及选择 DeepSeek Harness 时)自

若改过安装目录,看安装文件夹里的 `集成说明.txt`,不要照抄默认路径。

不需要 fork 仓库。一行安装(不需要 pnpm):

```sh
npx beauticode-dsh
npx @deepseek-ai/dsh web
```

已有 pnpm 时也可以:

```sh
npx @deepseek-ai/dsh plugin --profile web add beauticode-dsh
npx @deepseek-ai/dsh web
```

也可手动(`dsh plugin add` 需要本机有 pnpm):

```sh
Expand All @@ -34,7 +48,7 @@ dsh plugin --profile web add file:%LOCALAPPDATA%\Programs\beautiCode\integration
npx @deepseek-ai/dsh plugin --profile web add file:%LOCALAPPDATA%\Programs\beautiCode\integrations\deepseek-harness
```

包尚未发布到 npm,请按路径安装。未把 `dsh` 装到 PATH 时,用上面的 `npx @deepseek-ai/dsh` 写法。
未把 `dsh` 装到 PATH 时,用上面的 `npx @deepseek-ai/dsh` 写法。

## 启用插件

Expand Down
16 changes: 15 additions & 1 deletion integrations/deepseek-harness/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@

beautiCode **不启动** DeepSeek Harness。请先自己运行 `dsh web`。

推荐在仓库根目录一键写入插件(不需要 pnpm):
不需要 fork 仓库。一行安装:

```sh
npx beauticode-dsh
npx @deepseek-ai/dsh web
```

已有 pnpm 时也可以:

```sh
npx @deepseek-ai/dsh plugin --profile web add beauticode-dsh
npx @deepseek-ai/dsh web
```

在仓库根目录一键写入插件(不需要 pnpm):

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install-dsh-plugin.ps1
Expand Down
219 changes: 219 additions & 0 deletions integrations/deepseek-harness/bin/beauticode-dsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#!/usr/bin/env node
/**
* One-line installer: npx beauticode-dsh
*
* Copies this package to a stable local folder and writes the DSH patch so
* `dsh web` loads beautiCode. Does not start DeepSeek Harness.
*/
import fs from "node:fs";
import fsp from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";

const here = path.dirname(fileURLToPath(import.meta.url));
const pluginName = "@beauticode/dsh-plugin";
const bridgeId = "beauticode-bridge";

function argValue(argv, name) {
const idx = argv.indexOf(name);
if (idx === -1) return null;
const value = argv[idx + 1];
return value && !value.startsWith("--") ? value : null;
}

function defaultDataRoot() {
if (process.env.BEAUTICODE_DATA_ROOT) return process.env.BEAUTICODE_DATA_ROOT;
if (process.env.LOCALAPPDATA) {
return path.join(process.env.LOCALAPPDATA, "beautiCode");
}
return path.join(os.homedir(), ".beauticode");
}

function defaultDshHome() {
return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
}

function toFileUri(filePath) {
const full = path.resolve(filePath).replaceAll("\\", "/");
if (/^[A-Za-z]:/.test(full)) return `file:///${full}`;
return pathToFileURL(full).href;
}

function fileUriInsert(uri) {
return [
"# beauticode-bridge (installer)",
"- insert:",
" - id: beauticode-bridge",
` name: '${uri}'`,
" inject: [webServer]",
"",
].join("\n");
}

function packageInsert() {
return [
"# beauticode-bridge (installer)",
"- insert:",
" - id: beauticode-bridge",
` name: '${pluginName}'`,
" inject: [webServer]",
"",
].join("\n");
}

function hasBridge(text) {
return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
}

function stripBridge(text) {
const patterns = [
/(?:^|\r?\n)# beauticode-bridge \(installer\)\r?\n- insert:\r?\n(?:[ \t]+.*\r?\n)*/g,
/(?:^|\r?\n)- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/g,
];
let next = text;
for (const pattern of patterns) next = next.replace(pattern, "\n");
return next;
}

async function writePatch(filePath, body) {
await fsp.mkdir(path.dirname(filePath), { recursive: true });
if (!fs.existsSync(filePath)) {
await fsp.writeFile(filePath, body, "utf8");
return;
}
const raw = await fsp.readFile(filePath, "utf8");
if (hasBridge(raw)) {
const replaced = raw.replace(
/(?:# beauticode-bridge \(installer\)\r?\n)?- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/,
body,
);
await fsp.writeFile(filePath, replaced.endsWith("\n") ? replaced : `${replaced}\n`, "utf8");
return;
}
const stripped = raw.trim();
if (stripped === "" || stripped === "[]") {
await fsp.writeFile(filePath, body, "utf8");
return;
}
await fsp.writeFile(filePath, `${stripped}\n\n${body}`, "utf8");
}

async function removePatch(filePath) {
if (!fs.existsSync(filePath)) return false;
const raw = await fsp.readFile(filePath, "utf8");
if (!hasBridge(raw)) return false;
const cleaned = stripBridge(raw).trim();
if (cleaned === "" || cleaned === "[]") {
await fsp.writeFile(
filePath,
"# Your patch layer for this dsh profile.\n[]\n",
"utf8",
);
return true;
}
await fsp.writeFile(filePath, `${cleaned}\n`, "utf8");
return true;
}

function shouldCopy(source) {
const relative = path.relative(here, source);
if (relative.startsWith("test") || relative.includes(`${path.sep}test${path.sep}`)) {
return false;
}
if (relative.endsWith(".test.mjs")) return false;
return true;
}

async function copyPackage(dest) {
await fsp.rm(dest, { recursive: true, force: true });
await fsp.cp(here, dest, {
recursive: true,
filter: (source) => shouldCopy(source),
});
}

async function ensureEngine(dest) {
const vendor = path.join(dest, "vendor", "adapter-dsh", "index.js");
if (fs.existsSync(vendor)) return;
const packPath = path.resolve(here, "../../scripts/pack-dsh-plugin.mjs");
if (!fs.existsSync(packPath)) {
throw new Error("插件包不完整:缺少本机导入引擎。请重新执行 npx beauticode-dsh。");
}
const { stageEngineInto } = await import(pathToFileURL(packPath).href);
await stageEngineInto(dest);
}

async function install(opts) {
const dest = path.resolve(opts.pluginHome);
const dshHome = path.resolve(opts.dshHome);
const webProfile = path.join(dshHome, "profiles", "web");
const webPatch = path.join(webProfile, "cordis.patch.yml");
const webPackage = path.join(webProfile, "package.json");
const homePatch = path.join(dshHome, "cordis.patch.yml");

await fsp.mkdir(dest, { recursive: true });
await copyPackage(dest);
await ensureEngine(dest);
const indexFile = path.join(dest, "index.mjs");
if (!fs.existsSync(indexFile)) {
throw new Error(`缺少插件入口:${indexFile}`);
}

if (fs.existsSync(webPackage)) {
await writePatch(webPatch, fileUriInsert(toFileUri(indexFile)));
if (fs.existsSync(homePatch)) {
const homeRaw = await fsp.readFile(homePatch, "utf8");
if (hasBridge(homeRaw)) await removePatch(homePatch);
}
console.log(`已写入 ${webPatch}`);
console.log(`插件已复制到 ${dest}`);
console.log("请自己运行:npx @deepseek-ai/dsh web");
return { dest, patch: webPatch };
}

await fsp.mkdir(dshHome, { recursive: true });
await writePatch(homePatch, fileUriInsert(toFileUri(indexFile)));
console.log(`DSH web profile 还不存在,已写入 ${homePatch}`);
console.log(`插件已复制到 ${dest}`);
console.log("请自己运行:npx @deepseek-ai/dsh web");
return { dest, patch: homePatch };
}

async function uninstall(opts) {
const dshHome = path.resolve(opts.dshHome);
const removed = [];
if (await removePatch(path.join(dshHome, "profiles", "web", "cordis.patch.yml"))) {
removed.push("web patch");
}
if (await removePatch(path.join(dshHome, "cordis.patch.yml"))) {
removed.push("home patch");
}
const dest = path.resolve(opts.pluginHome);
if (fs.existsSync(dest)) {
await fsp.rm(dest, { recursive: true, force: true });
removed.push(dest);
}
console.log(removed.length ? `已移除:${removed.join("、")}` : "没有可移除的 beautiCode 插件接线。");
return { removed };
}

export async function runCli(argv = process.argv.slice(2)) {
const dshHome = argValue(argv, "--dsh-home") || defaultDshHome();
const pluginHome =
argValue(argv, "--plugin-home") || path.join(defaultDataRoot(), "plugin");
const opts = { dshHome, pluginHome };
if (argv.includes("--remove")) return uninstall(opts);
return install(opts);
}

const launchedDirectly =
Boolean(process.argv[1]) &&
pathToFileURL(path.resolve(process.argv[1])).href.toLowerCase() ===
import.meta.url.toLowerCase();
if (launchedDirectly) {
runCli().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
}
Loading
Loading