@flotiarenor/dsh-tool-text-editor 1.1.2 → 1.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 +191 -117
- package/README.zh.md +67 -137
- package/lib/core.mjs +341 -375
- package/lib/editor.mjs +131 -154
- package/lib/mask.mjs +440 -0
- package/package.json +19 -13
- package/preset/preset.yml +1 -1
- package/scripts/install-preset.mjs +127 -74
|
@@ -2,22 +2,15 @@
|
|
|
2
2
|
// SPDX-FileCopyrightText: 2026 Flotiarenor
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
/**
|
|
5
|
-
* install-preset.mjs —— 把 `edit_text` / `write_text`
|
|
5
|
+
* install-preset.mjs —— 把 `edit_text` / `write_text` 装成一个用户 preset:读本机 dsh 自带的 preset 组合
|
|
6
|
+
* (默认 `standard`),插入 `tool-text-editor` 行,连同 `preset/preset.yml` 写到
|
|
7
|
+
* `<DSH_HOME>/.agent-presets/<id>/`。
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* 组合必须从用户自己的 dsh 派生,不在仓库里放拷贝:自带组合是别人(MIT, Copyright (c) 2026 DeepSeek)的
|
|
10
|
+
* 作品,随包分发要连带履行其署名义务。
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 它的署名义务,而这份拷贝与本插件的功能无关;
|
|
13
|
-
* * 从用户自己的 dsh 里取,preset 自然跟着他装的 dsh 版本走 —— 不会像拷贝那样随 dsh 升级而过期。
|
|
14
|
-
*
|
|
15
|
-
* 用法:
|
|
16
|
-
* node scripts/install-preset.mjs # 默认 --id texteditor --base standard
|
|
17
|
-
* node scripts/install-preset.mjs --id my-edit --base code
|
|
18
|
-
* node scripts/install-preset.mjs --from <path-to-agent.cordis.yml> # 自己指定源组合
|
|
19
|
-
* node scripts/install-preset.mjs --force # 覆盖已存在的 preset(只覆盖两个文件)
|
|
20
|
-
* node scripts/install-preset.mjs --dry-run # 只打印会做什么,不落盘
|
|
12
|
+
* 用法:`node scripts/install-preset.mjs [--id <id>] [--base <id>] [--from <组合路径>] [--force] [--dry-run]
|
|
13
|
+
* [--mask-native]`;`--id` 默认 `texteditor`,`--base` 默认 `standard`。
|
|
21
14
|
*
|
|
22
15
|
* 退出码:0 成功,1 失败,2 用法错误 / 找不到 dsh 自带的 preset 组合。
|
|
23
16
|
*/
|
|
@@ -30,8 +23,17 @@ import { fileURLToPath } from 'node:url'
|
|
|
30
23
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
31
24
|
const REPO = resolve(HERE, '..')
|
|
32
25
|
const PLUGIN = join(REPO, 'lib', 'editor.mjs').replace(/\\/g, '/')
|
|
26
|
+
const MASK = join(REPO, 'lib', 'mask.mjs').replace(/\\/g, '/')
|
|
33
27
|
const META = join(REPO, 'preset', 'preset.yml')
|
|
34
|
-
|
|
28
|
+
/**
|
|
29
|
+
* 自带 preset 组合的两种布局(按版本从上到下试):`@deepseek-ai/dsh/config/agent-presets/<base>/`
|
|
30
|
+
* (≤ 0.1.0-rc.6)与 `@deepseek-ai/dsh-agent-presets/presets/<base>/`(0.1.5-rc.2 起自带组合搬进另一个
|
|
31
|
+
* 包,该包按 `new URL('../presets/', import.meta.url)` 定位,旧路径在新版里已不存在)。
|
|
32
|
+
*/
|
|
33
|
+
const SHIPPED_LAYOUTS = [
|
|
34
|
+
['dsh', 'config', 'agent-presets'],
|
|
35
|
+
['dsh-agent-presets', 'presets'],
|
|
36
|
+
]
|
|
35
37
|
const COMPOSITION = 'agent.cordis.yml'
|
|
36
38
|
|
|
37
39
|
function flagValue(name) {
|
|
@@ -39,30 +41,29 @@ function flagValue(name) {
|
|
|
39
41
|
if (index < 0) return undefined
|
|
40
42
|
const value = process.argv[index + 1]
|
|
41
43
|
if (value === undefined || value.startsWith('--')) {
|
|
42
|
-
console.error('FAIL ' + name + '
|
|
44
|
+
console.error('FAIL ' + name + ' needs a value')
|
|
43
45
|
process.exit(2)
|
|
44
46
|
}
|
|
45
47
|
return value
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
|
-
* dsh
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* `/usr/lib`、`~/.npm-global/lib`)。
|
|
54
|
-
* @param base - 自带 preset 的 id(standard / code / cordis / minimal)。
|
|
55
|
-
* @returns 候选绝对路径(按优先级)。
|
|
51
|
+
* 枚举自带 preset 组合的候选路径(dsh 可能装在任何位置,故不写死本机路径):`--from` /
|
|
52
|
+
* `DSH_PRESET_SOURCE` → profile 的 node_modules(`<DSH_HOME>` 与默认 `~/.dsh` 都试:`DSH_HOME` 可能被指到
|
|
53
|
+
* 别处如临时目录,而 dsh 本体仍在默认 home 下)→ npm 全局前缀。
|
|
54
|
+
* @param explicitPath - 显式指定的源组合(已确认存在)。
|
|
56
55
|
*/
|
|
57
|
-
function findCompositions(base) {
|
|
58
|
-
const candidates = []
|
|
59
|
-
const explicit = flagValue('--from') ?? process.env.DSH_PRESET_SOURCE
|
|
60
|
-
if (typeof explicit === 'string' && explicit !== '') candidates.push(resolve(explicit))
|
|
56
|
+
function findCompositions(base, explicitPath, dshHome) {
|
|
57
|
+
const candidates = explicitPath === undefined ? [] : [explicitPath]
|
|
61
58
|
const add = (nodeModules) => {
|
|
62
|
-
if (
|
|
63
|
-
|
|
59
|
+
if (nodeModules === '') return
|
|
60
|
+
for (const layout of SHIPPED_LAYOUTS) {
|
|
61
|
+
candidates.push(join(nodeModules, '@deepseek-ai', ...layout, base, COMPOSITION))
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const home of new Set([dshHome, join(homedir(), '.dsh')])) {
|
|
65
|
+
add(join(home, 'profiles', 'node_modules'))
|
|
64
66
|
}
|
|
65
|
-
add(join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'profiles', 'node_modules'))
|
|
66
67
|
const globalRoots = process.platform === 'win32'
|
|
67
68
|
? [process.env.APPDATA === undefined ? '' : join(process.env.APPDATA, 'npm', 'node_modules')]
|
|
68
69
|
: ['/usr/local/lib/node_modules', '/usr/lib/node_modules', join(homedir(), '.npm-global', 'lib', 'node_modules')]
|
|
@@ -70,40 +71,64 @@ function findCompositions(base) {
|
|
|
70
71
|
return candidates
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
/**
|
|
75
|
+
* 插进组合里的那一段(只有这一段是本仓库自己的文字与行)。
|
|
76
|
+
* @param sourcePath - 源组合路径(写进注释,便于升级后重跑)。
|
|
77
|
+
* @param maskNative - 是否插入屏蔽原生 write/edit 的门禁行,并让编辑行改用短引导。
|
|
78
|
+
* @returns 插件段文本(以单个换行结尾)。
|
|
79
|
+
*/
|
|
80
|
+
function pluginBlock(sourcePath, maskNative) {
|
|
81
|
+
const lines = [
|
|
76
82
|
'# ── 字节保真的文本编辑工具(edit_text / write_text)─────────────────────────',
|
|
77
83
|
'#',
|
|
78
|
-
'#
|
|
79
|
-
'#
|
|
80
|
-
'#
|
|
81
|
-
'#
|
|
84
|
+
'# 原生 `edit` / `write` 都会丢 UTF-8 BOM,且不还原文件自身的行尾(往 CRLF 文件写 LF 内容就变成 LF);',
|
|
85
|
+
'# 原生 `edit` 还只做精确匹配(`old_string` 差一个空格就报 FS_EDIT_NOT_FOUND)。本行两个工具保 BOM 与文件',
|
|
86
|
+
'# 自身行尾,精确失败时按行块相似度回退并给出最接近的候选。read-only 会话下在任何 I/O 之前拒写;模型可见',
|
|
87
|
+
'# 文本只有一行统计。',
|
|
82
88
|
'#',
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
...(maskNative
|
|
90
|
+
? ['# 原生 `edit`/`write` 由下面的门禁行按 agent 作用域屏蔽,本行因此改用 `guidance: short`。']
|
|
91
|
+
: ['# 原生 `edit`/`write` 保留不动:注册的是两个不同名工具,同一层不会同名冲突(回退:给本行加',
|
|
92
|
+
'# `disabled: true`,或整行删掉)。']),
|
|
85
93
|
'#',
|
|
86
|
-
'#
|
|
94
|
+
'# 由 `scripts/install-preset.mjs` 从本机 dsh 自带的 preset 组合派生:',
|
|
87
95
|
`# ${sourcePath}`,
|
|
88
|
-
'#
|
|
89
|
-
'#
|
|
90
|
-
'# 所以该插件刻意零依赖(只用 node: 内置模块),可以放在任何位置。',
|
|
91
|
-
'#',
|
|
92
|
-
'# 该插件消费宿主服务(tools / systemPrompt),不发布任何服务,因此不需要 isolate realm。',
|
|
96
|
+
'# 行名写绝对路径:preset 行的裸包名由宿主组装基址解析,模块内部的裸 import 由 Node 按文件真实路径解析,',
|
|
97
|
+
'# 而 preset 目录下没有 node_modules —— 该插件刻意零依赖(只用 node: 内置模块),可放任何位置。',
|
|
93
98
|
'#',
|
|
94
99
|
'# 可选 config(插件没有 Config schema,字段原样透传):',
|
|
95
|
-
'# backup / ledger: boolean 默认都 true(备份到 artifactsDir/backups,台账 artifactsDir/edits.log)',
|
|
96
|
-
'# artifactsDir: <路径> 默认 <会话工作区>/.dsh',
|
|
97
100
|
'# newFileBom: boolean 默认 false(新建文件是否写 BOM)',
|
|
98
|
-
'# context: number diff 上下文行数,默认 3',
|
|
99
101
|
'# root: <路径> 没有 agent 会话时的回退工作区',
|
|
102
|
+
'# guidance: full|short|false 默认 full;short 去掉"优先于原生"那半句',
|
|
100
103
|
'- id: tool-text-editor',
|
|
101
104
|
` name: '${PLUGIN}'`,
|
|
102
|
-
'',
|
|
103
|
-
]
|
|
105
|
+
...(maskNative ? [' config:', ' guidance: short'] : []),
|
|
106
|
+
]
|
|
107
|
+
if (maskNative) {
|
|
108
|
+
lines.push(
|
|
109
|
+
'',
|
|
110
|
+
'# ── 屏蔽原生的 write / edit(每个 agent 的作用域)──────────────────────────',
|
|
111
|
+
'#',
|
|
112
|
+
'# 原生两个工具仍在工具表里:每次请求付 1754 B 的两个 schema 加 608 B 的两段引导,只为劝模型别用它们。',
|
|
113
|
+
'# 本行按 agent 作用域收窄,三条通路任一条先到就先收窄(早先只挂 `agent/created`,而 GUI 是"先建 agent、',
|
|
114
|
+
'# 后换 preset",重挂是父级 re-link,那个事件早已发完):',
|
|
115
|
+
'# * `apply` 阶段就挂守卫:与创建顺序无关,第一次直呼原生工具即被否决并顺手收窄;',
|
|
116
|
+
'# * `agent/created`:建档时收窄本 preset 的 agent(含 subagent);',
|
|
117
|
+
'# * `tools/change`:换 preset 时 `recompose()` 发它,此时枚举活 agent 收窄属于本组合的,反向换出的成对撤销。',
|
|
118
|
+
'#',
|
|
119
|
+
'# 收窄用 `agent.ctx.tools.restrict({ deny })`:注册表只有一套可见性解析器,被拒的名字既不在工具表里也调不动',
|
|
120
|
+
'# (直呼得到 UNKNOWN_TOOL);另注册同名空段遮蔽 tool:write / tool:edit 引导。只影响选了本 preset 的会话,',
|
|
121
|
+
'# 其它 preset 照旧可用(天然对照组);只点名本 agent 真的看得见的工具,归属判据靠探测对象的身份。',
|
|
122
|
+
'# 回退:删掉本行,或给编辑行加 `guidance: full`。',
|
|
123
|
+
'- id: tool-native-edit-mask',
|
|
124
|
+
` name: '${MASK}'`,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
lines.push('')
|
|
128
|
+
return lines.join('\n')
|
|
104
129
|
}
|
|
105
130
|
|
|
106
|
-
/**
|
|
131
|
+
/** 插入位置:自带组合里"文件系统"之后、"后台任务"之前;找不到锚点就追加到末尾。 */
|
|
107
132
|
const ANCHORS = [
|
|
108
133
|
{ pattern: /^# ── background jobs/m, label: 'background jobs 段之前' },
|
|
109
134
|
{ pattern: /^- id: tool-jobs$/m, label: 'tool-jobs 行之前' },
|
|
@@ -114,11 +139,11 @@ const ANCHORS = [
|
|
|
114
139
|
* @returns `{ text, anchor }`
|
|
115
140
|
* @throws {Error} 源组合看起来已经打过补丁时。
|
|
116
141
|
*/
|
|
117
|
-
function inject(source, sourcePath) {
|
|
142
|
+
function inject(source, sourcePath, maskNative) {
|
|
118
143
|
if (/^- id: tool-text-editor$/m.test(source)) {
|
|
119
|
-
throw new Error('
|
|
144
|
+
throw new Error('the source composition already has a tool-text-editor row -- point at the unmodified composition shipped with dsh')
|
|
120
145
|
}
|
|
121
|
-
const block = pluginBlock(sourcePath)
|
|
146
|
+
const block = pluginBlock(sourcePath, maskNative)
|
|
122
147
|
for (const { pattern, label } of ANCHORS) {
|
|
123
148
|
const match = pattern.exec(source)
|
|
124
149
|
if (match !== null) {
|
|
@@ -130,59 +155,82 @@ function inject(source, sourcePath) {
|
|
|
130
155
|
return { text: source + separator + block, anchor: '文件末尾' }
|
|
131
156
|
}
|
|
132
157
|
|
|
133
|
-
// ── 参数与前置检查
|
|
158
|
+
// ── 参数与前置检查 ──
|
|
134
159
|
|
|
135
160
|
const id = flagValue('--id') ?? 'texteditor'
|
|
136
161
|
const base = flagValue('--base') ?? 'standard'
|
|
137
162
|
const force = process.argv.includes('--force')
|
|
138
163
|
const dryRun = process.argv.includes('--dry-run')
|
|
139
|
-
const
|
|
164
|
+
const maskNative = process.argv.includes('--mask-native')
|
|
165
|
+
/** `--from` / `DSH_PRESET_SOURCE`:显式源组合,给定时不再枚举自带布局。 */
|
|
166
|
+
const explicitSource = (flagValue('--from') ?? process.env.DSH_PRESET_SOURCE ?? '').trim()
|
|
167
|
+
const explicitPath = explicitSource === '' ? undefined : resolve(explicitSource)
|
|
168
|
+
/** 生效的用户目录:`DSH_HOME` 非空则用它,否则 `~/.dsh`。 */
|
|
169
|
+
const dshHome = process.env.DSH_HOME?.trim() || join(homedir(), '.dsh')
|
|
140
170
|
|
|
141
171
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
|
|
142
|
-
console.error('FAIL preset id
|
|
172
|
+
console.error('FAIL the preset id must match [a-z0-9][a-z0-9-]* (it becomes a directory name), got: ' + id)
|
|
143
173
|
process.exit(2)
|
|
144
174
|
}
|
|
145
175
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(base)) {
|
|
146
|
-
console.error('FAIL --base
|
|
176
|
+
console.error('FAIL --base must be the id of a preset shipped with dsh (standard / minimal / cordis / ptc), got: ' + base)
|
|
147
177
|
process.exit(2)
|
|
148
178
|
}
|
|
149
179
|
if (!existsSync(PLUGIN)) {
|
|
150
|
-
console.error('FAIL
|
|
180
|
+
console.error('FAIL plugin file not found: ' + PLUGIN)
|
|
181
|
+
process.exit(1)
|
|
182
|
+
}
|
|
183
|
+
if (maskNative && !existsSync(MASK)) {
|
|
184
|
+
console.error('FAIL mask file not found: ' + MASK + ' (--mask-native needs it)')
|
|
151
185
|
process.exit(1)
|
|
152
186
|
}
|
|
153
187
|
if (!existsSync(META)) {
|
|
154
|
-
console.error('FAIL
|
|
188
|
+
console.error('FAIL preset metadata not found: ' + META)
|
|
155
189
|
process.exit(1)
|
|
156
190
|
}
|
|
191
|
+
if (explicitPath !== undefined && !existsSync(explicitPath)) {
|
|
192
|
+
console.error('FAIL --from / DSH_PRESET_SOURCE points at a composition that does not exist: ' + explicitPath)
|
|
193
|
+
process.exit(2)
|
|
194
|
+
}
|
|
157
195
|
|
|
158
|
-
const candidates = findCompositions(base)
|
|
196
|
+
const candidates = findCompositions(base, explicitPath, dshHome)
|
|
159
197
|
const sourcePath = candidates.find((candidate) => existsSync(candidate))
|
|
160
198
|
if (sourcePath === undefined) {
|
|
161
|
-
console.error(`FAIL
|
|
199
|
+
console.error(`FAIL no preset composition shipped with the local dsh (--base ${base}); tried:`)
|
|
162
200
|
for (const candidate of candidates) console.error(' ' + candidate)
|
|
163
|
-
console.error('
|
|
201
|
+
console.error(' installing dsh provides it; you can also point --from <agent.cordis.yml path> or DSH_PRESET_SOURCE at one.')
|
|
164
202
|
process.exit(2)
|
|
165
203
|
}
|
|
166
204
|
|
|
167
205
|
const source = readFileSync(sourcePath, 'utf8')
|
|
168
206
|
let injected
|
|
169
207
|
try {
|
|
170
|
-
injected = inject(source, sourcePath)
|
|
208
|
+
injected = inject(source, sourcePath, maskNative)
|
|
171
209
|
} catch (error) {
|
|
172
210
|
console.error('FAIL ' + error.message)
|
|
173
211
|
process.exit(2)
|
|
174
212
|
}
|
|
175
213
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
214
|
+
/**
|
|
215
|
+
* 写出的 `preset.yml` 必须与实际组合一致:仓库那份与模式无关,而"原生工具是否被屏蔽"随 `--mask-native` 变
|
|
216
|
+
* (旧版原样拷贝,开着门禁的描述里也写着"原生 edit/write 保持不变")。状态句接在 `preset/preset.yml` 的最后
|
|
217
|
+
* 一行 `description:` 之后;它是 YAML 纯标量,不能出现 `: ` 或 ` #`。
|
|
218
|
+
*/
|
|
219
|
+
function metadataText() {
|
|
220
|
+
const state = maskNative
|
|
221
|
+
? ';原生 `edit`/`write` 已被门禁行按 agent 作用域屏蔽:既不出现在工具表里,也调不动。'
|
|
222
|
+
: ';原生 edit/write 保持不变。'
|
|
223
|
+
return readFileSync(META, 'utf8').trimEnd() + state + '\n'
|
|
224
|
+
}
|
|
225
|
+
|
|
179
226
|
const targetDir = join(dshHome, '.agent-presets', id)
|
|
180
227
|
const targetComposition = join(targetDir, COMPOSITION)
|
|
181
228
|
const targetMeta = join(targetDir, 'preset.yml')
|
|
182
229
|
|
|
183
230
|
console.log('仓库 : ' + REPO)
|
|
184
231
|
console.log('插件 : ' + PLUGIN)
|
|
185
|
-
console.log('
|
|
232
|
+
if (maskNative) console.log('门禁 : ' + MASK + '(屏蔽原生 write/edit,编辑行 guidance: short)')
|
|
233
|
+
console.log('源组合 : ' + sourcePath + (explicitPath === undefined ? `(--base ${base})` : ''))
|
|
186
234
|
console.log('插入位置 : ' + injected.anchor)
|
|
187
235
|
console.log('DSH_HOME : ' + dshHome)
|
|
188
236
|
console.log('目标 preset : ' + targetDir)
|
|
@@ -195,18 +243,18 @@ if (dryRun) {
|
|
|
195
243
|
console.log(' ' + targetComposition + `(源组合 ${source.split('\n').length} 行 + 插件段)`)
|
|
196
244
|
console.log(' ' + targetMeta)
|
|
197
245
|
if (exists && !force) {
|
|
246
|
+
// 只预告冲突:dry-run 不落盘,退出码按契约仍为 0。
|
|
198
247
|
console.error('')
|
|
199
|
-
console.error('[dry-run]
|
|
200
|
-
console.error('
|
|
201
|
-
process.exit(1)
|
|
248
|
+
console.error('[dry-run] but the target already exists, so a real run would be refused: ' + targetComposition)
|
|
249
|
+
console.error(' add --force to overwrite (only agent.cordis.yml and preset.yml are replaced; other files in that directory are left alone).')
|
|
202
250
|
}
|
|
203
251
|
process.exit(0)
|
|
204
252
|
}
|
|
205
253
|
|
|
206
254
|
if (exists && !force) {
|
|
207
255
|
console.error('')
|
|
208
|
-
console.error('FAIL
|
|
209
|
-
console.error('
|
|
256
|
+
console.error('FAIL that preset already exists: ' + targetComposition)
|
|
257
|
+
console.error(' add --force to overwrite (only agent.cordis.yml and preset.yml are replaced; other files in that directory are left alone).')
|
|
210
258
|
process.exit(1)
|
|
211
259
|
}
|
|
212
260
|
if (exists) {
|
|
@@ -215,7 +263,7 @@ if (exists) {
|
|
|
215
263
|
|
|
216
264
|
mkdirSync(targetDir, { recursive: true })
|
|
217
265
|
writeFileSync(targetComposition, injected.text, 'utf8')
|
|
218
|
-
writeFileSync(targetMeta,
|
|
266
|
+
writeFileSync(targetMeta, metadataText(), 'utf8')
|
|
219
267
|
|
|
220
268
|
console.log('')
|
|
221
269
|
console.log('OK 已安装 preset "' + id + '"')
|
|
@@ -223,5 +271,10 @@ console.log('下一步:')
|
|
|
223
271
|
console.log(' 1. 重启 dsh web(preset 名单在启动时读取;运行中的会话不会换 preset)')
|
|
224
272
|
console.log(' 2. 新建一个会话,preset 选 "' + id + '"')
|
|
225
273
|
console.log(' 3. 会话里直接用 edit_text / write_text(纯 Node 进程内实现:零依赖、零外部运行时)')
|
|
274
|
+
if (maskNative) {
|
|
275
|
+
console.log(' 该 preset 的会话里原生 write/edit 既不出现在工具表里、也调不动(直呼得到 UNKNOWN_TOOL);')
|
|
276
|
+
console.log(' 想对比或观察原生行为,用别的 preset(如 standard)新建会话即可 —— 门禁只作用于本 preset。')
|
|
277
|
+
}
|
|
226
278
|
console.log('升级 dsh 后重跑本脚本(加 --force)即可让 preset 跟上新版自带组合。')
|
|
227
279
|
console.log('回退:给 ' + targetComposition + ' 里的 tool-text-editor 行加 disabled: true,或删掉 ' + targetDir)
|
|
280
|
+
if (maskNative) console.log(' 只想撤掉屏蔽:删掉同文件里的 tool-native-edit-mask 行,并把编辑行的 guidance 改回 full。')
|