@xinxing1708/dsh-codex-style 0.0.0-stage → 0.1.3
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/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +116 -2
- package/THIRD_PARTY_NOTICES.md +21 -0
- package/cordis.patch.yml +3 -0
- package/install.cmd +17 -0
- package/lib/client.js +1453 -0
- package/lib/host.js +3 -0
- package/licenses/DeepSeek-Harness-LICENSE +21 -0
- package/package.json +63 -4
- package/scripts/install.mjs +110 -0
- package/scripts/rollback.mjs +20 -0
- package/skin.json +11 -0
package/lib/host.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xinxing1708/dsh-codex-style",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "DSH 的 Codex 风格深色界面插件",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/host.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/host.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "node scripts/build.mjs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"cordis.patch.yml",
|
|
18
|
+
"skin.json",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"CHANGELOG.md",
|
|
22
|
+
"THIRD_PARTY_NOTICES.md",
|
|
23
|
+
"install.cmd",
|
|
24
|
+
"scripts/install.mjs",
|
|
25
|
+
"scripts/rollback.mjs",
|
|
26
|
+
"licenses"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"registry": "https://registry.npmjs.org"
|
|
32
|
+
},
|
|
33
|
+
"dsh": {
|
|
34
|
+
"engines": {
|
|
35
|
+
"dsh": "0.2.1-rc.2"
|
|
36
|
+
},
|
|
37
|
+
"bundle": {
|
|
38
|
+
"patch": "./cordis.patch.yml"
|
|
39
|
+
},
|
|
40
|
+
"client": {
|
|
41
|
+
"platform": "web",
|
|
42
|
+
"inject": [
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-theme",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-workspace",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
47
|
+
"@deepseek-ai/dsh-client-ui-model-selection",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-permission-presets",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-settings-general",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/xinxing1708/DSH-Codex-Style.git"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/xinxing1708/DSH-Codex-Style#readme",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/xinxing1708/DSH-Codex-Style/issues"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=20"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// 安装到当前用户 desktop profile;只复制本包,旧主题保留以便回退。
|
|
2
|
+
import { readFile, writeFile, mkdir, copyFile, lstat, symlink, realpath, access, unlink } from 'node:fs/promises';
|
|
3
|
+
import { resolve, dirname, relative, isAbsolute } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
const source = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const dshRoot = resolve(process.env.DSH_HOME || resolve(homedir(), '.dsh'));
|
|
10
|
+
const profile = resolve(dshRoot, 'profiles/desktop');
|
|
11
|
+
const manifest = JSON.parse(await readFile(resolve(source, 'package.json'), 'utf8'));
|
|
12
|
+
const runtime = JSON.parse(await readFile(resolve(dshRoot, 'dsh-runtimes/dsh-primary-runtime/runtime.json'), 'utf8'));
|
|
13
|
+
if (runtime.desktopVersion !== manifest.dsh.engines.dsh) throw new Error(`DSH 版本不匹配:${runtime.desktopVersion}`);
|
|
14
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
15
|
+
const target = resolve(dshRoot, 'dsh-codex-style', manifest.version + '-' + stamp);
|
|
16
|
+
const backup = resolve(dshRoot, 'codex-style-backups', stamp);
|
|
17
|
+
const packagePath = resolve(profile, 'package.json');
|
|
18
|
+
const patchPath = resolve(profile, 'cordis.patch.yml');
|
|
19
|
+
const lockPath = resolve(profile, 'pnpm-lock.yaml');
|
|
20
|
+
const linkPath = resolve(profile, 'node_modules', manifest.name);
|
|
21
|
+
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
22
|
+
const originals = await Promise.all([packagePath, patchPath, lockPath].map(async path => ({ path, bytes: await readFile(path) })));
|
|
23
|
+
const config = JSON.parse(originals[0].bytes.toString());
|
|
24
|
+
const legacyName = 'dsh-codex-style';
|
|
25
|
+
let previousTarget;
|
|
26
|
+
let previousName;
|
|
27
|
+
let previousLinkPath;
|
|
28
|
+
// 仅迁移本安装器管理的旧包;npm 上的同名第三方包不能按版本号认领。
|
|
29
|
+
for (const name of new Set([manifest.name, legacyName])) {
|
|
30
|
+
const candidatePath = resolve(profile, 'node_modules', name);
|
|
31
|
+
const previousDependency = config.dependencies?.[name];
|
|
32
|
+
const previousLink = await lstat(candidatePath).catch(e => { if (e.code === 'ENOENT') return null; throw e; });
|
|
33
|
+
if (!previousDependency && !previousLink) continue;
|
|
34
|
+
if (!previousDependency) throw new Error('node_modules 中已存在同名路径,停止以免覆盖:' + name);
|
|
35
|
+
if (!previousDependency.startsWith('link:') || !previousLink?.isSymbolicLink()) throw new Error('同名安装不是本插件的本地链接,请人工核对。');
|
|
36
|
+
const candidateTarget = await realpath(candidatePath);
|
|
37
|
+
const managedRelative = relative(resolve(dshRoot, 'dsh-codex-style'), candidateTarget);
|
|
38
|
+
if (!managedRelative || managedRelative.startsWith('..') || isAbsolute(managedRelative) || resolve(profile, previousDependency.slice(5)) !== resolve(candidateTarget)) throw new Error('现有插件链接不在受管理目录,停止升级。');
|
|
39
|
+
const previousManifest = JSON.parse(await readFile(resolve(candidateTarget, 'package.json'), 'utf8'));
|
|
40
|
+
if (previousManifest.name !== name) throw new Error('旧插件身份不匹配');
|
|
41
|
+
if (previousTarget) throw new Error('新旧包同时存在,请先核对安装来源。');
|
|
42
|
+
previousTarget = candidateTarget;
|
|
43
|
+
previousName = name;
|
|
44
|
+
previousLinkPath = candidatePath;
|
|
45
|
+
}
|
|
46
|
+
await access(resolve(source, 'lib/client.js'));
|
|
47
|
+
await access(resolve(source, 'lib/host.js'));
|
|
48
|
+
await mkdir(backup, { recursive: true });
|
|
49
|
+
for (const entry of originals) await writeFile(resolve(backup, entry.path.split(/[\\/]/).pop()), entry.bytes);
|
|
50
|
+
await mkdir(resolve(target, 'lib'), { recursive: true });
|
|
51
|
+
await mkdir(resolve(target, 'licenses'), { recursive: true });
|
|
52
|
+
for (const file of ['package.json', 'cordis.patch.yml', 'skin.json', 'README.md', 'LICENSE', 'CHANGELOG.md', 'THIRD_PARTY_NOTICES.md', 'licenses/DeepSeek-Harness-LICENSE', 'lib/client.js', 'lib/host.js']) await copyFile(resolve(source, file), resolve(target, file));
|
|
53
|
+
|
|
54
|
+
const localLink = 'link:' + relative(profile, target).replaceAll('\\', '/');
|
|
55
|
+
if (previousName && previousName !== manifest.name) delete config.dependencies[previousName];
|
|
56
|
+
config.dependencies[manifest.name] = localLink;
|
|
57
|
+
const oldSkins = new Set(['dsh-claude-style', '@michengai/dsh-codex-ui']);
|
|
58
|
+
config.dsh.profile.bundles = config.dsh.profile.bundles.filter(name => !oldSkins.has(name) && name !== manifest.name && name !== previousName);
|
|
59
|
+
config.dsh.profile.bundles.push(manifest.name);
|
|
60
|
+
const eol = originals[1].bytes.includes(Buffer.from('\r\n')) ? '\r\n' : '\n';
|
|
61
|
+
const patchBefore = originals[1].bytes.toString();
|
|
62
|
+
const ownBlock = /^- id: ui-skin-codex-style\r?\n(?:[ \t]+[^\r\n]*(?:\r?\n|$))*/gm;
|
|
63
|
+
const ownBlocks = [...patchBefore.matchAll(ownBlock)];
|
|
64
|
+
if (ownBlocks.length > 1 || (previousTarget && ownBlocks.length !== 1)) throw new Error('未识别唯一的皮肤配置行;未改 profile。');
|
|
65
|
+
const patch = ownBlocks.length ? patchBefore.replace(ownBlock, block => {
|
|
66
|
+
const nameLine = /^ name: (.+)$/m;
|
|
67
|
+
const oldName = block.match(nameLine)?.[1].trim().replace(/^(['"])(.*)\1$/, '$2');
|
|
68
|
+
if (oldName !== manifest.name && oldName !== previousName) throw new Error('皮肤配置指向其他包,未改 profile。');
|
|
69
|
+
return block.replace(nameLine, () => ' name: ' + JSON.stringify(manifest.name));
|
|
70
|
+
}) : patchBefore + eol + [
|
|
71
|
+
'# Codex Style:旧主题依赖保留,仅停用其界面行。',
|
|
72
|
+
'- id: ui-skin-claude-style',
|
|
73
|
+
' disabled: true',
|
|
74
|
+
'- id: ui-skin-codex-style',
|
|
75
|
+
' name: ' + JSON.stringify(manifest.name),
|
|
76
|
+
' disabled: false',
|
|
77
|
+
''
|
|
78
|
+
].join(eol);
|
|
79
|
+
const lockBefore = originals[2].bytes.toString();
|
|
80
|
+
const header = /(importers:\r?\n\r?\n \.:\r?\n dependencies:\r?\n)/;
|
|
81
|
+
if (!header.test(lockBefore)) throw new Error('未识别 desktop 锁文件结构;备份已保留,未改 profile。');
|
|
82
|
+
const lockEntry = ` '${manifest.name}':${eol} specifier: ${localLink}${eol} version: ${localLink}${eol}`;
|
|
83
|
+
const escapedPreviousName = (previousName || manifest.name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
84
|
+
const previousLockEntry = new RegExp(`^ (?:'${escapedPreviousName}'|"${escapedPreviousName}"|${escapedPreviousName}):\\r?\\n specifier: [^\\r\\n]+\\r?\\n version: [^\\r\\n]+(?:\\r?\\n|$)`, 'm');
|
|
85
|
+
if (previousTarget && !previousLockEntry.test(lockBefore)) throw new Error('未识别旧插件锁文件条目;未改 profile。');
|
|
86
|
+
const lockFinal = previousTarget ? lockBefore.replace(previousLockEntry, () => lockEntry) : lockBefore.replace(header, match => match + lockEntry);
|
|
87
|
+
const outputs = [Buffer.from(JSON.stringify(config, null, 2) + '\n'), Buffer.from(patch), Buffer.from(lockFinal)];
|
|
88
|
+
const receipt = { name: manifest.name, version: manifest.version, target, previousTarget, previousName, previousLinkPath, linkPath, profile, backup, files: originals.map((entry, index) => ({ path: entry.path, backup: resolve(backup, entry.path.split(/[\\/]/).pop()), before: hash(entry.bytes), after: hash(outputs[index]) })), clientHash: hash(await readFile(resolve(target, 'lib/client.js'))) };
|
|
89
|
+
await writeFile(resolve(backup, 'installation.json'), JSON.stringify(receipt, null, 2) + '\n');
|
|
90
|
+
await copyFile(resolve(source, 'scripts/rollback.mjs'), resolve(backup, 'rollback.mjs'));
|
|
91
|
+
try {
|
|
92
|
+
for (const entry of receipt.files) if (hash(await readFile(entry.path)) !== entry.before) throw new Error('安装前配置发生更改,停止:' + entry.path);
|
|
93
|
+
await mkdir(dirname(linkPath), { recursive: true });
|
|
94
|
+
if (previousTarget) await unlink(previousLinkPath);
|
|
95
|
+
await symlink(target, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
96
|
+
for (let i = 0; i < originals.length; i++) {
|
|
97
|
+
if (hash(await readFile(originals[i].path)) !== receipt.files[i].before) throw new Error('安装期间配置发生更改,停止以保留新内容:' + originals[i].path);
|
|
98
|
+
await writeFile(originals[i].path, outputs[i]);
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// 仅恢复仍保持为本次写入内容的文件,避免覆盖同时发生的人工更改。
|
|
102
|
+
for (const entry of receipt.files) if (hash(await readFile(entry.path)) === entry.after) await copyFile(entry.backup, entry.path);
|
|
103
|
+
const linked = await realpath(linkPath).catch(e => { if (e.code === 'ENOENT') return null; throw e; });
|
|
104
|
+
if (linked && resolve(linked) === target) await unlink(linkPath);
|
|
105
|
+
if (previousTarget && !(await lstat(previousLinkPath).catch(e => { if (e.code === 'ENOENT') return null; throw e; }))) await symlink(previousTarget, previousLinkPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
const installed = await realpath(linkPath);
|
|
109
|
+
if (resolve(installed) !== target) throw new Error('插件链接目标不匹配');
|
|
110
|
+
console.log(JSON.stringify({ installed: true, name: manifest.name, version: manifest.version, target, profile, backup, rollback: resolve(backup, 'rollback.mjs'), clientHash: receipt.clientHash, restartRequired: true }, null, 2));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// 只在安装后未发生其它配置修改时恢复备份;不删除旧主题或用户数据。
|
|
2
|
+
import { readFile, copyFile, realpath, lstat, unlink, symlink } from 'node:fs/promises';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
const receipt = JSON.parse(await readFile(resolve(dirname(fileURLToPath(import.meta.url)), 'installation.json'), 'utf8'));
|
|
7
|
+
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
8
|
+
for (const entry of receipt.files) {
|
|
9
|
+
const current = hash(await readFile(entry.path));
|
|
10
|
+
if (current !== entry.after && current !== entry.before) throw new Error('安装后配置已有更新,请手动合并备份,停止覆盖:' + entry.path);
|
|
11
|
+
if (hash(await readFile(entry.backup)) !== entry.before) throw new Error('备份摘要不匹配:' + entry.backup);
|
|
12
|
+
}
|
|
13
|
+
const linkInfo = await lstat(receipt.linkPath).catch(e => { if (e.code === 'ENOENT') return null; throw e; });
|
|
14
|
+
if (linkInfo && (!linkInfo.isSymbolicLink() || resolve(await realpath(receipt.linkPath)) !== resolve(receipt.target))) throw new Error('插件链接已变化,停止回退');
|
|
15
|
+
const previousLinkPath = receipt.previousLinkPath || receipt.linkPath;
|
|
16
|
+
if (receipt.previousTarget && previousLinkPath !== receipt.linkPath && await lstat(previousLinkPath).catch(e => { if (e.code === 'ENOENT') return null; throw e; })) throw new Error('旧包链接已被重新创建,停止回退');
|
|
17
|
+
for (const entry of receipt.files) await copyFile(entry.backup, entry.path);
|
|
18
|
+
if (linkInfo) await unlink(receipt.linkPath);
|
|
19
|
+
if (receipt.previousTarget) await symlink(receipt.previousTarget, previousLinkPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
20
|
+
console.log('已恢复安装前的 desktop 配置。插件产物保留在 ' + receipt.target + ';重启 DSH 后生效。');
|
package/skin.json
ADDED