dsh-paoding 0.2.2 → 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 +26 -22
- package/README_en.md +26 -22
- package/bin/dsh-paoding.mjs +73 -11
- package/docs/architecture.md +36 -31
- package/docs/architecture_en.md +54 -38
- package/docs/configuration.md +151 -57
- package/docs/configuration_en.md +155 -60
- package/docs/installation.md +96 -234
- package/docs/installation_en.md +111 -252
- package/docs/orchestration.md +24 -24
- package/docs/orchestration_en.md +24 -24
- package/package.json +8 -5
- package/plugins/paoding-config-ui/api-core.mjs +215 -18
- package/plugins/paoding-config-ui/index.mjs +413 -154
- package/plugins/paoding-config-ui/lib/base.css +1083 -42
- package/plugins/paoding-config-ui/lib/client.js +2266 -252
- package/plugins/paoding-config-ui/package.json +2 -2
- package/plugins/paoding-config-ui/runtime-inventory.mjs +27 -26
- package/presets/orchestrator/agent.cordis.yml +19 -9
- package/presets/orchestrator/preset.yml +3 -2
- package/presets/orchestrator/restrict.mjs +2 -1
- package/tools/install.mjs +9 -4
- package/tools/lib/alloc.mjs +60 -29
- package/tools/lib/cli.mjs +127 -279
- package/tools/lib/compose.mjs +261 -95
- package/tools/lib/config.mjs +266 -86
- package/tools/lib/host.mjs +55 -24
- package/tools/lib/skills.mjs +72 -45
- package/tools/lib/state.mjs +172 -40
- package/tools/lib/upgrade.mjs +170 -0
- package/tools/lib/util.mjs +7 -0
- package/tools/lib/version.mjs +233 -0
- package/tools/lib/workspaces.mjs +74 -0
- package/tools/lib/yaml.mjs +92 -15
- package/install.sh +0 -31
- package/tools/lib/wizard.mjs +0 -516
package/tools/lib/cli.mjs
CHANGED
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI 层:参数解析与帮助(parseArgs / printHelp)、检测报告(printReport
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* CLI 层:参数解析与帮助(parseArgs / printHelp)、检测报告(printReport)与
|
|
3
|
+
* 主流程(main,供 tools/install.mjs 直接运行)。
|
|
4
|
+
*
|
|
5
|
+
* 官方安装通道是 DSH 插件通道(dsh plugin --profile web add dsh-paoding),
|
|
6
|
+
* npm 包不再携带 CLI 入口;本 CLI 仅作仓库内不宣传的开发兜底(--auto /
|
|
7
|
+
* --dry-run / --config / --help)。交互模式已整块移除:可视化配置走插件通道
|
|
8
|
+
* 自带的 Web 配置器(设置 → 庖丁配置)。
|
|
9
|
+
*
|
|
10
|
+
* 依赖 util(ROLES / SRC_DIR / PROFILE_NAME_RE)、config(normalizeRoleName)、
|
|
11
|
+
* host(removedReason)、state(collectState /
|
|
12
|
+
* generateAndInstall)、alloc(autoAssignments,--auto 语义与插件首装共享)、
|
|
13
|
+
* version(getVersionInfo,main 收尾的新版提示)以及 node:os / node:path。
|
|
9
14
|
*/
|
|
10
|
-
import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
11
15
|
import { homedir } from 'node:os'
|
|
12
16
|
import path from 'node:path'
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
17
|
+
import { PROFILE_NAME_RE, ROLES, SRC_DIR } from './util.mjs'
|
|
18
|
+
import { normalizeRoleName } from './config.mjs'
|
|
15
19
|
import { removedReason } from './host.mjs'
|
|
16
20
|
import { collectState, generateAndInstall } from './state.mjs'
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
21
|
+
import { autoAssignments } from './alloc.mjs'
|
|
22
|
+
import { getVersionInfo } from './version.mjs'
|
|
19
23
|
|
|
20
24
|
// ── CLI ─────────────────────────────────────────────────────────────────────
|
|
21
25
|
|
|
22
26
|
export function printHelp() {
|
|
23
|
-
console.log(`dsh-paoding installer —
|
|
27
|
+
console.log(`dsh-paoding installer — dev fallback installer for the orchestrator preset
|
|
28
|
+
(host-aware tool filtering)
|
|
29
|
+
|
|
30
|
+
官方安装通道是 DSH 插件通道(本 CLI 仅作仓库内开发兜底,不作宣传):
|
|
31
|
+
|
|
32
|
+
dsh plugin --profile web add dsh-paoding
|
|
33
|
+
|
|
34
|
+
可视化配置随插件通道自带:DSH Web 设置 → 庖丁配置。
|
|
24
35
|
|
|
25
36
|
Usage:
|
|
26
37
|
node tools/install.mjs [options]
|
|
27
|
-
./install.sh [options]
|
|
28
|
-
npx dsh-paoding [options]
|
|
29
|
-
npm exec dsh-paoding -- [options]
|
|
30
38
|
|
|
31
39
|
Options:
|
|
32
40
|
--profile <name> Profile whose patch layer is scanned (default: web).
|
|
@@ -37,42 +45,34 @@ Options:
|
|
|
37
45
|
--config <file> Configuration file with role/tool assignments
|
|
38
46
|
(default: $DSH_HOME/dsh-paoding.config.yml).
|
|
39
47
|
--auto Non-interactive: apply the config file when present,
|
|
40
|
-
otherwise apply the base template (
|
|
41
|
-
defaults on a fresh install).
|
|
42
|
-
not a TTY.
|
|
43
|
-
--wizard Force the interactive wizard even when stdin is not a TTY
|
|
44
|
-
(reads answers from stdin).
|
|
48
|
+
otherwise apply the base template (--suggest seeds smart
|
|
49
|
+
defaults on a fresh install instead).
|
|
45
50
|
--dry-run Print the detection report and the generated allow lists
|
|
46
51
|
without writing anything; exit 0.
|
|
47
52
|
--suggest Fresh install without a config file: seed smart defaults
|
|
48
53
|
from the detected host/MCP tools instead of the base
|
|
49
54
|
template (host tools are folded in automatically).
|
|
50
|
-
--no-ui npx entry only: skip mounting the config UI (the npx
|
|
51
|
-
entry mounts it by default; --config-ui wins when both
|
|
52
|
-
are given).
|
|
53
|
-
--config-ui Also mount the visual config UI into the DSH Settings
|
|
54
|
-
page: symlink plugins/paoding-config-ui into
|
|
55
|
-
$DSH_HOME/node_modules and ensure the cordis.patch.yml
|
|
56
|
-
row (id: paoding-config-ui) is enabled. Requires a DSH
|
|
57
|
-
restart to take effect.
|
|
58
55
|
--help Show this help and exit.
|
|
59
56
|
|
|
60
57
|
Environment:
|
|
61
58
|
DSH_HOME Directory holding cordis.patch.yml etc. (default: $HOME/.dsh)
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
With --auto and no config file, a base template is written: only the dsh
|
|
61
|
+
built-in tools stay enabled — add host/MCP tools afterwards in 设置 → 庖丁配置
|
|
62
|
+
(visual config UI from the plugin channel). Pass --suggest to fall back to
|
|
63
|
+
the smart defaults that fold detected host tools in automatically.`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --profile 值白名单:与 bin/dsh-paoding.mjs 共用同一份 PROFILE_NAME_RE(抽在
|
|
67
|
+
// util 防两处漂移)。profile 名会被拼进 dsh 子进程命令行,解析源头只收常规名称。
|
|
68
|
+
function assertProfileName(value) {
|
|
69
|
+
if (!PROFILE_NAME_RE.test(value)) {
|
|
70
|
+
throw new Error(`error: --profile 只收常规名称(字母数字开头,仅限 [A-Za-z0-9._-]): ${value}`)
|
|
71
|
+
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export function parseArgs(argv) {
|
|
75
|
-
const opts = { profile: null, patches: [], dryRun: false, help: false, auto: false,
|
|
75
|
+
const opts = { profile: null, patches: [], dryRun: false, help: false, auto: false, config: undefined, suggest: false }
|
|
76
76
|
for (let i = 0; i < argv.length; i++) {
|
|
77
77
|
const arg = argv[i]
|
|
78
78
|
if (arg === '--help' || arg === '-h') {
|
|
@@ -81,14 +81,6 @@ export function parseArgs(argv) {
|
|
|
81
81
|
opts.dryRun = true
|
|
82
82
|
} else if (arg === '--auto') {
|
|
83
83
|
opts.auto = true
|
|
84
|
-
} else if (arg === '--wizard') {
|
|
85
|
-
opts.wizard = true
|
|
86
|
-
} else if (arg === '--config-ui') {
|
|
87
|
-
opts.configUi = true
|
|
88
|
-
} else if (arg === '--no-ui') {
|
|
89
|
-
// 仅 bin(npx 入口)有意义:跳过 bin 默认注入的 --config-ui;与
|
|
90
|
-
// --config-ui 同传时后者优先(configUi 已为 true,注入逻辑自然跳过)。
|
|
91
|
-
opts.noUi = true
|
|
92
84
|
} else if (arg === '--suggest') {
|
|
93
85
|
// fresh 安装时用智能默认(自动纳入检测到的 host 工具)而非基础模板。
|
|
94
86
|
opts.suggest = true
|
|
@@ -99,15 +91,27 @@ export function parseArgs(argv) {
|
|
|
99
91
|
}
|
|
100
92
|
opts.config = value
|
|
101
93
|
} else if (arg.startsWith('--config=')) {
|
|
102
|
-
|
|
94
|
+
// 等号形式同样校验空串(--config= 与缺值同款报错)。
|
|
95
|
+
const value = arg.slice('--config='.length)
|
|
96
|
+
if (value === '') {
|
|
97
|
+
throw new Error('error: --config requires a file path')
|
|
98
|
+
}
|
|
99
|
+
opts.config = value
|
|
103
100
|
} else if (arg === '--profile') {
|
|
104
101
|
const value = argv[++i]
|
|
105
102
|
if (value === undefined || value.startsWith('--')) {
|
|
106
103
|
throw new Error('error: --profile requires a value')
|
|
107
104
|
}
|
|
105
|
+
assertProfileName(value)
|
|
108
106
|
opts.profile = value
|
|
109
107
|
} else if (arg.startsWith('--profile=')) {
|
|
110
|
-
|
|
108
|
+
// 等号形式:空串与缺值同款报错;值同样过 PROFILE_NAME_RE 白名单。
|
|
109
|
+
const value = arg.slice('--profile='.length)
|
|
110
|
+
if (value === '') {
|
|
111
|
+
throw new Error('error: --profile requires a value')
|
|
112
|
+
}
|
|
113
|
+
assertProfileName(value)
|
|
114
|
+
opts.profile = value
|
|
111
115
|
} else if (arg === '--patch') {
|
|
112
116
|
const value = argv[++i]
|
|
113
117
|
if (value === undefined || value.startsWith('--')) {
|
|
@@ -142,8 +146,13 @@ export function printReport({ scanned, mcpReports, pluginReports, roleResults, d
|
|
|
142
146
|
if (mcpReports.length > 0) {
|
|
143
147
|
console.log(' mcp servers:')
|
|
144
148
|
for (const r of mcpReports) {
|
|
149
|
+
// 来源标签:runtime(运行时注册表合并,见 state.collectState 的 runtimeFacts
|
|
150
|
+
// 段)排最前,与握手 / 静态表并列展示,不再误落「handshake failed」档。
|
|
145
151
|
const sourceLabel =
|
|
146
|
-
r.source === '
|
|
152
|
+
r.source === 'runtime' ? 'runtime registry'
|
|
153
|
+
: r.source === 'static' ? 'static table'
|
|
154
|
+
: r.source === 'handshake' ? 'live handshake'
|
|
155
|
+
: 'handshake failed'
|
|
147
156
|
const count = r.error ? `0 tools (${r.error})` : `${r.count} tool${r.count === 1 ? '' : 's'}`
|
|
148
157
|
console.log(` ${pad(r.serverName, 16)}${pad(r.transport ?? '-', 20)}${count} (${sourceLabel})`)
|
|
149
158
|
}
|
|
@@ -179,7 +188,7 @@ export function printReport({ scanned, mcpReports, pluginReports, roleResults, d
|
|
|
179
188
|
console.log(' removed: none — every host-dependent allow entry is enabled')
|
|
180
189
|
}
|
|
181
190
|
if (staleNote) {
|
|
182
|
-
console.log('\nnote: 检测到 patch 配置在本次安装后有过修改,改动后请重跑
|
|
191
|
+
console.log('\nnote: 检测到 patch 配置在本次安装后有过修改,改动后请重跑 node tools/install.mjs --auto')
|
|
183
192
|
}
|
|
184
193
|
console.log()
|
|
185
194
|
console.log(
|
|
@@ -201,160 +210,15 @@ export function printReport({ scanned, mcpReports, pluginReports, roleResults, d
|
|
|
201
210
|
console.log(`uninstall: rm -rf "${dstDir}"`)
|
|
202
211
|
}
|
|
203
212
|
|
|
204
|
-
// ── config UI mount (--config-ui) ──────────────────────────────────────────
|
|
205
|
-
//
|
|
206
|
-
// 借鉴 dsh-better-sidebar 的设置页整合机制(DSH 0.1.1-rc.1 实测通过):
|
|
207
|
-
// - 插件以包名安装进 $DSH_HOME/node_modules,patch 行 `name: <包名>` 即可被
|
|
208
|
-
// cordis Loader 解析;dsh-client-modules 节点半扫描该包 package.json 的
|
|
209
|
-
// `dsh.client` 声明 + exports["./client"],把 client bundle 编入
|
|
210
|
-
// __DSH_BOOT__ 并服务 /plugins/<包名>/client.js;
|
|
211
|
-
// - 浏览器内核为 manifest 里每个插件创建 loader 条目并激活(不依赖谁 require),
|
|
212
|
-
// bundle 以 window.__ModuleLoader__.load({id: <包名>, factory}) 注册;
|
|
213
|
-
// - 客户端 apply() 经 ctx.slots.inject("settings.section") + slots.register()
|
|
214
|
-
// 把分区挂进 DSH 设置壳(id/order/label 驱动导航),与 ui-settings-general
|
|
215
|
-
// 的 general 分区同款模式;
|
|
216
|
-
// - Node 半路由带信任围栏(/api/paoding 长于 /api 前缀会绕开网关围栏)。
|
|
217
|
-
|
|
218
|
-
/** 幂等挂载配置 UI:symlink 进 $DSH_HOME/node_modules + 启用 patch 行。 */
|
|
219
|
-
export function ensureConfigUiMount(dshHome, dryRun) {
|
|
220
|
-
// 仓库检出(含 .git)直接 symlink 仓库内插件目录;npm 包 / tarball / git 依赖
|
|
221
|
-
// 安装不含 .git,运行目录随时可能被 npx 缓存回收 —— 先把整包载荷复制到
|
|
222
|
-
// $DSH_HOME/dsh-paoding/(稳定位置)再挂载,缓存清掉后 symlink 也不悬空。
|
|
223
|
-
// 插件 api-core.mjs 以相对路径 import '../../tools/install.mjs',故必须整包
|
|
224
|
-
// 复制(tools/ + presets/ + plugins/ + package.json),不能只拷 plugins/。
|
|
225
|
-
const devCheckout = existsSync(path.join(REPO_ROOT, '.git'))
|
|
226
|
-
const payloadDir = path.join(dshHome, 'dsh-paoding')
|
|
227
|
-
// 自愈载荷内再运行(REPO_ROOT 已在 payload 里):源即目标,跳过复制,仅保证
|
|
228
|
-
// symlink 指向 payload —— 否则 rmSync 会先删掉正在运行的包。比较走 realpath:
|
|
229
|
-
// REPO_ROOT 可能带 /private 前缀(macOS /var 符号链接),字符串比对会失配。
|
|
230
|
-
let selfHosted = false
|
|
231
|
-
if (!devCheckout) {
|
|
232
|
-
try {
|
|
233
|
-
const realPayload = path.join(realpathSync(dshHome), 'dsh-paoding')
|
|
234
|
-
const realRoot = realpathSync(REPO_ROOT)
|
|
235
|
-
selfHosted = realRoot === realPayload || realRoot.startsWith(realPayload + path.sep)
|
|
236
|
-
} catch {
|
|
237
|
-
/* dshHome 尚不存在等:按非 selfHosted 处理 */
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const pluginDir = devCheckout || selfHosted
|
|
241
|
-
? path.join(REPO_ROOT, 'plugins', 'paoding-config-ui')
|
|
242
|
-
: path.join(payloadDir, 'plugins', 'paoding-config-ui')
|
|
243
|
-
const linkDir = path.join(dshHome, 'node_modules')
|
|
244
|
-
const linkTarget = path.join(linkDir, 'paoding-config-ui')
|
|
245
|
-
const patchFile = path.join(dshHome, 'cordis.patch.yml')
|
|
246
|
-
|
|
247
|
-
if (dryRun) {
|
|
248
|
-
if (!devCheckout && !selfHosted) {
|
|
249
|
-
console.log(`[config-ui] 将复制到 ${payloadDir} 再挂载(npx/npm 包形态:整包载荷进稳定位置,防缓存清理悬空)`)
|
|
250
|
-
}
|
|
251
|
-
console.log(`[config-ui] 将挂载: ${linkTarget} -> ${pluginDir}`)
|
|
252
|
-
console.log(`[config-ui] 将启用 patch 行: id: paoding-config-ui (${patchFile})`)
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!devCheckout && !selfHosted) {
|
|
257
|
-
// 整包复制(先删后建 = 幂等刷新载荷;bin 缺失跳过)。
|
|
258
|
-
console.log(`[config-ui] distributed copy: ${REPO_ROOT} → ${payloadDir}`)
|
|
259
|
-
rmSync(payloadDir, { recursive: true, force: true })
|
|
260
|
-
mkdirSync(payloadDir, { recursive: true })
|
|
261
|
-
for (const dir of ['tools', 'presets', 'bin', 'plugins']) {
|
|
262
|
-
const from = path.join(REPO_ROOT, dir)
|
|
263
|
-
if (existsSync(from)) cpSync(from, path.join(payloadDir, dir), { recursive: true })
|
|
264
|
-
}
|
|
265
|
-
copyFileSync(path.join(REPO_ROOT, 'package.json'), path.join(payloadDir, 'package.json'))
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// 1) symlink(已存在且指向正确则跳过)
|
|
269
|
-
if (!existsSync(pluginDir)) {
|
|
270
|
-
throw new Error(`config-ui 插件目录不存在: ${pluginDir}`)
|
|
271
|
-
}
|
|
272
|
-
let linkStat = null
|
|
273
|
-
try {
|
|
274
|
-
linkStat = lstatSync(linkTarget)
|
|
275
|
-
} catch {
|
|
276
|
-
/* 不存在:首次挂载 */
|
|
277
|
-
}
|
|
278
|
-
if (linkStat !== null && !linkStat.isSymbolicLink()) {
|
|
279
|
-
// 真实目录(用户自装/解包安装)不动,保持原有报错文案。
|
|
280
|
-
const real = realpathSync(linkTarget)
|
|
281
|
-
if (real !== realpathSync(pluginDir)) {
|
|
282
|
-
throw new Error(`config-ui 已安装为其它来源(${real});删除 ${linkTarget} 后重试`)
|
|
283
|
-
}
|
|
284
|
-
} else if (linkStat !== null) {
|
|
285
|
-
// 符号链接:指向正确 → 幂等跳过;指向别处或悬空(lstat 命中但 realpath
|
|
286
|
-
// 抛错,典型为旧 npx 缓存被清)→ 删除重建(迁移自愈)。
|
|
287
|
-
let real = null
|
|
288
|
-
try {
|
|
289
|
-
real = realpathSync(linkTarget)
|
|
290
|
-
} catch {
|
|
291
|
-
real = null
|
|
292
|
-
}
|
|
293
|
-
if (real !== realpathSync(pluginDir)) {
|
|
294
|
-
unlinkSync(linkTarget)
|
|
295
|
-
mkdirSync(linkDir, { recursive: true })
|
|
296
|
-
symlinkSync(pluginDir, linkTarget, 'dir')
|
|
297
|
-
warn(`config-ui 旧 symlink 指向 ${real ?? '<已失效>'},已重建 -> ${pluginDir}`)
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
mkdirSync(linkDir, { recursive: true })
|
|
301
|
-
symlinkSync(pluginDir, linkTarget, 'dir')
|
|
302
|
-
console.log(`[config-ui] symlink: ${linkTarget} -> ${pluginDir}`)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// 2) patch 行(幂等:已启用跳过;注释块则取消注释;缺失则追加)
|
|
306
|
-
if (!existsSync(patchFile)) {
|
|
307
|
-
writeFileSync(patchFile, '', 'utf8')
|
|
308
|
-
}
|
|
309
|
-
let patch = readFileSync(patchFile, 'utf8')
|
|
310
|
-
|
|
311
|
-
// 已启用的挂载行存在?
|
|
312
|
-
const enabledRow = /\n- insert:\n\s+- id: paoding-config-ui\s*\n\s+name: \S+\s*\n?$/.test('\n' + patch)
|
|
313
|
-
const hasAnyRow = /(^|\n)\s*#?\s*- insert:\s*\n\s*#?\s+- id: paoding-config-ui/.test(patch)
|
|
314
|
-
|
|
315
|
-
if (!hasAnyRow) {
|
|
316
|
-
const block =
|
|
317
|
-
'\n# ============================================================\n' +
|
|
318
|
-
'# paoding-config-ui — DSH Web 设置页内嵌可视化配置器\n' +
|
|
319
|
-
'# (install.sh --config-ui 自动挂载;停用:整段注释后重启 DSH)\n' +
|
|
320
|
-
'# ============================================================\n' +
|
|
321
|
-
'- insert:\n' +
|
|
322
|
-
' - id: paoding-config-ui\n' +
|
|
323
|
-
' name: paoding-config-ui\n'
|
|
324
|
-
patch += block
|
|
325
|
-
writeFileSync(patchFile, patch, 'utf8')
|
|
326
|
-
console.log(`[config-ui] patch 已追加: ${patchFile}`)
|
|
327
|
-
return
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (!enabledRow) {
|
|
331
|
-
// 取消注释三行挂载(保留其上的注释块)
|
|
332
|
-
const uncomment = (text) =>
|
|
333
|
-
text
|
|
334
|
-
.replace(/(^|\n)#- insert:/g, '$1- insert:')
|
|
335
|
-
.replace(/(^|\n)#\s+- id: paoding-config-ui/g, '$1 - id: paoding-config-ui')
|
|
336
|
-
.replace(/(^|\n)#\s+name: paoding-config-ui/g, '$1 name: paoding-config-ui')
|
|
337
|
-
const next = uncomment(patch)
|
|
338
|
-
if (next === patch) {
|
|
339
|
-
throw new Error('config-ui patch 行存在但无法自动启用,请手动编辑 ' + patchFile)
|
|
340
|
-
}
|
|
341
|
-
writeFileSync(patchFile, next, 'utf8')
|
|
342
|
-
console.log(`[config-ui] patch 行已启用: ${patchFile}`)
|
|
343
|
-
return
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
console.log('[config-ui] 已挂载(幂等跳过)')
|
|
347
|
-
}
|
|
348
|
-
|
|
349
213
|
// ── main ────────────────────────────────────────────────────────────────────
|
|
350
214
|
|
|
351
215
|
/**
|
|
352
|
-
* 主流程。argv 缺省取 process.argv.slice(2)(tools/install.mjs
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
216
|
+
* 主流程。argv 缺省取 process.argv.slice(2)(tools/install.mjs 直接运行)。
|
|
217
|
+
* 交互模式已随安装通道收敛整块移除:无 --auto(且非只读的 --dry-run)在参数
|
|
218
|
+
* 解析后、collectState 之前直接拒绝并指路(免做无谓的 MCP 握手 / patch 扫描)
|
|
219
|
+
* —— 非交互装走 --auto,可视化配置走插件通道的 Web 配置器。
|
|
356
220
|
*/
|
|
357
|
-
export async function main({ argv = process.argv.slice(2)
|
|
221
|
+
export async function main({ argv = process.argv.slice(2) } = {}) {
|
|
358
222
|
let opts
|
|
359
223
|
try {
|
|
360
224
|
opts = parseArgs(argv)
|
|
@@ -366,94 +230,66 @@ export async function main({ argv = process.argv.slice(2), bin = false } = {}) {
|
|
|
366
230
|
printHelp()
|
|
367
231
|
return 0
|
|
368
232
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
233
|
+
|
|
234
|
+
// 无 --auto 直接拒绝(交互模式已删):--dry-run 只读不动盘,放行作检测预览。
|
|
235
|
+
// 门禁放在 collectState 之前:被拒路径不做任何检测 / 握手,快速失败。
|
|
236
|
+
if (!opts.auto && !opts.dryRun) {
|
|
237
|
+
console.error(
|
|
238
|
+
'error: 交互安装已移除——请用 --auto 非交互安装;可视化配置请安装插件:' +
|
|
239
|
+
'dsh plugin --profile web add dsh-paoding',
|
|
240
|
+
)
|
|
241
|
+
return 1
|
|
372
242
|
}
|
|
373
243
|
|
|
374
244
|
const dshHome = process.env.DSH_HOME || path.join(homedir(), '.dsh')
|
|
375
245
|
const configFile = opts.config || path.join(dshHome, 'dsh-paoding.config.yml')
|
|
376
246
|
|
|
377
247
|
// Run the shared detection pipeline (patch scan, MCP handshake, plugins,
|
|
378
|
-
// skills, smart defaults). Failures are hard errors
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
248
|
+
// skills, smart defaults). Failures are hard errors: 与下方 generateAndInstall
|
|
249
|
+
// 同款 try/catch 收敛(error: ... 到 stderr、exit 1),不再裸堆栈。
|
|
250
|
+
let state
|
|
251
|
+
try {
|
|
252
|
+
state = await collectState({
|
|
253
|
+
dshHome,
|
|
254
|
+
configFile,
|
|
255
|
+
profile: opts.profile,
|
|
256
|
+
patches: opts.patches,
|
|
257
|
+
cwd: process.cwd(),
|
|
258
|
+
})
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error(`error: ${err.message}`)
|
|
261
|
+
return 1
|
|
262
|
+
}
|
|
386
263
|
const {
|
|
387
|
-
srcText,
|
|
388
|
-
blocks,
|
|
389
|
-
personaBlocks,
|
|
390
264
|
restrictBase,
|
|
391
265
|
existing,
|
|
392
|
-
effectiveProfile,
|
|
393
266
|
scanned,
|
|
394
267
|
mcpReports,
|
|
395
268
|
pluginReports,
|
|
396
|
-
inventory,
|
|
397
|
-
skills,
|
|
398
269
|
staticBase,
|
|
399
270
|
suggested,
|
|
400
271
|
dstDir,
|
|
401
|
-
dstAgentFile,
|
|
402
272
|
} = state
|
|
403
|
-
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
const
|
|
408
|
-
let assignments = null
|
|
409
|
-
let wizardRan = false
|
|
410
|
-
if (interactive) {
|
|
411
|
-
assignments = await runWizard({
|
|
412
|
-
inventory,
|
|
413
|
-
mcpReports,
|
|
414
|
-
pluginReports,
|
|
415
|
-
skills,
|
|
416
|
-
staticBase,
|
|
417
|
-
suggested,
|
|
418
|
-
existingConfig: existing,
|
|
419
|
-
personaBlocks,
|
|
420
|
-
srcText,
|
|
421
|
-
restrictBase,
|
|
422
|
-
})
|
|
423
|
-
if (assignments === null) return 0
|
|
424
|
-
wizardRan = true
|
|
425
|
-
} else if (opts.auto || existing || opts.dryRun) {
|
|
426
|
-
// 既有配置 → 按配置合成(不变);fresh(无配置)→ 默认基础模板(仅 dsh
|
|
427
|
-
// 基础工具,host/MCP 工具留给 Web 配置器 / 向导显式开启),--suggest 恢复
|
|
428
|
-
// 智能默认自动纳入。--dry-run fresh 同样按此计算但不落盘。
|
|
429
|
-
assignments = existing
|
|
430
|
-
? resolveAssignments(existing, suggested, staticBase)
|
|
431
|
-
: opts.suggest
|
|
432
|
-
? suggested
|
|
433
|
-
: baseAssignments(staticBase)
|
|
434
|
-
} else {
|
|
435
|
-
console.error(
|
|
436
|
-
'error: interactive wizard requires a terminal. Run with --auto for a non-interactive install, ' +
|
|
437
|
-
'or --config <file> to apply an existing config file. (--dry-run works without a TTY.)',
|
|
438
|
-
)
|
|
439
|
-
return 1
|
|
440
|
-
}
|
|
273
|
+
// --auto 语义与插件首装 ensurePresetInstalled 共用同一构造(alloc.autoAssignments,
|
|
274
|
+
// 防两处逻辑漂移):既有配置 → 按配置合成(不变);fresh(无配置)→ 默认基础
|
|
275
|
+
// 模板(仅 dsh 基础工具),--suggest 恢复智能默认自动纳入。--dry-run fresh
|
|
276
|
+
// 同样按此计算但不落盘。
|
|
277
|
+
const assignments = autoAssignments({ existing, suggested, staticBase, suggest: opts.suggest })
|
|
441
278
|
|
|
442
279
|
// Compose, validate and (unless dry-run) install via the shared generator.
|
|
443
|
-
// saveConfigOnWrite:
|
|
444
|
-
//
|
|
445
|
-
//
|
|
280
|
+
// saveConfigOnWrite:fresh 非交互安装把基础模板落进配置文件(显式
|
|
281
|
+
// main_agent_extra: [] 锚定「未开启 host 工具」);existing 路径绝不覆写
|
|
282
|
+
// 用户配置文件。
|
|
446
283
|
let composed
|
|
447
284
|
try {
|
|
448
285
|
composed = generateAndInstall(state, assignments, {
|
|
449
286
|
dryRun: opts.dryRun,
|
|
450
|
-
saveConfigOnWrite:
|
|
287
|
+
saveConfigOnWrite: !existing && !opts.dryRun,
|
|
451
288
|
})
|
|
452
289
|
} catch (err) {
|
|
453
290
|
console.error(`error: ${err.message}`)
|
|
454
291
|
return 1
|
|
455
292
|
}
|
|
456
|
-
const generatedText = composed.text
|
|
457
293
|
const roleResults = composed.roleResults
|
|
458
294
|
const staleNote = composed.staleNote
|
|
459
295
|
|
|
@@ -471,20 +307,28 @@ export async function main({ argv = process.argv.slice(2), bin = false } = {}) {
|
|
|
471
307
|
removed: assignments.main_agent_remove ?? [],
|
|
472
308
|
},
|
|
473
309
|
})
|
|
474
|
-
if (
|
|
475
|
-
console.log(`\n配置已写入 ${configFile}(改后重跑 ./install.sh --auto 应用)`)
|
|
476
|
-
} else if (!existing && !opts.dryRun) {
|
|
310
|
+
if (!existing && !opts.dryRun) {
|
|
477
311
|
// fresh 非交互安装:提示基础模板已落盘、host/MCP 工具去哪补(--suggest 可
|
|
478
312
|
// 恢复智能默认自动纳入)。
|
|
479
313
|
console.log(
|
|
480
314
|
`\n基础模板已写入 ${configFile}:默认仅 dsh 基础工具,重启后在 设置 → 庖丁配置` +
|
|
481
|
-
|
|
315
|
+
`里自定义 host/MCP 工具。`,
|
|
482
316
|
)
|
|
483
317
|
}
|
|
484
318
|
const customRoles = Object.keys(assignments.roles).filter((n) => !ROLES.includes(n))
|
|
485
319
|
if (customRoles.length > 0) {
|
|
486
320
|
console.log(`自定义角色: ${customRoles.join(', ')}(toolName 已注入主 agent restrict config.allow)`)
|
|
487
321
|
}
|
|
322
|
+
// 可续角色摘要(roles.<toolName>.background_mode = 'continuable',内置与自定义
|
|
323
|
+
// 角色一并列出;roles_remove 命中的角色其 background_mode 已被生成层忽略,不列)。
|
|
324
|
+
// 全部缺省(one-shot)时不打行,保持输出安静。
|
|
325
|
+
const removedRoleSet = new Set(assignments.roles_remove ?? [])
|
|
326
|
+
const continuableRoles = Object.entries(assignments.roles ?? {})
|
|
327
|
+
.filter(([toolName, role]) => role?.background_mode === 'continuable' && !removedRoleSet.has(toolName))
|
|
328
|
+
.map(([toolName]) => toolName)
|
|
329
|
+
if (continuableRoles.length > 0) {
|
|
330
|
+
console.log(`可续角色(continuable): ${continuableRoles.join(', ')}(委派失败先用 send_message 就地续修)`)
|
|
331
|
+
}
|
|
488
332
|
const skillAssignments = Object.entries(assignments.skills ?? {}).filter(([, roleList]) => roleList.length > 0)
|
|
489
333
|
if (skillAssignments.length > 0) {
|
|
490
334
|
console.log(`技能引导: ${skillAssignments.map(([s, r]) => `${s} → ${r.join(', ')}`).join(';')}`)
|
|
@@ -501,22 +345,26 @@ export async function main({ argv = process.argv.slice(2), bin = false } = {}) {
|
|
|
501
345
|
if (removedBuiltins.length > 0) {
|
|
502
346
|
console.log(`内置角色已删除: ${removedBuiltins.join(', ')}(委派行、主 persona 引用与 restrict allow 已一并移除)`)
|
|
503
347
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
348
|
+
// 主 agent 显示名(main_agent_display_name):仅 preset 显示名,persona 身份行不可改名。
|
|
349
|
+
const namedMainAgentDisplay = normalizeRoleName(assignments.main_agent_display_name ?? null, 'main_agent_display_name')
|
|
350
|
+
if (namedMainAgentDisplay !== null) {
|
|
351
|
+
console.log(`主 agent 显示名: ${namedMainAgentDisplay}(仅 preset 显示名,不动 persona 身份行)`)
|
|
507
352
|
}
|
|
508
353
|
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
354
|
+
// 版本提示(best-effort,收尾最后一行、在既有输出之后,不改变退出码与输出
|
|
355
|
+
// 顺序):对比 GitHub latest release,有新版才打印;超时 2 秒,检测失败
|
|
356
|
+
// (断网 / 限流 / 解析异常)完全静默。--help / 参数错误等早退路径不检测,
|
|
357
|
+
// 此处已是安装 / 应用完成之后。
|
|
358
|
+
try {
|
|
359
|
+
const info = await getVersionInfo({ timeoutMs: 2000 })
|
|
360
|
+
if (info.updateAvailable && info.latest) {
|
|
361
|
+
console.log(
|
|
362
|
+
`\n💡 发现新版本 ${info.latest}(当前 v${info.current}):` +
|
|
363
|
+
`${info.releaseUrl || 'https://github.com/lifangjin/dsh-paoding/releases/latest'}`,
|
|
364
|
+
)
|
|
519
365
|
}
|
|
366
|
+
} catch {
|
|
367
|
+
/* 静默:版本检测的任何意外都不影响安装结果 */
|
|
520
368
|
}
|
|
521
369
|
return 0
|
|
522
370
|
}
|