dsh-wechat-remote 1.7.1 → 1.7.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/bin/setup.mjs CHANGED
@@ -1,165 +1,161 @@
1
- #!/usr/bin/env node
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
- import os from 'node:os'
5
- import net from 'node:net'
6
- import { spawn } from 'node:child_process'
7
- import { randomBytes, createHash } from 'node:crypto'
8
- import { fileURLToPath } from 'node:url'
9
- import { attachControl, waitForJson, waitForInstallControl } from './native-control.mjs'
10
- import { resolveInstallRuntime, verifyInstallRuntime } from '../lib/install-runtime.js'
11
- import { validateCatalog, releaseMatches, compareVersions } from '../lib/update-policy.js'
12
- import { boundedFetch, downloadRelease, auditArchive } from '../lib/update-download.js'
13
- import { writePrivateJsonAtomic } from '../lib/secure-file.js'
14
- import { control, validateJob, releaseOwnedUpdateLock } from '../lib/update-worker.js'
15
-
16
- const root = fileURLToPath(new URL('../', import.meta.url))
17
- const packageName = '@harness-remote/dsh-wechat-remote'
18
- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
19
- export function findDsh() {
20
- const candidates = []
21
- for (const dir of (process.env.PATH || '').split(path.delimiter).filter(Boolean)) {
22
- candidates.push(path.join(dir, 'node_modules/@deepseek-ai/dsh/lib/bin.js'), path.join(dir, '../lib/node_modules/@deepseek-ai/dsh/lib/bin.js'))
23
- try { candidates.push(fs.realpathSync(path.join(dir, 'dsh'))) } catch {}
24
- }
25
- for (const candidate of candidates) {
26
- try {
27
- const cli = fs.realpathSync(candidate)
28
- const manifest = JSON.parse(fs.readFileSync(path.resolve(cli, '../../package.json'), 'utf8'))
29
- if (manifest.name === '@deepseek-ai/dsh' && path.basename(cli) === 'bin.js') return cli
30
- } catch {}
31
- }
32
- throw new Error('未找到已安装的 DSH。请先安装并启动 DeepSeek Harness。')
33
- }
34
- function portBusy(port) {
35
- return new Promise(resolve => {
36
- const socket = net.connect({ host: '127.0.0.1', port })
37
- const end = busy => { socket.destroy(); resolve(busy) }
38
- socket.setTimeout(500, () => end(false)); socket.once('connect', () => end(true)); socket.once('error', () => end(false))
39
- })
40
- }
41
- async function selectRelease(host, assetsRoot = path.join(root, 'assets'), repair = false) {
42
- const current = { agentKind: 'dsh', agentVersion: host.dshVersion, pluginVersion: host.pluginVersion,
43
- platform: { win32: 'windows', darwin: 'macos', linux: 'linux' }[host.platform], arch: host.arch }
44
- const pinned = JSON.parse(fs.readFileSync(path.join(assetsRoot, 'release.json'), 'utf8'))
45
- const catalog = validateCatalog(pinned.catalog)
46
- let releases = catalog.releases
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import os from 'node:os'
5
+ import net from 'node:net'
6
+ import { spawn } from 'node:child_process'
7
+ import { randomBytes, createHash } from 'node:crypto'
8
+ import { fileURLToPath } from 'node:url'
9
+ import { attachControl, waitForJson, waitForInstallControl } from './native-control.mjs'
10
+ import { resolveInstallRuntime, verifyInstallRuntime } from '../lib/install-runtime.js'
11
+ import { compareVersions } from '../lib/update-policy.js'
12
+ import { selectInstallTarget } from './release-selection.mjs'
13
+ import { boundedFetch, downloadRelease, auditArchive } from '../lib/update-download.js'
14
+ import { writePrivateJsonAtomic } from '../lib/secure-file.js'
15
+ import { control, validateJob, releaseOwnedUpdateLock } from '../lib/update-worker.js'
16
+
17
+ const root = fileURLToPath(new URL('../', import.meta.url))
18
+ const packageName = '@harness-remote/dsh-wechat-remote'
19
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
20
+ export function findDsh() {
21
+ const candidates = []
22
+ for (const dir of (process.env.PATH || '').split(path.delimiter).filter(Boolean)) {
23
+ candidates.push(path.join(dir, 'node_modules/@deepseek-ai/dsh/lib/bin.js'), path.join(dir, '../lib/node_modules/@deepseek-ai/dsh/lib/bin.js'))
24
+ try { candidates.push(fs.realpathSync(path.join(dir, 'dsh'))) } catch {}
25
+ }
26
+ for (const candidate of candidates) {
27
+ try {
28
+ const cli = fs.realpathSync(candidate)
29
+ const manifest = JSON.parse(fs.readFileSync(path.resolve(cli, '../../package.json'), 'utf8'))
30
+ if (manifest.name === '@deepseek-ai/dsh' && path.basename(cli) === 'bin.js') return cli
31
+ } catch {}
32
+ }
33
+ throw new Error('未找到已安装的 DSH。请先安装并启动 DeepSeek Harness。')
34
+ }
35
+ function portBusy(port) {
36
+ return new Promise(resolve => {
37
+ const socket = net.connect({ host: '127.0.0.1', port })
38
+ const end = busy => { socket.destroy(); resolve(busy) }
39
+ socket.setTimeout(500, () => end(false)); socket.once('connect', () => end(true)); socket.once('error', () => end(false))
40
+ })
41
+ }
42
+ export async function selectRelease(host, assetsRoot = path.join(root, 'assets'), repair = false,
43
+ fetchCatalog = () => boundedFetch('https://relay.xyxfood.xyz/v1/update-policy', 256 * 1024)) {
44
+ const current = { agentKind: 'dsh', agentVersion: host.dshVersion, pluginVersion: host.pluginVersion,
45
+ platform: { win32: 'windows', darwin: 'macos', linux: 'linux' }[host.platform], arch: host.arch }
46
+ const pinned = JSON.parse(fs.readFileSync(path.join(assetsRoot, 'release.json'), 'utf8'))
47
+ let remote
47
48
  try {
48
- const remote = validateCatalog(JSON.parse((await boundedFetch('https://relay.xyxfood.xyz/v1/update-policy', 256 * 1024)).toString('utf8')))
49
- if (remote.issuedAt <= Date.now() + 300000 && remote.expiresAt > Date.now()) {
50
- releases = [...releases, ...remote.releases].filter(r => !remote.blocked.some(b => b.pluginVersion === r.version && (!b.dsh || b.dsh.includes(current.agentVersion)) && (!b.platforms || b.platforms.includes(current.platform))))
51
- }
52
- } catch { /* Authenticated npm package retains its explicitly tested release. */ }
53
- const release = releases.filter(r => r.channel === 'stable' && releaseMatches(r, current))
54
- .sort((a, b) => compareVersions(b.version, a.version))[0]
55
- if (!release) throw new Error('当前 DSH 版本或系统尚无已验证的插件版本;没有修改现有安装。')
56
- if (host.pluginVersion !== '0.0.0' && (compareVersions(host.pluginVersion, release.version) > 0
57
- || (compareVersions(host.pluginVersion, release.version) === 0 && !repair))) return { release, archive: null }
58
- let archive
59
- if (release.version === pinned.version) archive = fs.readFileSync(path.join(assetsRoot, 'plugin.tgz'))
60
- else archive = await downloadRelease(release)
61
- auditArchive(archive, release)
62
- return { release, archive }
63
- }
64
- export async function install({ profileName = 'web', cli = findDsh(), assetsRoot, open = true, repair = false } = {}) {
65
- if (!/^[A-Za-z0-9_-]{1,80}$/.test(profileName)) throw new Error('无效的 profile 名称。')
66
- const runtime = resolveInstallRuntime(root); await verifyInstallRuntime(runtime)
67
- const home = path.resolve(process.env.DSH_HOME || path.join(os.homedir(), '.dsh'))
68
- fs.mkdirSync(home, { recursive: true, mode: 0o700 })
69
- if (fs.realpathSync(home) !== home) throw new Error('DSH 数据目录是链接,暂不自动替换安装。')
70
- const profile = path.join(home, 'profiles', profileName), id = randomBytes(16).toString('hex')
71
- const directory = path.join(home, 'harness-remote-updates', id), token = randomBytes(24).toString('hex')
72
- fs.mkdirSync(directory, { recursive: true, mode: 0o700 })
73
- let remove, ref, job, locked = false, authorized = false, launched
74
- const lockFile = path.join(profile, '.harness-remote-update.lock')
75
- console.log('正在检查 DSH 与插件…')
76
- try {
77
- // Initialize missing profiles through DSH itself, never synthesize a bundle list.
78
- if (!fs.existsSync(path.join(profile, 'package.json'))) {
79
- const log = fs.openSync(path.join(directory, 'startup.log'), 'a', 0o600)
80
- launched = spawn(process.execPath, [cli, 'web', '--profile', profileName, '--no-open'], {
81
- cwd: process.cwd(), env: process.env, detached: true, windowsHide: true, stdio: ['ignore', log, log] })
82
- fs.closeSync(log); launched.on('error', () => {}); launched.unref()
83
- for (let i = 0; i < 100 && !fs.existsSync(path.join(profile, 'package.json')); i++) await sleep(100)
84
- if (!fs.existsSync(path.join(profile, 'package.json'))) throw new Error('DSH 未能初始化 profile;详情见本机安装记录。')
85
- await sleep(2000)
86
- }
87
- const fd = fs.openSync(lockFile, 'wx', 0o600); fs.writeFileSync(fd, id); fs.closeSync(fd); locked = true
88
- const helper = path.join(directory, 'install-control.js')
89
- fs.copyFileSync(path.join(root, 'lib/install-control.js'), helper)
90
- writePrivateJsonAtomic(path.join(directory, 'package.json'), { type: 'module' })
91
- remove = attachControl(profile, helper, { directory, token, pnpm: runtime.cli })
92
- const handshakeStarted = Date.now()
93
- try {
94
- ref = await waitForInstallControl(path.join(directory, 'control-ready.json'), {
95
- accept: v => Number.isInteger(v.pid) && v.pid > 0 && (!launched || v.pid === launched.pid),
96
- onWaiting: () => console.log('正在等待 DSH 完成安装准备,请勿重复执行命令…'),
97
- ensureRunning: async () => {
98
- if (launched || await portBusy(Number(process.env.DSH_PORT || 3080))) return
99
- const log = fs.openSync(path.join(directory, 'startup.log'), 'a', 0o600)
100
- launched = spawn(process.execPath, [cli, 'web', '--profile', profileName, '--no-open'], {
101
- cwd: process.cwd(), env: process.env, detached: true, windowsHide: true, stdio: ['ignore', log, log] })
102
- fs.closeSync(log); launched.on('error', () => {}); launched.unref()
103
- },
104
- })
105
- writePrivateJsonAtomic(path.join(directory, 'handshake.json'), {
106
- state: 'ready', elapsedMs: Date.now() - handshakeStarted, startedHost: Boolean(launched),
107
- })
108
- } catch {
109
- writePrivateJsonAtomic(path.join(directory, 'handshake.json'), {
110
- state: 'timeout', elapsedMs: Date.now() - handshakeStarted, startedHost: Boolean(launched),
111
- })
112
- throw new Error('DSH 安装准备超时,原插件未替换。本机安装记录:' + path.join(directory, 'handshake.json'))
113
- }
114
- job = { controlOrigin: ref.origin, statusToken: token }
115
- const host = await control(job, 'describe')
116
- if (host.home !== home || host.profile !== profile || host.cli !== fs.realpathSync(cli) || (launched && host.pid !== launched.pid)) throw new Error('当前 DSH 身份与安装目标不一致,未修改安装。')
117
- remove(); remove = undefined
118
- await sleep(600)
119
- const selected = await selectRelease(host, assetsRoot, repair)
120
- if (!selected.archive) { console.log(`插件 ${host.pluginVersion} 无需更新。`); return { version: host.pluginVersion, changed: false } }
121
- console.log(`正在安装插件 ${selected.release.version},保留原配对与会话…`)
122
- job = { ...host, id, directory, parentPid: host.pid, pnpm: runtime.cli, targetVersion: selected.release.version,
123
- previousVersion: host.pluginVersion, statusToken: token, controlOrigin: ref.origin }
124
- validateJob(job)
125
- fs.writeFileSync(path.join(directory, 'release.tgz'), selected.archive, { mode: 0o600, flag: 'wx' })
126
- fs.copyFileSync(path.join(root, 'lib/update-worker.js'), path.join(directory, 'update-worker.js'))
127
- writePrivateJsonAtomic(path.join(directory, 'job.json'), job)
128
- await control(job, 'launch')
129
- const ready = await waitForJson(path.join(directory, 'worker-ready.json'), v => v.id === id)
130
- const scope = createHash('sha256').update(profileName).digest('hex').slice(0, 24)
131
- writePrivateJsonAtomic(path.join(home, 'harness-remote-updates', `profile-${scope}.json`), { jobId: id, statusOrigin: ready.origin, statusToken: token })
132
- writePrivateJsonAtomic(path.join(directory, 'authorized.json'), { id }); authorized = true
133
- let previous = '', result
134
- const until = Date.now() + 600000
135
- while (Date.now() < until) {
136
- try { result = JSON.parse(fs.readFileSync(path.join(directory, 'result.json'), 'utf8')) } catch {}
137
- if (result?.phase !== previous && result?.phase) { console.log(`${result.progress}% ${result.message}`); previous = result.phase }
138
- if (result?.terminal) break
139
- await sleep(500)
140
- }
141
- if (!result?.terminal || !result.ok) throw new Error(result?.message || '安装结果待确认,请保留本机安装记录,不要重复安装。')
142
- if (open) {
143
- try {
144
- const origin = `http://127.0.0.1:${host.webPort}`
145
- const response = await fetch(`http://127.0.0.1:${host.localPort}/gate/update/resume?job=${id}`, { headers: { origin }, signal: AbortSignal.timeout(10000) })
146
- const value = await response.json(), url = new URL(value.url)
147
- if (!response.ok || url.origin !== origin || url.pathname !== '/' || url.hash || url.username || url.password) throw new Error('invalid resume')
148
- const command = process.platform === 'win32' ? ['rundll32.exe', ['url.dll,FileProtocolHandler', url.href]] : process.platform === 'darwin' ? ['/usr/bin/open', [url.href]] : ['xdg-open', [url.href]]
149
- const browser = spawn(command[0], command[1], { detached: true, windowsHide: true, stdio: 'ignore' }); browser.on('error', () => {}); browser.unref()
150
- } catch { console.log('插件已更新;请返回原 DSH WebUI 查看。') }
151
- }
152
- return { version: selected.release.version, changed: true }
153
- } finally {
154
- remove?.()
155
- if (ref && !authorized) { try { await control({ controlOrigin: ref.origin, statusToken: token }, 'close') } catch {} }
156
- if (locked && !authorized) releaseOwnedUpdateLock(lockFile, id)
157
- }
158
- }
159
- if (process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
160
- const args = process.argv.slice(2), repair = args.includes('--repair')
161
- if (repair) args.splice(args.indexOf('--repair'), 1)
162
- if (args.includes('--help') || args.includes('-h')) console.log('安装或升级 DSH 微信连接插件:npx -y dsh-wechat-remote@latest\n可选:--profile <名称>(默认 web);--repair(重新安装当前兼容版本,不降级)')
163
- else if (args.length && !(args.length === 2 && args[0] === '--profile')) { console.error('不支持的参数。使用 --help 查看用法。'); process.exitCode = 1 }
164
- else install({ profileName: args[1] || 'web', repair }).catch(error => { console.error(error.message); process.exitCode = 1 })
165
- }
49
+ remote = JSON.parse((await fetchCatalog()).toString('utf8'))
50
+ } catch { /* Authenticated npm package retains its bundled release. */ }
51
+ const release = selectInstallTarget(pinned, remote, current)
52
+ if (host.pluginVersion !== '0.0.0' && (compareVersions(host.pluginVersion, release.version) > 0
53
+ || (compareVersions(host.pluginVersion, release.version) === 0 && !repair))) return { release, archive: null }
54
+ let archive
55
+ if (release.version === pinned.version) archive = fs.readFileSync(path.join(assetsRoot, 'plugin.tgz'))
56
+ else archive = await downloadRelease(release)
57
+ auditArchive(archive, release)
58
+ return { release, archive }
59
+ }
60
+ export async function install({ profileName = 'web', cli = findDsh(), assetsRoot, open = true, repair = false } = {}) {
61
+ if (!/^[A-Za-z0-9_-]{1,80}$/.test(profileName)) throw new Error('无效的 profile 名称。')
62
+ const runtime = resolveInstallRuntime(root); await verifyInstallRuntime(runtime)
63
+ const home = path.resolve(process.env.DSH_HOME || path.join(os.homedir(), '.dsh'))
64
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 })
65
+ if (fs.realpathSync(home) !== home) throw new Error('DSH 数据目录是链接,暂不自动替换安装。')
66
+ const profile = path.join(home, 'profiles', profileName), id = randomBytes(16).toString('hex')
67
+ const directory = path.join(home, 'harness-remote-updates', id), token = randomBytes(24).toString('hex')
68
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 })
69
+ let remove, ref, job, locked = false, authorized = false, launched
70
+ const lockFile = path.join(profile, '.harness-remote-update.lock')
71
+ console.log('正在检查 DSH 与插件…')
72
+ try {
73
+ // Initialize missing profiles through DSH itself, never synthesize a bundle list.
74
+ if (!fs.existsSync(path.join(profile, 'package.json'))) {
75
+ const log = fs.openSync(path.join(directory, 'startup.log'), 'a', 0o600)
76
+ launched = spawn(process.execPath, [cli, '--profile', profileName, '--no-open'], {
77
+ cwd: process.cwd(), env: process.env, detached: true, windowsHide: true, stdio: ['ignore', log, log] })
78
+ fs.closeSync(log); launched.on('error', () => {}); launched.unref()
79
+ for (let i = 0; i < 100 && !fs.existsSync(path.join(profile, 'package.json')); i++) await sleep(100)
80
+ if (!fs.existsSync(path.join(profile, 'package.json'))) throw new Error('DSH 未能初始化 profile;详情见本机安装记录。')
81
+ await sleep(2000)
82
+ }
83
+ const fd = fs.openSync(lockFile, 'wx', 0o600); fs.writeFileSync(fd, id); fs.closeSync(fd); locked = true
84
+ const helper = path.join(directory, 'install-control.js')
85
+ fs.copyFileSync(path.join(root, 'lib/install-control.js'), helper)
86
+ writePrivateJsonAtomic(path.join(directory, 'package.json'), { type: 'module' })
87
+ remove = attachControl(profile, helper, { directory, token, pnpm: runtime.cli })
88
+ const handshakeStarted = Date.now()
89
+ try {
90
+ ref = await waitForInstallControl(path.join(directory, 'control-ready.json'), {
91
+ accept: v => Number.isInteger(v.pid) && v.pid > 0 && (!launched || v.pid === launched.pid),
92
+ onWaiting: () => console.log('正在等待 DSH 完成安装准备,请勿重复执行命令…'),
93
+ ensureRunning: async () => {
94
+ if (launched || await portBusy(Number(process.env.DSH_PORT || 3080))) return
95
+ const log = fs.openSync(path.join(directory, 'startup.log'), 'a', 0o600)
96
+ launched = spawn(process.execPath, [cli, '--profile', profileName, '--no-open'], {
97
+ cwd: process.cwd(), env: process.env, detached: true, windowsHide: true, stdio: ['ignore', log, log] })
98
+ fs.closeSync(log); launched.on('error', () => {}); launched.unref()
99
+ },
100
+ })
101
+ writePrivateJsonAtomic(path.join(directory, 'handshake.json'), {
102
+ state: 'ready', elapsedMs: Date.now() - handshakeStarted, startedHost: Boolean(launched),
103
+ })
104
+ } catch {
105
+ writePrivateJsonAtomic(path.join(directory, 'handshake.json'), {
106
+ state: 'timeout', elapsedMs: Date.now() - handshakeStarted, startedHost: Boolean(launched),
107
+ })
108
+ throw new Error('DSH 安装准备超时,原插件未替换。本机安装记录:' + path.join(directory, 'handshake.json'))
109
+ }
110
+ job = { controlOrigin: ref.origin, statusToken: token }
111
+ const host = await control(job, 'describe')
112
+ if (host.home !== home || host.profile !== profile || host.cli !== fs.realpathSync(cli) || (launched && host.pid !== launched.pid)) throw new Error('当前 DSH 身份与安装目标不一致,未修改安装。')
113
+ remove(); remove = undefined
114
+ await sleep(600)
115
+ const selected = await selectRelease(host, assetsRoot, repair)
116
+ if (!selected.archive) { console.log(`插件 ${host.pluginVersion} 无需更新。`); return { version: host.pluginVersion, changed: false } }
117
+ console.log(`正在安装插件 ${selected.release.version},保留原配对与会话…`)
118
+ job = { ...host, id, directory, parentPid: host.pid, pnpm: runtime.cli, targetVersion: selected.release.version,
119
+ previousVersion: host.pluginVersion, statusToken: token, controlOrigin: ref.origin }
120
+ validateJob(job)
121
+ fs.writeFileSync(path.join(directory, 'release.tgz'), selected.archive, { mode: 0o600, flag: 'wx' })
122
+ fs.copyFileSync(path.join(root, 'lib/update-worker.js'), path.join(directory, 'update-worker.js'))
123
+ writePrivateJsonAtomic(path.join(directory, 'job.json'), job)
124
+ await control(job, 'launch')
125
+ const ready = await waitForJson(path.join(directory, 'worker-ready.json'), v => v.id === id)
126
+ const scope = createHash('sha256').update(profileName).digest('hex').slice(0, 24)
127
+ writePrivateJsonAtomic(path.join(home, 'harness-remote-updates', `profile-${scope}.json`), { jobId: id, statusOrigin: ready.origin, statusToken: token })
128
+ writePrivateJsonAtomic(path.join(directory, 'authorized.json'), { id }); authorized = true
129
+ let previous = '', result
130
+ const until = Date.now() + 600000
131
+ while (Date.now() < until) {
132
+ try { result = JSON.parse(fs.readFileSync(path.join(directory, 'result.json'), 'utf8')) } catch {}
133
+ if (result?.phase !== previous && result?.phase) { console.log(`${result.progress}% ${result.message}`); previous = result.phase }
134
+ if (result?.terminal) break
135
+ await sleep(500)
136
+ }
137
+ if (!result?.terminal || !result.ok) throw new Error(result?.message || '安装结果待确认,请保留本机安装记录,不要重复安装。')
138
+ if (open) {
139
+ try {
140
+ const origin = `http://127.0.0.1:${host.webPort}`
141
+ const response = await fetch(`http://127.0.0.1:${host.localPort}/gate/update/resume?job=${id}`, { headers: { origin }, signal: AbortSignal.timeout(10000) })
142
+ const value = await response.json(), url = new URL(value.url)
143
+ if (!response.ok || url.origin !== origin || url.pathname !== '/' || url.hash || url.username || url.password) throw new Error('invalid resume')
144
+ const command = process.platform === 'win32' ? ['rundll32.exe', ['url.dll,FileProtocolHandler', url.href]] : process.platform === 'darwin' ? ['/usr/bin/open', [url.href]] : ['xdg-open', [url.href]]
145
+ const browser = spawn(command[0], command[1], { detached: true, windowsHide: true, stdio: 'ignore' }); browser.on('error', () => {}); browser.unref()
146
+ } catch { console.log('插件已更新;请返回原 DSH WebUI 查看。') }
147
+ }
148
+ return { version: selected.release.version, changed: true }
149
+ } finally {
150
+ remove?.()
151
+ if (ref && !authorized) { try { await control({ controlOrigin: ref.origin, statusToken: token }, 'close') } catch {} }
152
+ if (locked && !authorized) releaseOwnedUpdateLock(lockFile, id)
153
+ }
154
+ }
155
+ if (process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
156
+ const args = process.argv.slice(2), repair = args.includes('--repair')
157
+ if (repair) args.splice(args.indexOf('--repair'), 1)
158
+ if (args.includes('--help') || args.includes('-h')) console.log('安装或升级 DSH 微信连接插件:npx -y dsh-wechat-remote@latest\n可选:--profile <名称>(默认 web);--repair(重新安装当前兼容版本,不降级)')
159
+ else if (args.length && !(args.length === 2 && args[0] === '--profile')) { console.error('不支持的参数。使用 --help 查看用法。'); process.exitCode = 1 }
160
+ else install({ profileName: args[1] || 'web', repair }).catch(error => { console.error(error.message); process.exitCode = 1 })
161
+ }