@ljwei-stak/dsh-model-router 0.17.1 → 0.18.1

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.
@@ -24,7 +24,7 @@
24
24
  export const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/'
25
25
 
26
26
  /** Shared shape of a "package manager" method: argv only, no script staging. */
27
- function packageManagerMethod(id, label, manager, spec, { registryFlag = '--registry=' } = {}) {
27
+ function packageManagerMethod(id, label, manager, spec, { registryFlag = '--registry=', command = 'install', extraArgs = [] } = {}) {
28
28
  return Object.freeze({
29
29
  id,
30
30
  label,
@@ -32,23 +32,20 @@ function packageManagerMethod(id, label, manager, spec, { registryFlag = '--regi
32
32
  manager,
33
33
  spec,
34
34
  registryFlag,
35
- installArgs: Object.freeze(['install', '-g', spec]),
35
+ installArgs: Object.freeze([command, '-g', spec, ...extraArgs]),
36
36
  uninstallArgs: Object.freeze(['uninstall', '-g', spec.replace(/@latest$/, '')]),
37
- /** npm/pnpm both accept --prefix to place a global install off the default. */
37
+ /** The resolver binds npm prefix or pnpm global-dir/global-bin-dir. */
38
38
  supportsInstallDir: true,
39
39
  supportsRegistry: true,
40
40
  })
41
41
  }
42
42
 
43
- /**
44
- * Vendor installer script run from a verified local copy.
45
- * A directory is passed either as `installArg` (`--install-dir`) or as
46
- * `installDirEnv` (`MCODE_INSTALL_DIR`, `CODEX_INSTALL_DIR`, …). Scripts that
47
- * hardcode their own path set neither and `supportsInstallDir` stays false.
48
- */
43
+ /** Vendor installer script run from a verified local copy. */
49
44
  function scriptMethod(id, label, {
50
45
  shell, scriptUrl, verify, installArg = [], env = [], platforms,
51
- installDirEnv = '', defaultInstallDir = '',
46
+ supportsInstallDir = false, installDirEnv = '', installDirSuffix = '',
47
+ defaultScriptInstallDir, defaultScriptInstallDirEnv, scriptBinaryNames,
48
+ registryEnv, installDirSubdir, uninstallUnsupportedReason, scriptFallbackUrls,
52
49
  }) {
53
50
  return Object.freeze({
54
51
  id,
@@ -61,14 +58,19 @@ function scriptMethod(id, label, {
61
58
  installArgs: Object.freeze(installArg),
62
59
  /** Environment the vendor script itself documents for this install. */
63
60
  env: Object.freeze(env),
64
- /** When set, the configured directory is this environment variable, not an argv flag. */
65
- installDirEnv,
66
- /** Home-relative directory the vendor uses when the user sets none. Slashes only. */
67
- defaultInstallDir,
68
61
  platforms: Object.freeze(platforms),
69
62
  /** Whether the vendor script honours a caller-chosen directory at all. */
70
- supportsInstallDir: Boolean(installDirEnv) || installArg.length > 0,
71
- supportsRegistry: false,
63
+ supportsInstallDir,
64
+ installDirEnv,
65
+ installDirSuffix,
66
+ ...(installDirSubdir ? { installDirSubdir } : {}),
67
+ ...(uninstallUnsupportedReason ? { uninstallUnsupportedReason } : {}),
68
+ ...(defaultScriptInstallDir ? { defaultScriptInstallDir } : {}),
69
+ ...(defaultScriptInstallDirEnv ? { defaultScriptInstallDirEnv } : {}),
70
+ ...(scriptBinaryNames ? { scriptBinaryNames: Object.freeze(scriptBinaryNames) } : {}),
71
+ ...(scriptFallbackUrls ? { scriptFallbackUrls: Object.freeze(scriptFallbackUrls) } : {}),
72
+ ...(registryEnv ? { registryEnv } : {}),
73
+ supportsRegistry: Boolean(registryEnv),
72
74
  })
73
75
  }
74
76
 
@@ -86,34 +88,25 @@ export const OFFICIAL_TOOLS = Object.freeze([
86
88
  probeNote: 'kimi 与旧 Python 版 kimi-cli 同名;请核对可执行文件来源和版本。',
87
89
  providerHints: ['moonshot', 'kimi'],
88
90
  scriptBinaryNames: Object.freeze(['kimi', 'kimi.exe']),
89
- scriptBinaryRelatives: Object.freeze(['bin/kimi', 'bin/kimi.exe']),
90
- defaultScriptInstallDir: '.kimi-code',
91
+ defaultScriptInstallDir: '.kimi-code/bin',
91
92
  installMethods: Object.freeze([
92
93
  packageManagerMethod('npm', 'npm', 'npm', '@moonshot-ai/kimi-code@latest'),
93
94
  packageManagerMethod('pnpm', 'pnpm', 'pnpm', '@moonshot-ai/kimi-code@latest'),
94
95
  scriptMethod('script-bash', 'curl | bash', {
95
- shell: 'bash',
96
- scriptUrl: 'https://code.kimi.com/kimi-code/install.sh',
97
- platforms: ['linux', 'darwin'],
98
- installDirEnv: 'KIMI_INSTALL_DIR',
99
- defaultInstallDir: '.kimi-code',
96
+ shell: 'bash', scriptUrl: 'https://code.kimi.com/kimi-code/install.sh',
97
+ platforms: ['linux', 'darwin'], supportsInstallDir: true,
98
+ installDirEnv: 'KIMI_INSTALL_DIR', installDirSuffix: 'bin',
100
99
  env: ['KIMI_INSTALL_DIR'],
101
- verify: Object.freeze({
102
- mustInclude: ['code.kimi.com/kimi-code', 'KIMI_INSTALL_DIR'],
103
- mustNotInclude: ['downloads.claude.ai/claude-code-releases', '@minimax-ai/code'],
104
- }),
100
+ scriptBinaryNames: ['kimi'],
101
+ verify: Object.freeze({ mustInclude: ['kimi-code', 'code.kimi.com/kimi-code'] }),
105
102
  }),
106
103
  scriptMethod('script-powershell', 'irm | iex', {
107
- shell: 'powershell',
108
- scriptUrl: 'https://code.kimi.com/kimi-code/install.ps1',
109
- platforms: ['win32'],
110
- installDirEnv: 'KIMI_INSTALL_DIR',
111
- defaultInstallDir: '.kimi-code',
104
+ shell: 'powershell', scriptUrl: 'https://code.kimi.com/kimi-code/install.ps1',
105
+ platforms: ['win32'], supportsInstallDir: true,
106
+ installDirEnv: 'KIMI_INSTALL_DIR', installDirSuffix: 'bin',
112
107
  env: ['KIMI_INSTALL_DIR'],
113
- verify: Object.freeze({
114
- mustInclude: ['code.kimi.com/kimi-code', 'KIMI_INSTALL_DIR'],
115
- mustNotInclude: ['downloads.claude.ai/claude-code-releases', '@minimax-ai/code'],
116
- }),
108
+ scriptBinaryNames: ['kimi.exe'],
109
+ verify: Object.freeze({ mustInclude: ['kimi-code', 'code.kimi.com/kimi-code'] }),
117
110
  }),
118
111
  ]),
119
112
  }),
@@ -128,31 +121,28 @@ export const OFFICIAL_TOOLS = Object.freeze([
128
121
  uninstallArgs: ['uninstall', '-g', '@anthropic-ai/claude-code'],
129
122
  probeExecutables: ['claude'],
130
123
  providerHints: ['anthropic', 'claude'],
131
- // The native installer always lands in ~/.local/bin; it has no directory flag.
132
124
  scriptBinaryNames: Object.freeze(['claude', 'claude.exe']),
133
125
  defaultScriptInstallDir: '.local/bin',
134
126
  installMethods: Object.freeze([
135
127
  packageManagerMethod('npm', 'npm', 'npm', '@anthropic-ai/claude-code@latest'),
136
- packageManagerMethod('pnpm', 'pnpm', 'pnpm', '@anthropic-ai/claude-code@latest'),
128
+ packageManagerMethod('pnpm', 'pnpm', 'pnpm', '@anthropic-ai/claude-code@latest', {
129
+ command: 'add',
130
+ extraArgs: ['--ignore-scripts=false', '--allow-build=@anthropic-ai/claude-code'],
131
+ }),
132
+ // The native installer has no directory argument. npm/pnpm support a prefix.
137
133
  scriptMethod('script-bash', 'curl | bash', {
138
- shell: 'bash',
139
- scriptUrl: 'https://claude.ai/install.sh',
134
+ shell: 'bash', scriptUrl: 'https://claude.ai/install.sh',
135
+ scriptFallbackUrls: ['https://downloads.claude.ai/claude-code-releases/bootstrap.sh'],
140
136
  platforms: ['linux', 'darwin'],
141
- defaultInstallDir: '.local/bin',
142
- verify: Object.freeze({
143
- mustInclude: ['downloads.claude.ai/claude-code-releases', 'claude.ai/install.sh'],
144
- mustNotInclude: ['@minimax-ai/code', 'releases.openai.com/codex'],
145
- }),
137
+ scriptBinaryNames: ['claude'],
138
+ verify: Object.freeze({ mustInclude: ['downloads.claude.ai/claude-code-releases', 'manifest.json'] }),
146
139
  }),
147
140
  scriptMethod('script-powershell', 'irm | iex', {
148
- shell: 'powershell',
149
- scriptUrl: 'https://claude.ai/install.ps1',
141
+ shell: 'powershell', scriptUrl: 'https://claude.ai/install.ps1',
142
+ scriptFallbackUrls: ['https://downloads.claude.ai/claude-code-releases/bootstrap.ps1'],
150
143
  platforms: ['win32'],
151
- defaultInstallDir: '.local/bin',
152
- verify: Object.freeze({
153
- mustInclude: ['downloads.claude.ai/claude-code-releases'],
154
- mustNotInclude: ['@minimax-ai/code', 'releases.openai.com/codex'],
155
- }),
144
+ scriptBinaryNames: ['claude.exe'],
145
+ verify: Object.freeze({ mustInclude: ['downloads.claude.ai/claude-code-releases', 'manifest.json'] }),
156
146
  }),
157
147
  ]),
158
148
  }),
@@ -169,33 +159,28 @@ export const OFFICIAL_TOOLS = Object.freeze([
169
159
  probeNote: 'Codex 版本横幅由适配层宽匹配;安装时取 npm 最新版。',
170
160
  providerHints: ['openai', 'gpt', 'codex'],
171
161
  scriptBinaryNames: Object.freeze(['codex', 'codex.exe']),
162
+ defaultScriptInstallDir: '.local/bin',
172
163
  installMethods: Object.freeze([
173
164
  packageManagerMethod('npm', 'npm', 'npm', '@openai/codex@latest'),
174
165
  packageManagerMethod('pnpm', 'pnpm', 'pnpm', '@openai/codex@latest'),
175
- scriptMethod('script-bash', 'curl | bash', {
176
- shell: 'bash',
177
- scriptUrl: 'https://chatgpt.com/codex/install.sh',
178
- platforms: ['linux', 'darwin'],
179
- installDirEnv: 'CODEX_INSTALL_DIR',
180
- defaultInstallDir: '.local/bin',
181
- env: ['CODEX_INSTALL_DIR'],
182
- verify: Object.freeze({
183
- mustInclude: ['releases.openai.com/codex', 'CODEX_INSTALL_DIR'],
184
- mustNotInclude: ['downloads.claude.ai/claude-code-releases', '@minimax-ai/code'],
185
- }),
166
+ scriptMethod('script-sh', 'curl | sh', {
167
+ shell: 'sh', scriptUrl: 'https://chatgpt.com/codex/install.sh',
168
+ platforms: ['linux', 'darwin'], supportsInstallDir: true,
169
+ installDirEnv: 'CODEX_INSTALL_DIR', env: ['CODEX_INSTALL_DIR'],
170
+ scriptBinaryNames: ['codex'],
171
+ verify: Object.freeze({ mustInclude: ['releases.openai.com/codex', 'CODEX_INSTALL_DIR'] }),
186
172
  }),
187
173
  scriptMethod('script-powershell', 'irm | iex', {
188
- shell: 'powershell',
189
- scriptUrl: 'https://chatgpt.com/codex/install.ps1',
190
- platforms: ['win32'],
191
- installDirEnv: 'CODEX_INSTALL_DIR',
192
- // %LOCALAPPDATA%\Programs\OpenAI\Codex\bin under a normal user profile.
193
- defaultInstallDir: 'AppData/Local/Programs/OpenAI/Codex/bin',
194
- env: ['CODEX_INSTALL_DIR'],
195
- verify: Object.freeze({
196
- mustInclude: ['releases.openai.com/codex', 'CODEX_INSTALL_DIR'],
197
- mustNotInclude: ['downloads.claude.ai/claude-code-releases', '@minimax-ai/code'],
198
- }),
174
+ shell: 'powershell', scriptUrl: 'https://chatgpt.com/codex/install.ps1',
175
+ platforms: ['win32'], supportsInstallDir: true,
176
+ installDirEnv: 'CODEX_INSTALL_DIR', env: ['CODEX_INSTALL_DIR'],
177
+ defaultScriptInstallDirEnv: 'LOCALAPPDATA',
178
+ defaultScriptInstallDir: 'Programs/OpenAI/Codex/bin',
179
+ installDirSubdir: 'codex/bin',
180
+ uninstallUnsupportedReason: 'Codex 官方 Windows 脚本使用目录联接和版本化资源;请按官方卸载说明移除,插件不会删除联接目标。',
181
+ // The Windows bin directory is a junction to the versioned payload.
182
+ scriptBinaryNames: ['codex.exe'],
183
+ verify: Object.freeze({ mustInclude: ['releases.openai.com/codex', 'CODEX_INSTALL_DIR'] }),
199
184
  }),
200
185
  ]),
201
186
  }),
@@ -210,41 +195,32 @@ export const OFFICIAL_TOOLS = Object.freeze([
210
195
  uninstallArgs: ['uninstall', '-g', '@minimax-ai/code'],
211
196
  probeExecutables: ['mcode'],
212
197
  providerHints: ['minimax'],
213
- scriptBinaryNames: Object.freeze([
214
- 'mcode', 'mcode.exe', 'mcode.cmd', 'mcode.ps1',
215
- 'mcode-tools', 'mcode-tools.cmd', 'mcode-tools.ps1',
216
- ]),
217
- scriptBinaryRelatives: Object.freeze([
218
- 'mcode.cmd', 'mcode.ps1', 'mcode-tools.cmd', 'mcode-tools.ps1',
219
- 'bin/mcode', 'bin/mcode-tools',
220
- ]),
221
- defaultScriptInstallDir: '.minimax-code',
198
+ defaultMethodsByPlatform: Object.freeze({ win32: 'script-powershell' }),
199
+ scriptBinaryNames: Object.freeze(['mcode', 'mcode-tools']),
200
+ defaultScriptInstallDir: '.minimax-code/bin',
222
201
  installMethods: Object.freeze([
223
- packageManagerMethod('npm', 'npm', 'npm', '@minimax-ai/code@latest'),
224
- packageManagerMethod('pnpm', 'pnpm', 'pnpm', '@minimax-ai/code@latest'),
202
+ packageManagerMethod('npm', 'npm', 'npm', '@minimax-ai/code@latest', {
203
+ extraArgs: ['--ignore-scripts=false', '--include=optional', '--allow-scripts=@minimax-ai/code,better-sqlite3'],
204
+ }),
205
+ packageManagerMethod('pnpm', 'pnpm', 'pnpm', '@minimax-ai/code@latest', {
206
+ command: 'add',
207
+ extraArgs: ['--ignore-scripts=false', '--allow-build=@minimax-ai/code', '--allow-build=better-sqlite3'],
208
+ }),
225
209
  scriptMethod('script-bash', 'curl | bash', {
226
- shell: 'bash',
227
- scriptUrl: 'https://filecdn.minimax.chat/public/install.sh',
228
- platforms: ['linux', 'darwin'],
229
- installDirEnv: 'MCODE_INSTALL_DIR',
230
- defaultInstallDir: '.minimax-code',
231
- env: ['MCODE_INSTALL_DIR', 'MCODE_NPM_REGISTRY'],
232
- verify: Object.freeze({
233
- mustInclude: ['@minimax-ai/code', 'MCODE_INSTALL_DIR', 'MiniMax Code'],
234
- mustNotInclude: ['downloads.claude.ai/claude-code-releases', 'releases.openai.com/codex'],
235
- }),
210
+ shell: 'bash', scriptUrl: 'https://filecdn.minimax.chat/public/install.sh',
211
+ platforms: ['linux', 'darwin'], supportsInstallDir: true,
212
+ installDirEnv: 'MCODE_INSTALL_DIR', installDirSuffix: 'bin',
213
+ registryEnv: 'MCODE_NPM_REGISTRY', env: ['MCODE_INSTALL_DIR', 'MCODE_NPM_REGISTRY'],
214
+ verify: Object.freeze({ mustInclude: ['MiniMax Code CLI', '@minimax-ai/code'] }),
236
215
  }),
237
216
  scriptMethod('script-powershell', 'irm | iex', {
238
- shell: 'powershell',
239
- scriptUrl: 'https://filecdn.minimax.chat/public/install.ps1',
240
- platforms: ['win32'],
217
+ shell: 'powershell', scriptUrl: 'https://filecdn.minimax.chat/public/install.ps1',
218
+ platforms: ['win32'], supportsInstallDir: true,
241
219
  installDirEnv: 'MCODE_INSTALL_DIR',
242
- defaultInstallDir: '.minimax-code',
243
- env: ['MCODE_INSTALL_DIR', 'MCODE_NPM_REGISTRY'],
244
- verify: Object.freeze({
245
- mustInclude: ['@minimax-ai/code', 'MCODE_INSTALL_DIR', 'MiniMax Code'],
246
- mustNotInclude: ['downloads.claude.ai/claude-code-releases', 'releases.openai.com/codex'],
247
- }),
220
+ registryEnv: 'MCODE_NPM_REGISTRY', env: ['MCODE_INSTALL_DIR', 'MCODE_NPM_REGISTRY'],
221
+ defaultScriptInstallDir: '.minimax-code',
222
+ scriptBinaryNames: ['mcode.cmd', 'mcode.ps1', 'mcode-tools.cmd', 'mcode-tools.ps1'],
223
+ verify: Object.freeze({ mustInclude: ['MiniMax Code CLI', '@minimax-ai/code'] }),
248
224
  }),
249
225
  ]),
250
226
  }),
@@ -264,28 +240,18 @@ export const OFFICIAL_TOOLS = Object.freeze([
264
240
  installMethods: Object.freeze([
265
241
  packageManagerMethod('npm', 'npm', 'npm', '@mimo-ai/cli@latest'),
266
242
  packageManagerMethod('pnpm', 'pnpm', 'pnpm', '@mimo-ai/cli@latest'),
267
- // The Unix script hardcodes ~/.mimocode/bin and ignores MIMOCODE_INSTALL_DIR.
268
243
  scriptMethod('script-bash', 'curl | bash', {
269
- shell: 'bash',
270
- scriptUrl: 'https://mimo.xiaomi.com/install',
244
+ shell: 'bash', scriptUrl: 'https://mimo.xiaomi.com/install',
271
245
  platforms: ['linux', 'darwin'],
272
- defaultInstallDir: '.mimocode/bin',
273
- verify: Object.freeze({
274
- mustInclude: ['MiMoCode', 'mimo.xiaomi.com'],
275
- mustNotInclude: ['@minimax-ai/code', 'downloads.claude.ai/claude-code-releases'],
276
- }),
246
+ scriptBinaryNames: ['mimo'],
247
+ verify: Object.freeze({ mustInclude: ['mimo.xiaomi.com', '.mimocode'] }),
277
248
  }),
278
249
  scriptMethod('script-powershell', 'irm | iex', {
279
- shell: 'powershell',
280
- scriptUrl: 'https://mimo.xiaomi.com/install.ps1',
281
- platforms: ['win32'],
282
- installDirEnv: 'MIMOCODE_INSTALL_DIR',
283
- defaultInstallDir: '.mimocode/bin',
284
- env: ['MIMOCODE_INSTALL_DIR'],
285
- verify: Object.freeze({
286
- mustInclude: ['MiMoCode', 'mimo.xiaomi.com', 'MIMOCODE_INSTALL_DIR'],
287
- mustNotInclude: ['@minimax-ai/code', 'downloads.claude.ai/claude-code-releases'],
288
- }),
250
+ shell: 'powershell', scriptUrl: 'https://mimo.xiaomi.com/install.ps1',
251
+ platforms: ['win32'], supportsInstallDir: true,
252
+ installDirEnv: 'MIMOCODE_INSTALL_DIR', env: ['MIMOCODE_INSTALL_DIR'],
253
+ scriptBinaryNames: ['mimo.exe'],
254
+ verify: Object.freeze({ mustInclude: ['mimo.xiaomi.com', '.mimocode'] }),
289
255
  }),
290
256
  ]),
291
257
  }),
@@ -300,34 +266,25 @@ export const OFFICIAL_TOOLS = Object.freeze([
300
266
  uninstallArgs: ['uninstall', '-g', '@xai-official/grok'],
301
267
  probeExecutables: ['grok'],
302
268
  providerHints: ['xai', 'grok'],
303
- scriptBinaryNames: Object.freeze(['grok', 'grok.exe', 'agent', 'agent.exe']),
269
+ scriptBinaryNames: Object.freeze(['grok', 'agent', 'grok.exe', 'agent.exe', 'grove.exe', 'grove-fsmonitor.exe', 'grove-credential.exe']),
304
270
  defaultScriptInstallDir: '.grok/bin',
271
+ pathProfileMarkers: Object.freeze(['# >>> grok installer >>>', '# <<< grok installer <<<']),
305
272
  installMethods: Object.freeze([
306
273
  packageManagerMethod('npm', 'npm', 'npm', '@xai-official/grok@latest'),
307
274
  packageManagerMethod('pnpm', 'pnpm', 'pnpm', '@xai-official/grok@latest'),
308
275
  scriptMethod('script-bash', 'curl | bash', {
309
- shell: 'bash',
310
- scriptUrl: 'https://x.ai/cli/install.sh',
311
- platforms: ['linux', 'darwin'],
312
- installDirEnv: 'GROK_BIN_DIR',
313
- defaultInstallDir: '.grok/bin',
314
- env: ['GROK_BIN_DIR'],
315
- verify: Object.freeze({
316
- mustInclude: ['x.ai/cli', 'GROK_BIN_DIR'],
317
- mustNotInclude: ['@minimax-ai/code', 'downloads.claude.ai/claude-code-releases'],
318
- }),
276
+ shell: 'bash', scriptUrl: 'https://x.ai/cli/install.sh',
277
+ platforms: ['linux', 'darwin'], supportsInstallDir: true,
278
+ installDirEnv: 'GROK_BIN_DIR', env: ['GROK_BIN_DIR'],
279
+ scriptBinaryNames: ['grok', 'agent'],
280
+ verify: Object.freeze({ mustInclude: ['Grok CLI installer', 'x.ai/cli'] }),
319
281
  }),
320
282
  scriptMethod('script-powershell', 'irm | iex', {
321
- shell: 'powershell',
322
- scriptUrl: 'https://x.ai/cli/install.ps1',
323
- platforms: ['win32'],
324
- installDirEnv: 'GROK_BIN_DIR',
325
- defaultInstallDir: '.grok/bin',
326
- env: ['GROK_BIN_DIR'],
327
- verify: Object.freeze({
328
- mustInclude: ['x.ai/cli', 'GROK_BIN_DIR'],
329
- mustNotInclude: ['@minimax-ai/code', 'downloads.claude.ai/claude-code-releases'],
330
- }),
283
+ shell: 'powershell', scriptUrl: 'https://x.ai/cli/install.ps1',
284
+ platforms: ['win32'], supportsInstallDir: true,
285
+ installDirEnv: 'GROK_BIN_DIR', env: ['GROK_BIN_DIR'],
286
+ scriptBinaryNames: ['grok.exe', 'agent.exe', 'grove.exe', 'grove-fsmonitor.exe', 'grove-credential.exe'],
287
+ verify: Object.freeze({ mustInclude: ['Grok CLI installer', 'x.ai/cli'] }),
331
288
  }),
332
289
  ]),
333
290
  }),
@@ -375,7 +332,9 @@ export const OFFICIAL_TOOLS = Object.freeze([
375
332
  shell: 'bash',
376
333
  scriptUrl: 'https://opencode.ai/install',
377
334
  platforms: ['linux', 'darwin'],
378
- defaultInstallDir: '.opencode/bin',
335
+ // The published script hardcodes $HOME/.opencode/bin, so it cannot honour
336
+ // a custom install directory; the npm/pnpm methods above can.
337
+ supportsInstallDir: false,
379
338
  verify: Object.freeze({
380
339
  mustInclude: ['anomalyco/opencode', 'opencode.ai'],
381
340
  mustNotInclude: ['static-openapi.stepfun.com'],
@@ -405,7 +364,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
405
364
  platforms: ['linux', 'darwin'],
406
365
  installArg: ['--install-dir'],
407
366
  env: ['STEP_INSTALL_DIR', 'STEP_RELEASE_BASE_URL'],
408
- defaultInstallDir: '.stepcode/bin',
367
+ supportsInstallDir: true,
409
368
  verify: Object.freeze({
410
369
  mustInclude: ['static-openapi.stepfun.com', 'stepcode installer'],
411
370
  mustNotInclude: ['anomalyco/opencode'],
@@ -417,7 +376,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
417
376
  platforms: ['win32'],
418
377
  installArg: ['-InstallDir'],
419
378
  env: ['STEP_INSTALL_DIR', 'STEP_RELEASE_BASE_URL'],
420
- defaultInstallDir: '.stepcode/bin',
379
+ supportsInstallDir: true,
421
380
  verify: Object.freeze({
422
381
  mustInclude: ['static-openapi.stepfun.com', 'stepcode'],
423
382
  mustNotInclude: ['anomalyco/opencode'],
@@ -464,7 +423,10 @@ export function installMethodsFor(tool) {
464
423
  export function installMethodFor(tool, methodId) {
465
424
  const wanted = String(methodId ?? '').trim()
466
425
  if (!wanted) return null
467
- return installMethodsFor(tool).find(method => method.id === wanted) ?? null
426
+ // 0.17.1 saved Codex's POSIX installer as script-bash. The vendor uses sh;
427
+ // preserve that saved choice while returning the current declared method.
428
+ const canonical = tool?.id === 'codex' && wanted === 'script-bash' ? 'script-sh' : wanted
429
+ return installMethodsFor(tool).find(method => method.id === canonical) ?? null
468
430
  }
469
431
 
470
432
  /** The tool's default method, kept identical to `manager` / `installArgs`. */
@@ -480,7 +442,7 @@ export function installMethodBlockReason(method, platform = 'linux') {
480
442
 
481
443
  /**
482
444
  * A method able to place the tool in the user's chosen directory. npm and pnpm
483
- * take --prefix; the opencode script hardcodes its own path and cannot.
445
+ * bind their global directories; vendor scripts declare an argument or environment binding.
484
446
  */
485
447
  export function methodHonoursInstallDir(method) {
486
448
  return method?.supportsInstallDir === true
@@ -499,8 +461,8 @@ export function installCommandLineFor(tool, method) {
499
461
  if (!tool || tool.unsupported) return null
500
462
  if (!method) return null
501
463
  if (method.kind === 'script') {
502
- const piped = method.shell === 'powershell' ? `irm ${method.scriptUrl} | iex` : `curl -fsSL ${method.scriptUrl} | bash`
503
- return method.installArgs.length ? `${piped} ${method.installArgs[0]} <安装目录>` : piped
464
+ const piped = method.shell === 'powershell' ? `irm ${method.scriptUrl} | iex` : `curl -fsSL ${method.scriptUrl} | ${method.shell}`
465
+ return piped
504
466
  }
505
467
  return `${method.manager} ${method.installArgs.join(' ')}`
506
468
  }
@@ -509,6 +471,7 @@ export function installCommandLineFor(tool, method) {
509
471
  export function uninstallCommandLineFor(tool, method) {
510
472
  if (!tool || tool.unsupported || !method) return null
511
473
  if (tool.manager === 'signed-windows-installer') return null
474
+ if (method.uninstallUnsupportedReason) return method.uninstallUnsupportedReason
512
475
  if (method.kind === 'script') return '删除官方安装的可执行文件(保留配置与登录信息)'
513
476
  return `${method.manager} ${method.uninstallArgs.join(' ')}`
514
477
  }
@@ -193,6 +193,7 @@ const jsonParameter = (name, codec) => Object.freeze({ name, wire: name, source:
193
193
 
194
194
  export const OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
195
195
  descriptor('list', [], listResultCodec),
196
+ descriptor('installSettings', [], anyObjectCodec('OfficialInstallSettings')),
196
197
  descriptor('installTool', [jsonParameter('request', toolActionCodec)], installResultCodec),
197
198
  descriptor('uninstallTool', [jsonParameter('request', toolActionCodec)], installResultCodec),
198
199
  descriptor('repairTool', [jsonParameter('request', toolActionCodec)], installResultCodec),