dsh-her 0.2.0 → 0.3.0
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.md +1 -1
- package/lib/dsh/body-install.js +25 -8
- package/lib/dsh/body-launcher.js +1 -1
- package/lib/platform.js +12 -0
- package/lib/types/dsh/body-install.d.ts +5 -1
- package/lib/types/platform.d.ts +9 -0
- package/lib/voice/models.js +2 -1
- package/lib/voice/uv.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
| M3 外形包与姿势图 | ✅ mac 通过([记录](docs/M3记录.md));用合成测试包验证,"想看 JK" 全流程跑通;待真实素材校准 |
|
|
15
15
|
| M4 内置记忆 | ✅ mac 通过([记录](docs/M4记录.md));换全新会话仍记得,删掉的事不再提 |
|
|
16
16
|
| M5 向导与管理页 | ✅ mac 通过([记录](docs/M5记录.md));dsh 侧栏"她":7 步向导 + 管理页。身体的下载安装挪到 M6 |
|
|
17
|
-
| M6 打磨与发布 | 进行中([记录](docs/M6记录.md)):mac 身体打包、一键下载安装和自动更新、Edge 声音不再依赖系统 Python
|
|
17
|
+
| M6 打磨与发布 | 进行中([记录](docs/M6记录.md)):mac 身体打包、一键下载安装和自动更新、Edge 声音不再依赖系统 Python;Windows 身体打包和安装已写好、等实测([Windows适配](docs/Windows适配.md));签名公证、省电待做 |
|
|
18
18
|
|
|
19
19
|
## 组成
|
|
20
20
|
|
package/lib/dsh/body-install.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* { "format": "dsh-her-body@1", "version": "0.1.0",
|
|
7
7
|
* "assets": { "darwin-arm64": { "file": "dsh-her-body-0.1.0-darwin-arm64.zip", "size": …, "sha256": "…" } } }
|
|
8
8
|
*
|
|
9
|
+
* The app is `dsh-her.app` on macOS and a `dsh-her` folder with
|
|
10
|
+
* `dsh-her.exe` on Windows (body/scripts/package.mjs makes both).
|
|
9
11
|
* `<home>/body/current.json` says which version is installed. A download
|
|
10
12
|
* that does not match its SHA-256 is thrown away; the previous version stays
|
|
11
13
|
* until the new one is in place, and one older version is kept to go back to.
|
|
@@ -17,9 +19,13 @@ import { join } from 'node:path';
|
|
|
17
19
|
import { Readable } from 'node:stream';
|
|
18
20
|
import { pipeline } from 'node:stream/promises';
|
|
19
21
|
import { promisify } from 'node:util';
|
|
22
|
+
import { systemTar } from "../platform.js";
|
|
20
23
|
const run = promisify(execFile);
|
|
21
24
|
export const MANIFEST = 'body-latest.json';
|
|
22
|
-
|
|
25
|
+
/** The app inside a release zip (and in `<home>/body/<version>/`). */
|
|
26
|
+
export const appName = (platform = process.platform) => (platform === 'win32' ? 'dsh-her' : 'dsh-her.app');
|
|
27
|
+
/** Platforms with a body in the release. */
|
|
28
|
+
const PACKAGED = new Set(['darwin', 'win32']);
|
|
23
29
|
/** This computer's asset key in the manifest. */
|
|
24
30
|
export const platformKey = (platform = process.platform, arch = process.arch) => `${platform}-${arch}`;
|
|
25
31
|
/** -1, 0, 1: "0.1.0-dev.0" < "0.1.0" < "0.1.1" < "0.2.0". */
|
|
@@ -72,6 +78,8 @@ export function installedPlan(home) {
|
|
|
72
78
|
return undefined;
|
|
73
79
|
if (process.platform === 'darwin')
|
|
74
80
|
return { binary: join(installed.app, 'Contents', 'MacOS', 'Electron'), args: [], cwd: join(installed.app, '..') };
|
|
81
|
+
if (process.platform === 'win32')
|
|
82
|
+
return { binary: join(installed.app, 'dsh-her.exe'), args: [], cwd: installed.app };
|
|
75
83
|
return undefined;
|
|
76
84
|
}
|
|
77
85
|
/** `base` + file: the release's download address ends with a slash, or the file is appended after one. */
|
|
@@ -87,8 +95,8 @@ export async function fetchManifest(base) {
|
|
|
87
95
|
* the installed version and make it current. Progress in bytes.
|
|
88
96
|
*/
|
|
89
97
|
export async function install(home, base, manifest, onProgress) {
|
|
90
|
-
if (process.platform
|
|
91
|
-
throw new Error('
|
|
98
|
+
if (!PACKAGED.has(process.platform))
|
|
99
|
+
throw new Error('这个系统的身体程序还没有发布(现在有 macOS 和 Windows 版)');
|
|
92
100
|
const asset = manifest.assets[platformKey()];
|
|
93
101
|
if (asset === undefined)
|
|
94
102
|
throw new Error(`发布里没有这台电脑(${platformKey()})的身体程序`);
|
|
@@ -116,22 +124,31 @@ export async function install(home, base, manifest, onProgress) {
|
|
|
116
124
|
const unpacking = `${target}.unpacking`;
|
|
117
125
|
rmSync(unpacking, { recursive: true, force: true });
|
|
118
126
|
mkdirSync(unpacking, { recursive: true });
|
|
119
|
-
// ditto keeps the app's symlinks, permissions and signature.
|
|
120
|
-
|
|
121
|
-
|
|
127
|
+
// macOS: ditto keeps the app's symlinks, permissions and signature. Windows: the system tar reads zip.
|
|
128
|
+
if (process.platform === 'darwin')
|
|
129
|
+
await run('ditto', ['-x', '-k', zip, unpacking], { timeout: 300_000 });
|
|
130
|
+
else
|
|
131
|
+
await run(systemTar(), ['-xf', zip, '-C', unpacking], { timeout: 300_000 });
|
|
132
|
+
if (!existsSync(join(unpacking, appName())))
|
|
122
133
|
throw new Error('下载的包里没有身体程序');
|
|
123
134
|
rmSync(target, { recursive: true, force: true });
|
|
124
135
|
renameSync(unpacking, target);
|
|
125
136
|
rmSync(zip, { force: true });
|
|
126
137
|
const previous = installedBody(home);
|
|
127
|
-
const installed = { version: manifest.version, app: join(target,
|
|
138
|
+
const installed = { version: manifest.version, app: join(target, appName()), installedAt: new Date().toISOString() };
|
|
128
139
|
writeFileSync(join(root, 'current.json.tmp'), `${JSON.stringify(installed, null, 2)}\n`);
|
|
129
140
|
renameSync(join(root, 'current.json.tmp'), join(root, 'current.json'));
|
|
130
141
|
// Keep this version and the one before it; older ones go.
|
|
131
142
|
for (const entry of readdirSync(root)) {
|
|
132
143
|
const keep = entry === manifest.version || entry === previous?.version || entry === 'download' || entry.endsWith('.json');
|
|
133
|
-
if (
|
|
144
|
+
if (keep)
|
|
145
|
+
continue;
|
|
146
|
+
try {
|
|
134
147
|
rmSync(join(root, entry), { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Windows keeps a running program's files locked: that old version goes next time.
|
|
151
|
+
}
|
|
135
152
|
}
|
|
136
153
|
return installed;
|
|
137
154
|
}
|
package/lib/dsh/body-launcher.js
CHANGED
|
@@ -42,7 +42,7 @@ export function launchBody(plan, env) {
|
|
|
42
42
|
const childEnv = { ...process.env, DSH_HER_URL: env.url, DSH_HER_TOKEN: env.token, DSH_HER_HOME: env.home };
|
|
43
43
|
delete childEnv.ELECTRON_RUN_AS_NODE;
|
|
44
44
|
const args = env.pack === '' ? plan.args : [...plan.args, '--pack', env.pack];
|
|
45
|
-
const child = spawn(plan.binary, args, { cwd: plan.cwd, env: childEnv, detached: true, stdio: 'ignore' });
|
|
45
|
+
const child = spawn(plan.binary, args, { cwd: plan.cwd, env: childEnv, detached: true, stdio: 'ignore', windowsHide: true });
|
|
46
46
|
child.unref();
|
|
47
47
|
return child.pid;
|
|
48
48
|
}
|
package/lib/platform.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small differences between operating systems the plugin runs on.
|
|
3
|
+
*/
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* The system's own bsdtar (macOS, Windows 10+). On Windows it is named by its
|
|
7
|
+
* full path: a `tar` earlier on PATH (Git for Windows ships GNU tar) reads
|
|
8
|
+
* `C:\…` as a remote host and fails.
|
|
9
|
+
*/
|
|
10
|
+
export function systemTar(platform = process.platform) {
|
|
11
|
+
return platform === 'win32' ? join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'tar.exe') : 'tar';
|
|
12
|
+
}
|
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
* { "format": "dsh-her-body@1", "version": "0.1.0",
|
|
7
7
|
* "assets": { "darwin-arm64": { "file": "dsh-her-body-0.1.0-darwin-arm64.zip", "size": …, "sha256": "…" } } }
|
|
8
8
|
*
|
|
9
|
+
* The app is `dsh-her.app` on macOS and a `dsh-her` folder with
|
|
10
|
+
* `dsh-her.exe` on Windows (body/scripts/package.mjs makes both).
|
|
9
11
|
* `<home>/body/current.json` says which version is installed. A download
|
|
10
12
|
* that does not match its SHA-256 is thrown away; the previous version stays
|
|
11
13
|
* until the new one is in place, and one older version is kept to go back to.
|
|
12
14
|
*/
|
|
13
15
|
import type { LaunchPlan } from './body-launcher.ts';
|
|
14
16
|
export declare const MANIFEST = "body-latest.json";
|
|
17
|
+
/** The app inside a release zip (and in `<home>/body/<version>/`). */
|
|
18
|
+
export declare const appName: (platform?: string) => "dsh-her" | "dsh-her.app";
|
|
15
19
|
export interface BodyAsset {
|
|
16
20
|
file: string;
|
|
17
21
|
size: number;
|
|
@@ -24,7 +28,7 @@ export interface BodyManifest {
|
|
|
24
28
|
}
|
|
25
29
|
export interface Installed {
|
|
26
30
|
version: string;
|
|
27
|
-
/** The app (macOS: dsh-her.app) inside `<home>/body/<version>/`. */
|
|
31
|
+
/** The app (macOS: dsh-her.app; Windows: the dsh-her folder) inside `<home>/body/<version>/`. */
|
|
28
32
|
app: string;
|
|
29
33
|
installedAt: string;
|
|
30
34
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small differences between operating systems the plugin runs on.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The system's own bsdtar (macOS, Windows 10+). On Windows it is named by its
|
|
6
|
+
* full path: a `tar` earlier on PATH (Git for Windows ships GNU tar) reads
|
|
7
|
+
* `C:\…` as a remote host and fails.
|
|
8
|
+
*/
|
|
9
|
+
export declare function systemTar(platform?: string): string;
|
package/lib/voice/models.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* the cloud (cosyvoice.ts). Design: docs/语音方案.md.
|
|
5
5
|
*/
|
|
6
6
|
import { execFile } from 'node:child_process';
|
|
7
|
+
import { systemTar } from "../platform.js";
|
|
7
8
|
import { createWriteStream, existsSync, mkdirSync, renameSync, rmSync } from 'node:fs';
|
|
8
9
|
import { join } from 'node:path';
|
|
9
10
|
import { Readable } from 'node:stream';
|
|
@@ -59,7 +60,7 @@ export async function fetchModel(modelsDir, spec, onProgress) {
|
|
|
59
60
|
mkdirSync(staging, { recursive: true });
|
|
60
61
|
try {
|
|
61
62
|
// bsdtar ships with macOS and Windows 10+, and reads bzip2 itself.
|
|
62
|
-
await promisify(execFile)(
|
|
63
|
+
await promisify(execFile)(systemTar(), ['-xjf', partial, '-C', staging]);
|
|
63
64
|
renameSync(join(staging, spec.name), modelPath(modelsDir, spec));
|
|
64
65
|
}
|
|
65
66
|
finally {
|
package/lib/voice/uv.js
CHANGED
|
@@ -10,6 +10,7 @@ import { createHash } from 'node:crypto';
|
|
|
10
10
|
import { existsSync } from 'node:fs';
|
|
11
11
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
|
+
import { systemTar } from "../platform.js";
|
|
13
14
|
export function run(cmd, args, timeout, env) {
|
|
14
15
|
return new Promise((resolve, reject) => {
|
|
15
16
|
execFile(cmd, args, { timeout, maxBuffer: 8 << 20, ...(env === undefined ? {} : { env: { ...process.env, ...env } }) }, (error, stdout, stderr) => {
|
|
@@ -51,7 +52,7 @@ export async function installUv(uvHome) {
|
|
|
51
52
|
const archive = join(dir, asset.file);
|
|
52
53
|
await writeFile(archive, bytes);
|
|
53
54
|
// tar reads both the .tar.gz and (Windows 10+) the .zip.
|
|
54
|
-
await run(
|
|
55
|
+
await run(systemTar(), ['-xf', archive, '-C', dir], 120_000);
|
|
55
56
|
await rm(archive, { force: true });
|
|
56
57
|
const uv = [join(dir, 'uv'), join(dir, 'uv.exe'), join(dir, asset.file.replace(/\.(tar\.gz|zip)$/, ''), 'uv')].find(p => existsSync(p));
|
|
57
58
|
if (uv === undefined)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-her",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A desktop companion for DeepSeek Harness (dsh): she lives on your real desktop (on top of your windows or inside the wallpaper), talks with you by voice and remembers you. · DSH 桌面女友:住在你真正的桌面上,能语音聊天、记得你。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|