@yivan-lab/pretty-please 1.1.0 → 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.
Files changed (62) hide show
  1. package/README.md +390 -1
  2. package/bin/pls.tsx +1255 -123
  3. package/dist/bin/pls.js +1098 -103
  4. package/dist/package.json +4 -4
  5. package/dist/src/alias.d.ts +41 -0
  6. package/dist/src/alias.js +240 -0
  7. package/dist/src/chat-history.js +10 -1
  8. package/dist/src/components/Chat.js +54 -26
  9. package/dist/src/components/CodeColorizer.js +26 -20
  10. package/dist/src/components/CommandBox.js +19 -8
  11. package/dist/src/components/ConfirmationPrompt.js +2 -1
  12. package/dist/src/components/Duration.js +2 -1
  13. package/dist/src/components/InlineRenderer.js +2 -1
  14. package/dist/src/components/MarkdownDisplay.js +2 -1
  15. package/dist/src/components/MultiStepCommandGenerator.d.ts +3 -1
  16. package/dist/src/components/MultiStepCommandGenerator.js +20 -10
  17. package/dist/src/components/TableRenderer.js +2 -1
  18. package/dist/src/config.d.ts +33 -3
  19. package/dist/src/config.js +83 -34
  20. package/dist/src/mastra-agent.d.ts +1 -0
  21. package/dist/src/mastra-agent.js +3 -11
  22. package/dist/src/mastra-chat.d.ts +13 -6
  23. package/dist/src/mastra-chat.js +31 -31
  24. package/dist/src/multi-step.d.ts +23 -7
  25. package/dist/src/multi-step.js +45 -26
  26. package/dist/src/prompts.d.ts +30 -4
  27. package/dist/src/prompts.js +218 -70
  28. package/dist/src/remote-history.d.ts +63 -0
  29. package/dist/src/remote-history.js +315 -0
  30. package/dist/src/remote.d.ts +113 -0
  31. package/dist/src/remote.js +634 -0
  32. package/dist/src/shell-hook.d.ts +58 -0
  33. package/dist/src/shell-hook.js +295 -26
  34. package/dist/src/ui/theme.d.ts +60 -23
  35. package/dist/src/ui/theme.js +544 -22
  36. package/dist/src/upgrade.d.ts +41 -0
  37. package/dist/src/upgrade.js +348 -0
  38. package/dist/src/utils/console.d.ts +4 -0
  39. package/dist/src/utils/console.js +89 -17
  40. package/package.json +4 -4
  41. package/src/alias.ts +301 -0
  42. package/src/chat-history.ts +11 -1
  43. package/src/components/Chat.tsx +71 -26
  44. package/src/components/CodeColorizer.tsx +27 -19
  45. package/src/components/CommandBox.tsx +26 -8
  46. package/src/components/ConfirmationPrompt.tsx +2 -1
  47. package/src/components/Duration.tsx +2 -1
  48. package/src/components/InlineRenderer.tsx +2 -1
  49. package/src/components/MarkdownDisplay.tsx +2 -1
  50. package/src/components/MultiStepCommandGenerator.tsx +25 -11
  51. package/src/components/TableRenderer.tsx +2 -1
  52. package/src/config.ts +126 -35
  53. package/src/mastra-agent.ts +3 -12
  54. package/src/mastra-chat.ts +40 -34
  55. package/src/multi-step.ts +62 -30
  56. package/src/prompts.ts +236 -78
  57. package/src/remote-history.ts +390 -0
  58. package/src/remote.ts +800 -0
  59. package/src/shell-hook.ts +339 -26
  60. package/src/ui/theme.ts +632 -23
  61. package/src/upgrade.ts +397 -0
  62. package/src/utils/console.ts +99 -17
package/bin/pls.tsx CHANGED
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env tsx
2
- import React from 'react'
3
- import { render } from 'ink'
4
2
  import { Command } from 'commander'
5
3
  import { fileURLToPath } from 'url'
6
4
  import { dirname, join } from 'path'
5
+ import path from 'path'
7
6
  import { exec } from 'child_process'
8
7
  import fs from 'fs'
9
8
  import os from 'os'
10
9
  import chalk from 'chalk'
11
- import { MultiStepCommandGenerator } from '../src/components/MultiStepCommandGenerator.js'
12
- import { Chat } from '../src/components/Chat.js'
10
+ // React Ink 懒加载(只在需要 UI 时加载)
11
+ // import React from 'react'
12
+ // import { render } from 'ink'
13
+ // import { MultiStepCommandGenerator } from '../src/components/MultiStepCommandGenerator.js'
14
+ // import { Chat } from '../src/components/Chat.js'
13
15
  import { isConfigValid, setConfigValue, getConfig, maskApiKey } from '../src/config.js'
14
16
  import { clearHistory, addHistory, getHistory, getHistoryFilePath } from '../src/history.js'
15
17
  import { clearChatHistory, getChatRoundCount, getChatHistoryFilePath, displayChatHistory } from '../src/chat-history.js'
@@ -23,9 +25,63 @@ import {
23
25
  displayShellHistory,
24
26
  clearShellHistory,
25
27
  } from '../src/shell-hook.js'
28
+ import {
29
+ checkForUpdates,
30
+ showUpdateNotice,
31
+ performUpgrade,
32
+ } from '../src/upgrade.js'
33
+ import { getCurrentTheme } from '../src/ui/theme.js'
34
+ import {
35
+ addAlias,
36
+ removeAlias,
37
+ displayAliases,
38
+ resolveAlias,
39
+ } from '../src/alias.js'
40
+ import {
41
+ addRemote,
42
+ removeRemote,
43
+ displayRemotes,
44
+ getRemote,
45
+ testRemoteConnection,
46
+ sshExec,
47
+ collectRemoteSysInfo,
48
+ setRemoteWorkDir,
49
+ getRemoteWorkDir,
50
+ generateBatchRemoteCommands,
51
+ executeBatchRemoteCommands,
52
+ } from '../src/remote.js'
53
+ import {
54
+ addRemoteHistory,
55
+ displayRemoteHistory,
56
+ clearRemoteHistory,
57
+ fetchRemoteShellHistory,
58
+ displayRemoteShellHistory,
59
+ clearRemoteShellHistory,
60
+ } from '../src/remote-history.js'
61
+ import {
62
+ detectRemoteShell,
63
+ getRemoteShellConfigPath,
64
+ installRemoteShellHook,
65
+ uninstallRemoteShellHook,
66
+ getRemoteHookStatus,
67
+ } from '../src/shell-hook.js'
68
+
69
+ // 获取主题颜色的辅助函数
70
+ function getThemeColors() {
71
+ const theme = getCurrentTheme()
72
+ return {
73
+ primary: theme.primary,
74
+ success: theme.success,
75
+ error: theme.error,
76
+ warning: theme.warning,
77
+ info: theme.info,
78
+ muted: theme.text.muted,
79
+ secondary: theme.text.secondary,
80
+ }
81
+ }
26
82
  import * as console2 from '../src/utils/console.js'
27
83
  // 导入 package.json(Bun 会自动打包进二进制)
28
- import packageJson from '../package.json'
84
+ import packageJson from '../package.json' with { type: 'json' }
29
85
 
30
86
  // 保留这些用于其他可能的用途
31
87
  const __filename = fileURLToPath(import.meta.url)
@@ -33,32 +89,70 @@ const __dirname = dirname(__filename)
33
89
 
34
90
  const program = new Command()
35
91
 
92
+
93
+ // 启动时异步检查更新(不阻塞主流程)
94
+ let updateCheckResult: { hasUpdate: boolean; latestVersion: string | null } | null = null
95
+ const isUpgradeCommand = process.argv.includes('upgrade')
96
+
97
+ // 延迟更新检查到命令解析后(减少启动时间)
98
+ // 非 upgrade 命令时才检查更新
99
+ if (!isUpgradeCommand) {
100
+ // 延迟 100ms 开始检查,避免影响简单命令的响应速度
101
+ setTimeout(() => {
102
+ checkForUpdates(packageJson.version)
103
+ .then((result) => {
104
+ updateCheckResult = result
105
+ })
106
+ .catch(() => {
107
+ // 静默失败
108
+ })
109
+ }, 100)
110
+ }
111
+
112
+ // 程序退出时显示更新提示
113
+ process.on('beforeExit', () => {
114
+ if (updateCheckResult?.hasUpdate && updateCheckResult.latestVersion && !isUpgradeCommand) {
115
+ showUpdateNotice(packageJson.version, updateCheckResult.latestVersion)
116
+ }
117
+ })
118
+
36
119
  /**
37
120
  * 执行命令(原生版本)
38
121
  */
39
- function executeCommand(command: string): Promise<{ exitCode: number; output: string }> {
122
+ function executeCommand(command: string): Promise<{ exitCode: number; output: string; stdout: string; stderr: string }> {
40
123
  return new Promise((resolve) => {
41
- let output = ''
124
+ let stdout = ''
125
+ let stderr = ''
42
126
  let hasOutput = false
43
127
 
44
128
  console.log('') // 空行
45
129
 
46
- // 计算命令框宽度,让分隔线长度一致
130
+ // 计算命令框宽度,让分隔线长度一致(限制终端宽度)
131
+ const termWidth = process.stdout.columns || 80
132
+ const maxContentWidth = termWidth - 6
47
133
  const lines = command.split('\n')
48
- const maxContentWidth = Math.max(...lines.map(l => console2.getDisplayWidth(l)))
49
- const boxWidth = Math.max(maxContentWidth + 4, console2.getDisplayWidth('生成命令') + 6, 20)
134
+ const wrappedLines: string[] = []
135
+ for (const line of lines) {
136
+ wrappedLines.push(...console2.wrapText(line, maxContentWidth))
137
+ }
138
+ const actualMaxWidth = Math.max(
139
+ ...wrappedLines.map((l) => console2.getDisplayWidth(l)),
140
+ console2.getDisplayWidth('生成命令')
141
+ )
142
+ const boxWidth = Math.min(actualMaxWidth + 4, termWidth - 2)
50
143
  console2.printSeparator('输出', boxWidth)
51
144
 
52
- const child = exec(command)
145
+ // 使用 bash 并启用 pipefail,确保管道中任何命令失败都能正确返回非零退出码
146
+ const child = exec(`set -o pipefail; ${command}`, { shell: '/bin/bash' })
53
147
 
54
148
  child.stdout?.on('data', (data) => {
55
- output += data
149
+ stdout += data
56
150
  hasOutput = true
57
151
  process.stdout.write(data)
58
152
  })
59
153
 
60
154
  child.stderr?.on('data', (data) => {
61
- output += data
155
+ stderr += data
62
156
  hasOutput = true
63
157
  process.stderr.write(data)
64
158
  })
@@ -67,7 +161,7 @@ function executeCommand(command: string): Promise<{ exitCode: number; output: st
67
161
  if (hasOutput) {
68
162
  console2.printSeparator('', boxWidth)
69
163
  }
70
- resolve({ exitCode: code || 0, output })
164
+ resolve({ exitCode: code || 0, output: stdout + stderr, stdout, stderr })
71
165
  })
72
166
 
73
167
  child.on('error', (err) => {
@@ -76,7 +170,7 @@ function executeCommand(command: string): Promise<{ exitCode: number; output: st
76
170
  }
77
171
  console2.error(err.message)
78
172
  console2.printSeparator('', boxWidth)
79
- resolve({ exitCode: 1, output: err.message })
173
+ resolve({ exitCode: 1, output: err.message, stdout: '', stderr: err.message })
80
174
  })
81
175
  })
82
176
  }
@@ -87,6 +181,7 @@ program
87
181
  .description('AI 驱动的命令行工具,将自然语言转换为可执行的 Shell 命令')
88
182
  .version(packageJson.version, '-v, --version', '显示版本号')
89
183
  .helpOption('-h, --help', '显示帮助信息')
184
+ .allowUnknownOption(true) // 允许未知选项(用于别名参数传递)
90
185
 
91
186
  // config 子命令
92
187
  const configCmd = program.command('config').description('管理配置')
@@ -102,23 +197,28 @@ configCmd
102
197
  console.log('')
103
198
  console2.title('当前配置:')
104
199
  console2.muted('━'.repeat(50))
105
- console.log(` ${chalk.hex('#00D9FF')('apiKey')}: ${maskApiKey(config.apiKey)}`)
106
- console.log(` ${chalk.hex('#00D9FF')('baseUrl')}: ${config.baseUrl}`)
107
- console.log(` ${chalk.hex('#00D9FF')('provider')}: ${config.provider}`)
108
- console.log(` ${chalk.hex('#00D9FF')('model')}: ${config.model}`)
200
+ console.log(` ${chalk.hex(getThemeColors().primary)('apiKey')}: ${maskApiKey(config.apiKey)}`)
201
+ console.log(` ${chalk.hex(getThemeColors().primary)('baseUrl')}: ${config.baseUrl}`)
202
+ console.log(` ${chalk.hex(getThemeColors().primary)('provider')}: ${config.provider}`)
203
+ console.log(` ${chalk.hex(getThemeColors().primary)('model')}: ${config.model}`)
204
+ console.log(
205
+ ` ${chalk.hex(getThemeColors().primary)('shellHook')}: ${
206
+ config.shellHook ? chalk.hex(getThemeColors().success)('已启用') : chalk.gray('未启用')
207
+ }`
208
+ )
109
209
  console.log(
110
- ` ${chalk.hex('#00D9FF')('shellHook')}: ${
111
- config.shellHook ? chalk.hex('#10B981')('已启用') : chalk.gray('未启用')
210
+ ` ${chalk.hex(getThemeColors().primary)('editMode')}: ${
211
+ config.editMode === 'auto' ? chalk.hex(getThemeColors().primary)('auto (自动编辑)') : chalk.gray('manual (按E编辑)')
112
212
  }`
113
213
  )
214
+ console.log(` ${chalk.hex(getThemeColors().primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
215
+ console.log(` ${chalk.hex(getThemeColors().primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
216
+ console.log(` ${chalk.hex(getThemeColors().primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
114
217
  console.log(
115
- ` ${chalk.hex('#00D9FF')('editMode')}: ${
116
- config.editMode === 'auto' ? chalk.hex('#00D9FF')('auto (自动编辑)') : chalk.gray('manual (按E编辑)')
218
+ ` ${chalk.hex(getThemeColors().primary)('theme')}: ${
219
+ config.theme === 'dark' ? chalk.hex(getThemeColors().primary)('dark (深色)') : chalk.hex(getThemeColors().primary)('light (浅色)')
117
220
  }`
118
221
  )
119
- console.log(` ${chalk.hex('#00D9FF')('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
120
- console.log(` ${chalk.hex('#00D9FF')('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
121
- console.log(` ${chalk.hex('#00D9FF')('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
122
222
  console2.muted('━'.repeat(50))
123
223
  console2.muted(`配置文件: ${CONFIG_FILE}`)
124
224
  console.log('')
@@ -127,11 +227,21 @@ configCmd
127
227
  configCmd
128
228
  .command('set <key> <value>')
129
229
  .description('设置配置项 (apiKey, baseUrl, provider, model, shellHook, chatHistoryLimit)')
130
- .action((key, value) => {
230
+ .action(async (key, value) => {
131
231
  try {
232
+ const oldConfig = getConfig()
233
+ const oldShellHistoryLimit = oldConfig.shellHistoryLimit
234
+
132
235
  setConfigValue(key, value)
133
236
  console.log('')
134
237
  console2.success(`已设置 ${key}`)
238
+
239
+ // 如果修改了 shellHistoryLimit,自动重装 hook
240
+ if (key === 'shellHistoryLimit') {
241
+ const { reinstallHookForLimitChange } = await import('../src/shell-hook.js')
242
+ await reinstallHookForLimitChange(oldShellHistoryLimit, Number(value))
243
+ }
244
+
135
245
  console.log('')
136
246
  } catch (error: any) {
137
247
  console.log('')
@@ -147,6 +257,270 @@ configCmd.action(async () => {
147
257
  await runConfigWizard()
148
258
  })
149
259
 
260
+ // theme 子命令
261
+ const themeCmd = program.command('theme').description('管理主题')
262
+
263
+ themeCmd
264
+ .command('list')
265
+ .description('查看所有可用主题')
266
+ .option('--custom', '只显示自定义主题')
267
+ .option('--builtin', '只显示内置主题')
268
+ .action(async (options: { custom?: boolean; builtin?: boolean }) => {
269
+ const { getAllThemeMetadata, isBuiltinTheme } = await import('../src/ui/theme.js')
270
+ const config = getConfig()
271
+ const currentTheme = config.theme || 'dark'
272
+
273
+ console.log('')
274
+ console2.title('🎨 可用主题:')
275
+ console2.muted('━'.repeat(50))
276
+
277
+ // 动态获取所有主题元数据
278
+ const allThemes = getAllThemeMetadata()
279
+
280
+ // 根据选项过滤主题
281
+ const builtinThemes = allThemes.filter((meta) => isBuiltinTheme(meta.name))
282
+ const customThemes = allThemes.filter((meta) => !isBuiltinTheme(meta.name))
283
+
284
+ // 显示内置主题
285
+ if (!options.custom) {
286
+ if (builtinThemes.length > 0) {
287
+ console.log('')
288
+ console2.info('内置主题:')
289
+ builtinThemes.forEach((meta) => {
290
+ const isCurrent = meta.name === currentTheme
291
+ const prefix = isCurrent ? '●' : '○'
292
+ const label = `${meta.name} (${meta.displayName})`
293
+
294
+ if (isCurrent) {
295
+ console.log(` ${chalk.hex(meta.previewColor)(prefix)} ${chalk.hex(meta.previewColor).bold(label)} ${chalk.gray('(当前)')}`)
296
+ } else {
297
+ console.log(` ${chalk.gray(prefix)} ${label}`)
298
+ }
299
+ })
300
+ }
301
+ }
302
+
303
+ // 显示自定义主题
304
+ if (!options.builtin) {
305
+ if (customThemes.length > 0) {
306
+ console.log('')
307
+ console2.info('自定义主题:')
308
+ customThemes.forEach((meta) => {
309
+ const isCurrent = meta.name === currentTheme
310
+ const prefix = isCurrent ? '●' : '○'
311
+ const label = `${meta.name} (${meta.displayName})`
312
+ const emoji = ' ✨'
313
+
314
+ if (isCurrent) {
315
+ console.log(` ${chalk.hex(meta.previewColor)(prefix)} ${chalk.hex(meta.previewColor).bold(label)}${emoji} ${chalk.gray('(当前)')}`)
316
+ } else {
317
+ console.log(` ${chalk.gray(prefix)} ${label}${emoji}`)
318
+ }
319
+ })
320
+ } else if (options.custom) {
321
+ console.log('')
322
+ console2.muted(' 还没有自定义主题')
323
+ console2.muted(' 使用 pls theme create <name> 创建')
324
+ }
325
+ }
326
+
327
+ console.log('')
328
+ console2.muted('━'.repeat(50))
329
+ console.log('')
330
+ })
331
+
332
+ themeCmd
333
+ .argument('[name]', '主题名称')
334
+ .description('切换主题')
335
+ .action(async (name?: string) => {
336
+ const { getThemeMetadata, getAllThemeMetadata, isValidTheme } = await import('../src/ui/theme.js')
337
+
338
+ if (!name) {
339
+ // 显示当前主题
340
+ const config = getConfig()
341
+ const currentTheme = config.theme || 'dark'
342
+ const meta = getThemeMetadata(currentTheme as any)
343
+
344
+ if (meta) {
345
+ console.log('')
346
+ console.log(`当前主题: ${chalk.hex(meta.previewColor).bold(`${meta.name} (${meta.displayName})`)}`)
347
+ if (meta.description) {
348
+ console2.muted(` ${meta.description}`)
349
+ }
350
+ console.log('')
351
+ }
352
+
353
+ console2.muted('使用 pls theme list 查看所有主题')
354
+ console2.muted('使用 pls theme <name> 切换主题')
355
+ console.log('')
356
+ return
357
+ }
358
+
359
+ // 切换主题
360
+ try {
361
+ // 验证主题是否存在
362
+ if (!isValidTheme(name)) {
363
+ const allThemes = getAllThemeMetadata()
364
+ const themeNames = allThemes.map((m) => m.name).join(', ')
365
+ throw new Error(`未知主题 "${name}",可用主题: ${themeNames}`)
366
+ }
367
+
368
+ setConfigValue('theme', name)
369
+ const meta = getThemeMetadata(name)
370
+
371
+ if (meta) {
372
+ console.log('')
373
+ console2.success(`已切换到 ${chalk.hex(meta.previewColor).bold(`${meta.name} (${meta.displayName})`)} 主题`)
374
+ if (meta.description) {
375
+ console2.muted(` ${meta.description}`)
376
+ }
377
+ console.log('')
378
+ }
379
+ } catch (error: any) {
380
+ console.log('')
381
+ console2.error(error.message)
382
+ console.log('')
383
+ process.exit(1)
384
+ }
385
+ })
386
+
387
+ // theme create - 创建主题模板
388
+ themeCmd
389
+ .command('create <name>')
390
+ .description('创建自定义主题模板')
391
+ .option('-d, --display-name <name>', '显示名称')
392
+ .option('-c, --category <type>', '主题类别 (dark 或 light)', 'dark')
393
+ .action(async (name: string, options: { displayName?: string; category?: string }) => {
394
+ const { createThemeTemplate } = await import('../src/ui/theme.js')
395
+
396
+ try {
397
+ // 验证主题名称格式
398
+ if (!/^[a-z0-9-]+$/.test(name)) {
399
+ throw new Error('主题名称只能包含小写字母、数字和连字符')
400
+ }
401
+
402
+ // 验证类别
403
+ const category = options.category as 'dark' | 'light'
404
+ if (category !== 'dark' && category !== 'light') {
405
+ throw new Error('主题类别必须是 dark 或 light')
406
+ }
407
+
408
+ // 创建主题目录
409
+ const themesDir = path.join(os.homedir(), '.please', 'themes')
410
+ if (!fs.existsSync(themesDir)) {
411
+ fs.mkdirSync(themesDir, { recursive: true })
412
+ }
413
+
414
+ // 检查主题文件是否已存在
415
+ const themePath = path.join(themesDir, `${name}.json`)
416
+ if (fs.existsSync(themePath)) {
417
+ throw new Error(`主题 "${name}" 已存在`)
418
+ }
419
+
420
+ // 创建主题模板
421
+ const displayName = options.displayName || name
422
+ const template = createThemeTemplate(name, displayName, category)
423
+
424
+ // 保存到文件
425
+ fs.writeFileSync(themePath, JSON.stringify(template, null, 2), 'utf-8')
426
+
427
+ // 显示成功信息
428
+ console.log('')
429
+ console2.success(`已创建主题模板: ${themePath}`)
430
+ console.log('')
431
+
432
+ console2.info('📝 下一步:')
433
+ console.log(` 1. 编辑主题文件修改颜色配置`)
434
+ console2.muted(` vim ${themePath}`)
435
+ console.log('')
436
+ console.log(` 2. 验证主题格式`)
437
+ console2.muted(` pls theme validate ${themePath}`)
438
+ console.log('')
439
+ console.log(` 3. 应用主题查看效果`)
440
+ console2.muted(` pls theme ${name}`)
441
+ console.log('')
442
+
443
+ console2.info('💡 提示:')
444
+ console2.muted(' - 使用在线工具选择颜色: https://colorhunt.co')
445
+ console2.muted(' - 参考内置主题: pls theme list')
446
+ console.log('')
447
+ } catch (error: any) {
448
+ console.log('')
449
+ console2.error(error.message)
450
+ console.log('')
451
+ process.exit(1)
452
+ }
453
+ })
454
+
455
+ // theme validate - 验证主题文件
456
+ themeCmd
457
+ .command('validate <file>')
458
+ .description('验证主题文件格式')
459
+ .action(async (file: string) => {
460
+ const { validateThemeWithDetails } = await import('../src/ui/theme.js')
461
+
462
+ try {
463
+ // 读取主题文件
464
+ const themePath = path.isAbsolute(file) ? file : path.join(process.cwd(), file)
465
+
466
+ if (!fs.existsSync(themePath)) {
467
+ throw new Error(`文件不存在: ${themePath}`)
468
+ }
469
+
470
+ const content = fs.readFileSync(themePath, 'utf-8')
471
+ const theme = JSON.parse(content)
472
+
473
+ // 验证主题
474
+ const result = validateThemeWithDetails(theme)
475
+
476
+ console.log('')
477
+
478
+ if (result.valid) {
479
+ console2.success('✓ 主题验证通过')
480
+ console.log('')
481
+
482
+ if (theme.metadata) {
483
+ console2.info('主题信息:')
484
+ console.log(` 名称: ${theme.metadata.name} (${theme.metadata.displayName})`)
485
+ console.log(` 类别: ${theme.metadata.category}`)
486
+ if (theme.metadata.description) {
487
+ console.log(` 描述: ${theme.metadata.description}`)
488
+ }
489
+ if (theme.metadata.author) {
490
+ console.log(` 作者: ${theme.metadata.author}`)
491
+ }
492
+ }
493
+
494
+ console.log('')
495
+ } else {
496
+ console2.error('✗ 主题验证失败')
497
+ console.log('')
498
+ console2.info('错误列表:')
499
+ result.errors.forEach((err, idx) => {
500
+ console.log(` ${idx + 1}. ${err}`)
501
+ })
502
+ console.log('')
503
+
504
+ console2.info('修复建议:')
505
+ console2.muted(` 1. 编辑主题文件: vim ${themePath}`)
506
+ console2.muted(' 2. 参考内置主题格式')
507
+ console2.muted(' 3. 确保所有颜色使用 #RRGGBB 格式')
508
+ console.log('')
509
+
510
+ process.exit(1)
511
+ }
512
+ } catch (error: any) {
513
+ console.log('')
514
+ if (error.message.includes('Unexpected token')) {
515
+ console2.error('JSON 格式错误,请检查文件语法')
516
+ } else {
517
+ console2.error(error.message)
518
+ }
519
+ console.log('')
520
+ process.exit(1)
521
+ }
522
+ })
523
+
150
524
  // history 子命令
151
525
  const historyCmd = program.command('history').description('查看或管理命令历史')
152
526
 
@@ -170,16 +544,16 @@ historyCmd
170
544
  history.forEach((item: any, index: number) => {
171
545
  const status = item.executed
172
546
  ? item.exitCode === 0
173
- ? chalk.hex('#10B981')('✓')
174
- : chalk.hex('#EF4444')(`✗ 退出码:${item.exitCode}`)
547
+ ? chalk.hex(getThemeColors().success)('✓')
548
+ : chalk.hex(getThemeColors().error)(`✗ 退出码:${item.exitCode}`)
175
549
  : chalk.gray('(未执行)')
176
550
 
177
- console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex('#00D9FF')(item.userPrompt)}`)
551
+ console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex(getThemeColors().primary)(item.userPrompt)}`)
178
552
 
179
553
  // 显示用户修改信息
180
554
  if (item.userModified && item.aiGeneratedCommand) {
181
555
  console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(item.aiGeneratedCommand)}`)
182
- console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.yellow('(已修改)')}`)
556
+ console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.hex(getThemeColors().warning)('(已修改)')}`)
183
557
  } else {
184
558
  console.log(` ${chalk.dim('→')} ${item.command} ${status}`)
185
559
  }
@@ -251,16 +625,16 @@ historyCmd.action(() => {
251
625
  history.forEach((item: any, index: number) => {
252
626
  const status = item.executed
253
627
  ? item.exitCode === 0
254
- ? chalk.hex('#10B981')('✓')
255
- : chalk.hex('#EF4444')(`✗ 退出码:${item.exitCode}`)
628
+ ? chalk.hex(getThemeColors().success)('✓')
629
+ : chalk.hex(getThemeColors().error)(`✗ 退出码:${item.exitCode}`)
256
630
  : chalk.gray('(未执行)')
257
631
 
258
- console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex('#00D9FF')(item.userPrompt)}`)
632
+ console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex(getThemeColors().primary)(item.userPrompt)}`)
259
633
 
260
634
  // 显示用户修改信息
261
635
  if (item.userModified && item.aiGeneratedCommand) {
262
636
  console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(item.aiGeneratedCommand)}`)
263
- console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.yellow('(已修改)')}`)
637
+ console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.hex(getThemeColors().warning)('(已修改)')}`)
264
638
  } else {
265
639
  console.log(` ${chalk.dim('→')} ${item.command} ${status}`)
266
640
  }
@@ -329,19 +703,19 @@ hookCmd
329
703
  console.log('')
330
704
  console2.title('📊 Shell Hook 状态')
331
705
  console2.muted('━'.repeat(40))
332
- console.log(` ${chalk.hex('#00D9FF')('Shell 类型')}: ${status.shellType}`)
333
- console.log(` ${chalk.hex('#00D9FF')('配置文件')}: ${status.configPath || '未知'}`)
706
+ console.log(` ${chalk.hex(getThemeColors().primary)('Shell 类型')}: ${status.shellType}`)
707
+ console.log(` ${chalk.hex(getThemeColors().primary)('配置文件')}: ${status.configPath || '未知'}`)
334
708
  console.log(
335
- ` ${chalk.hex('#00D9FF')('已安装')}: ${
336
- status.installed ? chalk.hex('#10B981')('是') : chalk.gray('否')
709
+ ` ${chalk.hex(getThemeColors().primary)('已安装')}: ${
710
+ status.installed ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
337
711
  }`
338
712
  )
339
713
  console.log(
340
- ` ${chalk.hex('#00D9FF')('已启用')}: ${
341
- status.enabled ? chalk.hex('#10B981')('是') : chalk.gray('否')
714
+ ` ${chalk.hex(getThemeColors().primary)('已启用')}: ${
715
+ status.enabled ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
342
716
  }`
343
717
  )
344
- console.log(` ${chalk.hex('#00D9FF')('历史文件')}: ${status.historyFile}`)
718
+ console.log(` ${chalk.hex(getThemeColors().primary)('历史文件')}: ${status.historyFile}`)
345
719
  console2.muted('━'.repeat(40))
346
720
 
347
721
  if (!status.installed) {
@@ -358,19 +732,19 @@ hookCmd.action(() => {
358
732
  console.log('')
359
733
  console2.title('📊 Shell Hook 状态')
360
734
  console2.muted('━'.repeat(40))
361
- console.log(` ${chalk.hex('#00D9FF')('Shell 类型')}: ${status.shellType}`)
362
- console.log(` ${chalk.hex('#00D9FF')('配置文件')}: ${status.configPath || '未知'}`)
735
+ console.log(` ${chalk.hex(getThemeColors().primary)('Shell 类型')}: ${status.shellType}`)
736
+ console.log(` ${chalk.hex(getThemeColors().primary)('配置文件')}: ${status.configPath || '未知'}`)
363
737
  console.log(
364
- ` ${chalk.hex('#00D9FF')('已安装')}: ${
365
- status.installed ? chalk.hex('#10B981')('是') : chalk.gray('否')
738
+ ` ${chalk.hex(getThemeColors().primary)('已安装')}: ${
739
+ status.installed ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
366
740
  }`
367
741
  )
368
742
  console.log(
369
- ` ${chalk.hex('#00D9FF')('已启用')}: ${
370
- status.enabled ? chalk.hex('#10B981')('是') : chalk.gray('否')
743
+ ` ${chalk.hex(getThemeColors().primary)('已启用')}: ${
744
+ status.enabled ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
371
745
  }`
372
746
  )
373
- console.log(` ${chalk.hex('#00D9FF')('历史文件')}: ${status.historyFile}`)
747
+ console.log(` ${chalk.hex(getThemeColors().primary)('历史文件')}: ${status.historyFile}`)
374
748
  console2.muted('━'.repeat(40))
375
749
 
376
750
  if (!status.installed) {
@@ -380,24 +754,440 @@ hookCmd.action(() => {
380
754
  console.log('')
381
755
  })
382
756
 
383
- // chat 子命令
384
- const chatCmd = program.command('chat').description('AI 对话模式,问答、讲解命令')
757
+ // upgrade 子命令
758
+ program
759
+ .command('upgrade')
760
+ .description('升级到最新版本')
761
+ .action(async () => {
762
+ const success = await performUpgrade(packageJson.version)
763
+ process.exit(success ? 0 : 1)
764
+ })
385
765
 
386
- chatCmd
387
- .command('clear')
388
- .description('清空对话历史')
766
+ // alias 子命令
767
+ const aliasCmd = program.command('alias').description('管理命令别名')
768
+
769
+ // 获取所有子命令名称(用于检测冲突)
770
+ function getReservedCommands(): string[] {
771
+ return program.commands.map((cmd) => cmd.name())
772
+ }
773
+
774
+ aliasCmd
775
+ .command('list')
776
+ .description('列出所有别名')
389
777
  .action(() => {
390
- clearChatHistory()
778
+ displayAliases()
779
+ })
780
+
781
+ aliasCmd
782
+ .command('add <name> <prompt>')
783
+ .description('添加别名(prompt 支持 {{param}} 或 {{param:default}} 参数模板)')
784
+ .option('-d, --description <desc>', '别名描述')
785
+ .action((name, prompt, options) => {
786
+ try {
787
+ addAlias(name, prompt, options.description, getReservedCommands())
788
+ console.log('')
789
+ console2.success(`已添加别名: ${name}`)
790
+ console.log(` ${chalk.gray('→')} ${prompt}`)
791
+ console.log('')
792
+ } catch (error: any) {
793
+ console.log('')
794
+ console2.error(error.message)
795
+ console.log('')
796
+ process.exit(1)
797
+ }
798
+ })
799
+
800
+ aliasCmd
801
+ .command('remove <name>')
802
+ .description('删除别名')
803
+ .action((name) => {
804
+ const removed = removeAlias(name)
391
805
  console.log('')
392
- console2.success('对话历史已清空')
806
+ if (removed) {
807
+ console2.success(`已删除别名: ${name}`)
808
+ } else {
809
+ console2.error(`别名不存在: ${name}`)
810
+ }
393
811
  console.log('')
394
812
  })
395
813
 
396
- // 默认 chat 命令(进行对话)
397
- chatCmd
398
- .argument('[prompt...]', '你的问题')
814
+ // 默认 alias 命令(显示列表)
815
+ aliasCmd.action(() => {
816
+ displayAliases()
817
+ })
818
+
819
+ // remote 子命令
820
+ const remoteCmd = program.command('remote').description('管理远程服务器')
821
+
822
+ remoteCmd
823
+ .command('list')
824
+ .description('列出所有远程服务器')
825
+ .action(() => {
826
+ displayRemotes()
827
+ })
828
+
829
+ remoteCmd
830
+ .command('add <name> <host>')
831
+ .description('添加远程服务器(格式: user@host 或 user@host:port)')
832
+ .option('-k, --key <path>', 'SSH 私钥路径')
833
+ .option('-p, --password', '使用密码认证(每次执行时输入)')
834
+ .action((name, host, options) => {
835
+ try {
836
+ addRemote(name, host, { key: options.key, password: options.password })
837
+ console.log('')
838
+ console2.success(`已添加远程服务器: ${name}`)
839
+ console.log(` ${chalk.gray('→')} ${host}`)
840
+ if (options.key) {
841
+ console.log(` ${chalk.gray('密钥:')} ${options.key}`)
842
+ }
843
+ if (options.password) {
844
+ console.log(` ${chalk.gray('认证:')} 密码(每次执行时输入)`)
845
+ }
846
+ console.log('')
847
+ } catch (error: any) {
848
+ console.log('')
849
+ console2.error(error.message)
850
+ console.log('')
851
+ process.exit(1)
852
+ }
853
+ })
854
+
855
+ remoteCmd
856
+ .command('remove <name>')
857
+ .description('删除远程服务器')
858
+ .action((name) => {
859
+ const removed = removeRemote(name)
860
+ console.log('')
861
+ if (removed) {
862
+ console2.success(`已删除远程服务器: ${name}`)
863
+ } else {
864
+ console2.error(`远程服务器不存在: ${name}`)
865
+ }
866
+ console.log('')
867
+ })
868
+
869
+ remoteCmd
870
+ .command('test <name>')
871
+ .description('测试远程服务器连接')
872
+ .action(async (name) => {
873
+ const remote = getRemote(name)
874
+ if (!remote) {
875
+ console.log('')
876
+ console2.error(`远程服务器不存在: ${name}`)
877
+ console.log('')
878
+ process.exit(1)
879
+ }
880
+
881
+ console.log('')
882
+ console2.info(`正在测试连接 ${name} (${remote.user}@${remote.host}:${remote.port})...`)
883
+
884
+ const result = await testRemoteConnection(name)
885
+ console.log(` ${result.message}`)
886
+
887
+ if (result.success) {
888
+ // 采集系统信息
889
+ console2.info('正在采集系统信息...')
890
+ try {
891
+ const sysInfo = await collectRemoteSysInfo(name, true)
892
+ console.log(` ${chalk.gray('系统:')} ${sysInfo.os} ${sysInfo.osVersion}`)
893
+ console.log(` ${chalk.gray('Shell:')} ${sysInfo.shell}`)
894
+ console.log(` ${chalk.gray('主机名:')} ${sysInfo.hostname}`)
895
+ } catch (error: any) {
896
+ console2.warning(`无法采集系统信息: ${error.message}`)
897
+ }
898
+ }
899
+ console.log('')
900
+ })
901
+
902
+ // remote hook 子命令
903
+ const remoteHookCmd = remoteCmd.command('hook').description('管理远程服务器 Shell Hook')
904
+
905
+ remoteHookCmd
906
+ .command('install <name>')
907
+ .description('在远程服务器安装 Shell Hook')
908
+ .action(async (name) => {
909
+ const remote = getRemote(name)
910
+ if (!remote) {
911
+ console.log('')
912
+ console2.error(`远程服务器不存在: ${name}`)
913
+ console.log('')
914
+ process.exit(1)
915
+ }
916
+
917
+ console.log('')
918
+ console2.title('🔧 远程 Shell Hook 安装')
919
+ console2.muted('━'.repeat(40))
920
+ console2.info(`目标服务器: ${name} (${remote.user}@${remote.host})`)
921
+
922
+ try {
923
+ // 检测远程 shell 类型
924
+ const sshExecFn = async (cmd: string) => {
925
+ const result = await sshExec(name, cmd, { timeout: 30000 })
926
+ return { stdout: result.stdout, exitCode: result.exitCode }
927
+ }
928
+
929
+ const shellType = await detectRemoteShell(sshExecFn)
930
+ const configPath = getRemoteShellConfigPath(shellType)
931
+ console2.muted(`检测到 Shell: ${shellType}`)
932
+ console2.muted(`配置文件: ${configPath}`)
933
+ console.log('')
934
+
935
+ const result = await installRemoteShellHook(sshExecFn, shellType)
936
+ console.log(` ${result.message}`)
937
+
938
+ if (result.success) {
939
+ console.log('')
940
+ console2.warning('⚠️ 请在远程服务器重启终端或执行:')
941
+ console2.info(` source ${configPath}`)
942
+ }
943
+ } catch (error: any) {
944
+ console2.error(`安装失败: ${error.message}`)
945
+ }
946
+ console.log('')
947
+ })
948
+
949
+ remoteHookCmd
950
+ .command('uninstall <name>')
951
+ .description('从远程服务器卸载 Shell Hook')
952
+ .action(async (name) => {
953
+ const remote = getRemote(name)
954
+ if (!remote) {
955
+ console.log('')
956
+ console2.error(`远程服务器不存在: ${name}`)
957
+ console.log('')
958
+ process.exit(1)
959
+ }
960
+
961
+ console.log('')
962
+ console2.info(`正在从 ${name} 卸载 Shell Hook...`)
963
+
964
+ try {
965
+ const sshExecFn = async (cmd: string) => {
966
+ const result = await sshExec(name, cmd, { timeout: 30000 })
967
+ return { stdout: result.stdout, exitCode: result.exitCode }
968
+ }
969
+
970
+ const shellType = await detectRemoteShell(sshExecFn)
971
+ const result = await uninstallRemoteShellHook(sshExecFn, shellType)
972
+ console.log(` ${result.message}`)
973
+
974
+ if (result.success) {
975
+ console.log('')
976
+ console2.warning('⚠️ 请在远程服务器重启终端使其生效')
977
+ }
978
+ } catch (error: any) {
979
+ console2.error(`卸载失败: ${error.message}`)
980
+ }
981
+ console.log('')
982
+ })
983
+
984
+ remoteHookCmd
985
+ .command('status <name>')
986
+ .description('查看远程服务器 Shell Hook 状态')
987
+ .action(async (name) => {
988
+ const remote = getRemote(name)
989
+ if (!remote) {
990
+ console.log('')
991
+ console2.error(`远程服务器不存在: ${name}`)
992
+ console.log('')
993
+ process.exit(1)
994
+ }
995
+
996
+ console.log('')
997
+ console2.info(`正在检查 ${name} 的 Hook 状态...`)
998
+
999
+ try {
1000
+ const sshExecFn = async (cmd: string) => {
1001
+ const result = await sshExec(name, cmd, { timeout: 30000 })
1002
+ return { stdout: result.stdout, exitCode: result.exitCode }
1003
+ }
1004
+
1005
+ const status = await getRemoteHookStatus(sshExecFn)
1006
+
1007
+ console.log('')
1008
+ console2.title(`📊 远程 Shell Hook 状态 - ${name}`)
1009
+ console2.muted('━'.repeat(40))
1010
+ console.log(` ${chalk.hex(getThemeColors().primary)('Shell 类型')}: ${status.shellType}`)
1011
+ console.log(` ${chalk.hex(getThemeColors().primary)('配置文件')}: ${status.configPath}`)
1012
+ console.log(
1013
+ ` ${chalk.hex(getThemeColors().primary)('已安装')}: ${
1014
+ status.installed ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
1015
+ }`
1016
+ )
1017
+ console2.muted('━'.repeat(40))
1018
+
1019
+ if (!status.installed) {
1020
+ console.log('')
1021
+ console2.muted(`提示: 运行 pls remote hook install ${name} 安装 Shell Hook`)
1022
+ }
1023
+ } catch (error: any) {
1024
+ console2.error(`检查失败: ${error.message}`)
1025
+ }
1026
+ console.log('')
1027
+ })
1028
+
1029
+ // remote history 子命令
1030
+ const remoteHistoryCmd = remoteCmd.command('history').description('管理远程服务器历史记录')
1031
+
1032
+ remoteHistoryCmd
1033
+ .command('show <name>')
1034
+ .description('显示远程服务器命令历史')
1035
+ .action((name) => {
1036
+ displayRemoteHistory(name)
1037
+ })
1038
+
1039
+ remoteHistoryCmd
1040
+ .command('clear <name>')
1041
+ .description('清空远程服务器命令历史')
1042
+ .action((name) => {
1043
+ clearRemoteHistory(name)
1044
+ console.log('')
1045
+ console2.success(`已清空服务器 "${name}" 的命令历史`)
1046
+ console.log('')
1047
+ })
1048
+
1049
+ remoteHistoryCmd
1050
+ .command('shell <name>')
1051
+ .description('显示远程服务器 Shell 历史')
1052
+ .action(async (name) => {
1053
+ await displayRemoteShellHistory(name)
1054
+ })
1055
+
1056
+ remoteHistoryCmd
1057
+ .command('shell-clear <name>')
1058
+ .description('清空远程服务器 Shell 历史')
1059
+ .action(async (name) => {
1060
+ await clearRemoteShellHistory(name)
1061
+ })
1062
+
1063
+ // remote default 子命令
1064
+ remoteCmd
1065
+ .command('default [name]')
1066
+ .description('设置或查看默认远程服务器')
1067
+ .option('-c, --clear', '清除默认服务器设置')
1068
+ .action((name?: string, options?: { clear?: boolean }) => {
1069
+ const config = getConfig()
1070
+
1071
+ // 清除默认
1072
+ if (options?.clear) {
1073
+ if (config.defaultRemote) {
1074
+ setConfigValue('defaultRemote', '')
1075
+ console.log('')
1076
+ console2.success('已清除默认远程服务器')
1077
+ console.log('')
1078
+ } else {
1079
+ console.log('')
1080
+ console2.muted('当前没有设置默认远程服务器')
1081
+ console.log('')
1082
+ }
1083
+ return
1084
+ }
1085
+
1086
+ // 查看默认
1087
+ if (!name) {
1088
+ console.log('')
1089
+ if (config.defaultRemote) {
1090
+ const remote = getRemote(config.defaultRemote)
1091
+ if (remote) {
1092
+ console.log(`默认远程服务器: ${chalk.hex(getThemeColors().primary)(config.defaultRemote)}`)
1093
+ console.log(` ${chalk.gray('→')} ${remote.user}@${remote.host}:${remote.port}`)
1094
+ } else {
1095
+ console2.warning(`默认服务器 "${config.defaultRemote}" 不存在,建议清除设置`)
1096
+ console2.muted('运行 pls remote default --clear 清除')
1097
+ }
1098
+ } else {
1099
+ console2.muted('当前没有设置默认远程服务器')
1100
+ console2.muted('使用 pls remote default <name> 设置默认服务器')
1101
+ }
1102
+ console.log('')
1103
+ return
1104
+ }
1105
+
1106
+ // 设置默认
1107
+ const remote = getRemote(name)
1108
+ if (!remote) {
1109
+ console.log('')
1110
+ console2.error(`远程服务器不存在: ${name}`)
1111
+ console2.muted('使用 pls remote list 查看所有服务器')
1112
+ console.log('')
1113
+ process.exit(1)
1114
+ }
1115
+
1116
+ setConfigValue('defaultRemote', name)
1117
+ console.log('')
1118
+ console2.success(`已设置默认远程服务器: ${name}`)
1119
+ console.log(` ${chalk.gray('→')} ${remote.user}@${remote.host}:${remote.port}`)
1120
+ console2.muted('现在可以使用 pls -r <prompt> 直接在该服务器执行')
1121
+ console.log('')
1122
+ })
1123
+
1124
+ // remote workdir 子命令
1125
+ remoteCmd
1126
+ .command('workdir <name> [path]')
1127
+ .description('设置或查看远程服务器的工作目录')
1128
+ .option('-c, --clear', '清除工作目录设置')
1129
+ .action((name: string, workdirPath?: string, options?: { clear?: boolean }) => {
1130
+ const remote = getRemote(name)
1131
+ if (!remote) {
1132
+ console.log('')
1133
+ console2.error(`远程服务器不存在: ${name}`)
1134
+ console.log('')
1135
+ process.exit(1)
1136
+ }
1137
+
1138
+ // 清除工作目录
1139
+ if (options?.clear) {
1140
+ if (remote.workDir) {
1141
+ setRemoteWorkDir(name, '-')
1142
+ console.log('')
1143
+ console2.success(`已清除 ${name} 的工作目录设置`)
1144
+ console.log('')
1145
+ } else {
1146
+ console.log('')
1147
+ console2.muted(`${name} 没有设置工作目录`)
1148
+ console.log('')
1149
+ }
1150
+ return
1151
+ }
1152
+
1153
+ // 查看工作目录
1154
+ if (!workdirPath) {
1155
+ console.log('')
1156
+ if (remote.workDir) {
1157
+ console.log(`${chalk.hex(getThemeColors().primary)(name)} 的工作目录:`)
1158
+ console.log(` ${chalk.gray('→')} ${remote.workDir}`)
1159
+ } else {
1160
+ console2.muted(`${name} 没有设置工作目录`)
1161
+ console2.muted(`使用 pls remote workdir ${name} <path> 设置工作目录`)
1162
+ }
1163
+ console.log('')
1164
+ return
1165
+ }
1166
+
1167
+ // 设置工作目录
1168
+ setRemoteWorkDir(name, workdirPath)
1169
+ console.log('')
1170
+ console2.success(`已设置 ${name} 的工作目录: ${workdirPath}`)
1171
+ console2.muted('现在在该服务器执行的命令会自动切换到此目录')
1172
+ console.log('')
1173
+ })
1174
+
1175
+ // 默认 remote 命令(显示列表)
1176
+ remoteCmd.action(() => {
1177
+ displayRemotes()
1178
+ })
1179
+
1180
+ // chat 命令(AI 对话)
1181
+ program
1182
+ .command('chat')
1183
+ .description('AI 对话模式,问答、讲解命令')
1184
+ .argument('[prompt...]', '你的问题(不提供则显示状态)')
399
1185
  .option('-d, --debug', '显示调试信息')
400
1186
  .action((promptArgs, options) => {
1187
+ // Workaround: Commander.js 14.x 的子命令 option 解析有 bug
1188
+ // 直接从 process.argv 检查 --debug
1189
+ const debug = process.argv.includes('--debug') || process.argv.includes('-d')
1190
+
401
1191
  const prompt = promptArgs.join(' ')
402
1192
 
403
1193
  if (!prompt.trim()) {
@@ -408,13 +1198,13 @@ chatCmd
408
1198
  console.log('')
409
1199
  console2.title('💬 AI 对话模式')
410
1200
  console2.muted('━'.repeat(40))
411
- console.log(` ${chalk.hex('#00D9FF')('当前对话轮数')}: ${roundCount}`)
412
- console.log(` ${chalk.hex('#00D9FF')('历史文件')}: ${historyFile}`)
1201
+ console.log(` ${chalk.hex(getThemeColors().primary)('当前对话轮数')}: ${roundCount}`)
1202
+ console.log(` ${chalk.hex(getThemeColors().primary)('历史文件')}: ${historyFile}`)
413
1203
  console2.muted('━'.repeat(40))
414
1204
  console.log('')
415
1205
  console2.muted('用法:')
416
- console2.info(' pls chat <问题> 与 AI 对话')
417
- console2.info(' pls chat clear 清空对话历史')
1206
+ console2.info(' pls chat <问题> 与 AI 对话')
1207
+ console2.info(' pls history chat clear 清空对话历史')
418
1208
  console.log('')
419
1209
  return
420
1210
  }
@@ -428,28 +1218,42 @@ chatCmd
428
1218
  process.exit(1)
429
1219
  }
430
1220
 
431
- // 使用 Ink 渲染对话(Chat 适合用 Ink 流式输出)
432
- render(
433
- <Chat
434
- prompt={prompt}
435
- debug={options.debug}
436
- showRoundCount={true}
437
- onComplete={() => process.exit(0)}
438
- />
439
- )
1221
+ // 懒加载 Chat 组件(避免启动时加载 React/Ink
1222
+ ;(async () => {
1223
+ const React = await import('react')
1224
+ const { render } = await import('ink')
1225
+ const { Chat } = await import('../src/components/Chat.js')
1226
+
1227
+ render(
1228
+ React.createElement(Chat, {
1229
+ prompt,
1230
+ debug: debug, // 使用 debug 变量
1231
+ showRoundCount: true,
1232
+ onComplete: () => process.exit(0),
1233
+ })
1234
+ )
1235
+ })()
440
1236
  })
441
1237
 
442
1238
  // 默认命令(执行 prompt)
443
1239
  program
444
1240
  .argument('[prompt...]', '自然语言描述你想执行的操作')
445
1241
  .option('-d, --debug', '显示调试信息(系统信息、完整 prompt 等)')
1242
+ .option('-r, --remote [name]', '在远程服务器上执行(不指定则使用默认服务器)')
446
1243
  .action((promptArgs, options) => {
1244
+ // 智能处理 -r 参数:如果 -r 后面的值不是已注册的服务器名,把它当作 prompt 的一部分
1245
+ if (typeof options.remote === 'string' && !getRemote(options.remote)) {
1246
+ // "查看当前目录" 不是服务器名,放回 prompt
1247
+ promptArgs.unshift(options.remote)
1248
+ options.remote = true // 改为使用默认服务器
1249
+ }
1250
+
447
1251
  if (promptArgs.length === 0) {
448
1252
  program.help()
449
1253
  return
450
1254
  }
451
1255
 
452
- const prompt = promptArgs.join(' ')
1256
+ let prompt = promptArgs.join(' ')
453
1257
 
454
1258
  if (!prompt.trim()) {
455
1259
  console.log('')
@@ -459,6 +1263,23 @@ program
459
1263
  process.exit(1)
460
1264
  }
461
1265
 
1266
+ // 尝试解析别名(支持 pls disk 和 pls @disk 两种格式)
1267
+ try {
1268
+ const aliasResult = resolveAlias(prompt)
1269
+ if (aliasResult.resolved) {
1270
+ prompt = aliasResult.prompt
1271
+ if (options.debug) {
1272
+ console.log('')
1273
+ console2.muted(`别名解析: ${aliasResult.aliasName} → ${prompt}`)
1274
+ }
1275
+ }
1276
+ } catch (error: any) {
1277
+ console.log('')
1278
+ console2.error(error.message)
1279
+ console.log('')
1280
+ process.exit(1)
1281
+ }
1282
+
462
1283
  // 检查配置
463
1284
  if (!isConfigValid()) {
464
1285
  console.log('')
@@ -468,8 +1289,190 @@ program
468
1289
  process.exit(1)
469
1290
  }
470
1291
 
471
- // 使用多步骤命令生成器(统一处理单步和多步)
1292
+ // 解析远程服务器名称
1293
+ // options.remote 可能是:
1294
+ // - undefined: 没有使用 -r
1295
+ // - true: 使用了 -r 但没有指定名称(使用默认)
1296
+ // - string: 使用了 -r 并指定了名称(支持逗号分隔的多个服务器)
1297
+ let remoteName: string | undefined
1298
+ let remoteNames: string[] | undefined // 批量执行时的服务器列表
1299
+ if (options.remote !== undefined) {
1300
+ if (options.remote === true) {
1301
+ // 使用默认服务器
1302
+ const config = getConfig()
1303
+ if (!config.defaultRemote) {
1304
+ console.log('')
1305
+ console2.error('未设置默认远程服务器')
1306
+ console2.muted('使用 pls remote default <name> 设置默认服务器')
1307
+ console2.muted('或使用 pls -r <name> <prompt> 指定服务器')
1308
+ console.log('')
1309
+ process.exit(1)
1310
+ }
1311
+ remoteName = config.defaultRemote
1312
+ } else {
1313
+ // 检查是否为批量执行(逗号分隔的服务器名)
1314
+ if (options.remote.includes(',')) {
1315
+ remoteNames = options.remote.split(',').map(s => s.trim()).filter(s => s.length > 0)
1316
+
1317
+ // 验证所有服务器是否存在
1318
+ const invalidServers = remoteNames!.filter(name => !getRemote(name))
1319
+ if (invalidServers.length > 0) {
1320
+ console.log('')
1321
+ console2.error(`以下服务器不存在: ${invalidServers.join(', ')}`)
1322
+ console2.muted('使用 pls remote list 查看所有服务器')
1323
+ console2.muted('使用 pls remote add <name> <user@host> 添加服务器')
1324
+ console.log('')
1325
+ process.exit(1)
1326
+ }
1327
+ } else {
1328
+ remoteName = options.remote
1329
+
1330
+ // 检查服务器是否存在
1331
+ const remote = getRemote(remoteName!)
1332
+ if (!remote) {
1333
+ console.log('')
1334
+ console2.error(`远程服务器不存在: ${remoteName}`)
1335
+ console2.muted('使用 pls remote add <name> <user@host> 添加服务器')
1336
+ console.log('')
1337
+ process.exit(1)
1338
+ }
1339
+ }
1340
+ }
1341
+ }
1342
+
1343
+ // 懒加载 MultiStepCommandGenerator 组件(避免启动时加载 React/Ink)
472
1344
  ;(async () => {
1345
+ // 批量远程执行模式
1346
+ if (remoteNames && remoteNames.length > 0) {
1347
+ console.log('')
1348
+ console2.info(`正在为 ${remoteNames.length} 台服务器生成命令...`)
1349
+ console.log('')
1350
+
1351
+ try {
1352
+ // 1. 并发生成命令
1353
+ const commands = await generateBatchRemoteCommands(remoteNames, prompt, { debug: options.debug })
1354
+
1355
+ // 2. 显示生成的命令
1356
+ console2.success('✓ 命令生成完成\n')
1357
+ const theme = getCurrentTheme()
1358
+ commands.forEach(({ server, command, sysInfo }) => {
1359
+ console.log(chalk.hex(theme.primary)(`${server}`) + chalk.gray(` (${sysInfo.os}):`))
1360
+ console.log(chalk.hex(theme.secondary)(` ${command}`))
1361
+ })
1362
+ console.log('')
1363
+
1364
+ // 3. 询问用户确认
1365
+ const readline = await import('readline')
1366
+ const rl = readline.createInterface({
1367
+ input: process.stdin,
1368
+ output: process.stdout,
1369
+ })
1370
+
1371
+ const confirmed = await new Promise<boolean>((resolve) => {
1372
+ console.log(chalk.gray(`将在 ${remoteNames!.length} 台服务器执行以上命令`))
1373
+ rl.question(chalk.gray('执行? [回车执行 / Ctrl+C 取消] '), (answer) => {
1374
+ rl.close()
1375
+ resolve(true)
1376
+ })
1377
+ })
1378
+
1379
+ if (!confirmed) {
1380
+ console.log('')
1381
+ console2.muted('已取消执行')
1382
+ console.log('')
1383
+ process.exit(0)
1384
+ }
1385
+
1386
+ // 4. 并发执行
1387
+ console.log('')
1388
+ console2.info('正在执行...')
1389
+ const results = await executeBatchRemoteCommands(commands)
1390
+
1391
+ // 5. 显示执行结果摘要
1392
+ console.log('')
1393
+ console2.info('执行完成:\n')
1394
+ results.forEach(({ server, exitCode }) => {
1395
+ const icon = exitCode === 0 ? '✓' : '✗'
1396
+ const color = exitCode === 0 ? theme.success : theme.error
1397
+ console.log(` ${chalk.hex(color)(icon)} ${server} ${chalk.gray(`(退出码: ${exitCode})`)}`)
1398
+ })
1399
+
1400
+ // 6. 显示每个服务器的详细输出
1401
+ console.log('')
1402
+ results.forEach(({ server, output }) => {
1403
+ console.log(chalk.hex(theme.primary)(`─── ${server} ───`))
1404
+ console.log(output || chalk.gray('(无输出)'))
1405
+ })
1406
+
1407
+ // 7. 记录到历史
1408
+ results.forEach(({ server, command, exitCode, output }) => {
1409
+ addRemoteHistory(server, {
1410
+ userPrompt: prompt,
1411
+ command,
1412
+ aiGeneratedCommand: command, // 批量执行无编辑功能
1413
+ userModified: false,
1414
+ executed: true,
1415
+ exitCode,
1416
+ output,
1417
+ })
1418
+ })
1419
+
1420
+ // 8. 根据结果决定退出码
1421
+ const allSuccess = results.every(r => r.exitCode === 0)
1422
+ const allFailed = results.every(r => r.exitCode !== 0)
1423
+ if (allFailed) {
1424
+ process.exit(2) // 全部失败
1425
+ } else if (!allSuccess) {
1426
+ process.exit(1) // 部分失败
1427
+ }
1428
+ process.exit(0) // 全部成功
1429
+ } catch (error: any) {
1430
+ console.log('')
1431
+ console2.error(`批量执行失败: ${error.message}`)
1432
+ console.log('')
1433
+ process.exit(1)
1434
+ }
1435
+ return
1436
+ }
1437
+
1438
+ // 单服务器执行模式
1439
+ const React = await import('react')
1440
+ const { render } = await import('ink')
1441
+ const { MultiStepCommandGenerator } = await import('../src/components/MultiStepCommandGenerator.js')
1442
+
1443
+ // 如果是远程模式,先获取远程上下文
1444
+ let remoteContext: {
1445
+ name: string
1446
+ sysInfo: Awaited<ReturnType<typeof collectRemoteSysInfo>>
1447
+ shellHistory: Awaited<ReturnType<typeof fetchRemoteShellHistory>>
1448
+ } | null = null
1449
+
1450
+ if (remoteName) {
1451
+ console.log('')
1452
+ console2.info(`正在连接远程服务器 ${remoteName}...`)
1453
+
1454
+ try {
1455
+ // 采集系统信息(使用缓存)
1456
+ const sysInfo = await collectRemoteSysInfo(remoteName)
1457
+ if (options.debug) {
1458
+ console2.muted(`系统: ${sysInfo.os} ${sysInfo.osVersion} (${sysInfo.shell})`)
1459
+ }
1460
+
1461
+ // 获取远程 shell 历史
1462
+ const shellHistory = await fetchRemoteShellHistory(remoteName)
1463
+ if (options.debug && shellHistory.length > 0) {
1464
+ console2.muted(`Shell 历史: ${shellHistory.length} 条`)
1465
+ }
1466
+
1467
+ remoteContext = { name: remoteName, sysInfo, shellHistory }
1468
+ console2.success(`已连接到 ${remoteName}`)
1469
+ } catch (error: any) {
1470
+ console2.error(`无法连接到 ${remoteName}: ${error.message}`)
1471
+ console.log('')
1472
+ process.exit(1)
1473
+ }
1474
+ }
1475
+
473
1476
  const executedSteps: ExecutedStep[] = []
474
1477
  let currentStepNumber = 1
475
1478
  let lastStepFailed = false // 跟踪上一步是否失败
@@ -479,16 +1482,22 @@ program
479
1482
 
480
1483
  // 使用 Ink 渲染命令生成
481
1484
  const { waitUntilExit, unmount } = render(
482
- <MultiStepCommandGenerator
483
- prompt={prompt}
484
- debug={options.debug}
485
- previousSteps={executedSteps}
486
- currentStepNumber={currentStepNumber}
487
- onStepComplete={(res) => {
1485
+ React.createElement(MultiStepCommandGenerator, {
1486
+ prompt,
1487
+ debug: options.debug,
1488
+ previousSteps: executedSteps,
1489
+ currentStepNumber,
1490
+ remoteContext: remoteContext ? {
1491
+ name: remoteContext.name,
1492
+ sysInfo: remoteContext.sysInfo,
1493
+ shellHistory: remoteContext.shellHistory,
1494
+ } : undefined,
1495
+ isRemote: !!remoteName, // 远程执行时不检测 builtin
1496
+ onStepComplete: (res: any) => {
488
1497
  stepResult = res
489
1498
  unmount()
490
- }}
491
- />
1499
+ },
1500
+ })
492
1501
  )
493
1502
 
494
1503
  await waitUntilExit()
@@ -496,23 +1505,34 @@ program
496
1505
 
497
1506
  // 处理步骤结果
498
1507
  if (!stepResult || stepResult.cancelled) {
499
- console.log('')
500
- console2.muted('已取消执行')
501
- console.log('')
502
1508
  process.exit(0)
503
1509
  }
504
1510
 
505
1511
  if (stepResult.hasBuiltin) {
506
- addHistory({
507
- userPrompt: currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${prompt}`,
508
- command: stepResult.command,
509
- aiGeneratedCommand: stepResult.aiGeneratedCommand, // AI 原始命令
510
- userModified: stepResult.userModified || false,
511
- executed: false,
512
- exitCode: null,
513
- output: '',
514
- reason: 'builtin',
515
- })
1512
+ // 远程模式记录到远程历史
1513
+ if (remoteName) {
1514
+ addRemoteHistory(remoteName, {
1515
+ userPrompt: currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${prompt}`,
1516
+ command: stepResult.command,
1517
+ aiGeneratedCommand: stepResult.aiGeneratedCommand,
1518
+ userModified: stepResult.userModified || false,
1519
+ executed: false,
1520
+ exitCode: null,
1521
+ output: '',
1522
+ reason: 'builtin',
1523
+ })
1524
+ } else {
1525
+ addHistory({
1526
+ userPrompt: currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${prompt}`,
1527
+ command: stepResult.command,
1528
+ aiGeneratedCommand: stepResult.aiGeneratedCommand, // AI 原始命令
1529
+ userModified: stepResult.userModified || false,
1530
+ executed: false,
1531
+ exitCode: null,
1532
+ output: '',
1533
+ reason: 'builtin',
1534
+ })
1535
+ }
516
1536
  process.exit(0)
517
1537
  }
518
1538
 
@@ -543,11 +1563,34 @@ program
543
1563
  process.exit(1)
544
1564
  }
545
1565
 
546
- // 执行命令
1566
+ // 执行命令(本地或远程)
547
1567
  const execStart = Date.now()
548
- const { exitCode, output } = await executeCommand(stepResult.command)
1568
+ let exitCode: number
1569
+ let output: string
1570
+ let stdout: string
1571
+
1572
+ if (remoteName) {
1573
+ // 远程执行
1574
+ const result = await executeRemoteCommand(remoteName, stepResult.command)
1575
+ exitCode = result.exitCode
1576
+ output = result.output
1577
+ stdout = result.stdout
1578
+ } else {
1579
+ // 本地执行
1580
+ const result = await executeCommand(stepResult.command)
1581
+ exitCode = result.exitCode
1582
+ output = result.output
1583
+ stdout = result.stdout
1584
+ }
549
1585
  const execDuration = Date.now() - execStart
550
1586
 
1587
+ // 判断命令是否成功
1588
+ // 退出码 141 = 128 + 13 (SIGPIPE),是管道正常关闭时的信号
1589
+ // 例如:ps aux | head -3,head 读完 3 行就关闭管道,ps 收到 SIGPIPE
1590
+ // 但如果退出码是 141 且没有 stdout 输出,说明可能是真正的错误
1591
+ const isSigpipeWithOutput = exitCode === 141 && stdout.trim().length > 0
1592
+ const isSuccess = exitCode === 0 || isSigpipeWithOutput
1593
+
551
1594
  // 保存到执行历史
552
1595
  const executedStep: ExecutedStep = {
553
1596
  command: stepResult.command,
@@ -559,21 +1602,34 @@ program
559
1602
  }
560
1603
  executedSteps.push(executedStep)
561
1604
 
562
- // 记录到 pls 历史
563
- addHistory({
564
- userPrompt:
565
- currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${stepResult.reasoning || prompt}`,
566
- command: stepResult.command,
567
- aiGeneratedCommand: stepResult.aiGeneratedCommand, // AI 原始命令
568
- userModified: stepResult.userModified || false,
569
- executed: true,
570
- exitCode,
571
- output,
572
- })
1605
+ // 记录到 pls 历史(远程模式记录到远程历史)
1606
+ if (remoteName) {
1607
+ addRemoteHistory(remoteName, {
1608
+ userPrompt:
1609
+ currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${stepResult.reasoning || prompt}`,
1610
+ command: stepResult.command,
1611
+ aiGeneratedCommand: stepResult.aiGeneratedCommand,
1612
+ userModified: stepResult.userModified || false,
1613
+ executed: true,
1614
+ exitCode,
1615
+ output,
1616
+ })
1617
+ } else {
1618
+ addHistory({
1619
+ userPrompt:
1620
+ currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${stepResult.reasoning || prompt}`,
1621
+ command: stepResult.command,
1622
+ aiGeneratedCommand: stepResult.aiGeneratedCommand, // AI 原始命令
1623
+ userModified: stepResult.userModified || false,
1624
+ executed: true,
1625
+ exitCode,
1626
+ output,
1627
+ })
1628
+ }
573
1629
 
574
1630
  // 显示结果
575
1631
  console.log('')
576
- if (exitCode === 0) {
1632
+ if (isSuccess) {
577
1633
  if (currentStepNumber === 1 && stepResult.needsContinue !== true) {
578
1634
  // 单步命令
579
1635
  console2.success(`执行完成 ${console2.formatDuration(execDuration)}`)
@@ -627,25 +1683,101 @@ program
627
1683
  })()
628
1684
  })
629
1685
 
1686
+ /**
1687
+ * 执行远程命令
1688
+ * 如果设置了工作目录,自动添加 cd 前缀
1689
+ */
1690
+ async function executeRemoteCommand(
1691
+ remoteName: string,
1692
+ command: string
1693
+ ): Promise<{ exitCode: number; output: string; stdout: string; stderr: string }> {
1694
+ let stdout = ''
1695
+ let stderr = ''
1696
+
1697
+ // 如果有工作目录,自动添加 cd 前缀
1698
+ const workDir = getRemoteWorkDir(remoteName)
1699
+ const actualCommand = workDir ? `cd ${workDir} && ${command}` : command
1700
+
1701
+ console.log('') // 空行
1702
+
1703
+ // 计算命令框宽度,让分隔线长度一致(限制终端宽度)
1704
+ const termWidth = process.stdout.columns || 80
1705
+ const maxContentWidth = termWidth - 6
1706
+ const lines = command.split('\n')
1707
+ const wrappedLines: string[] = []
1708
+ for (const line of lines) {
1709
+ wrappedLines.push(...console2.wrapText(line, maxContentWidth))
1710
+ }
1711
+ const actualMaxWidth = Math.max(
1712
+ ...wrappedLines.map((l) => console2.getDisplayWidth(l)),
1713
+ console2.getDisplayWidth('生成命令')
1714
+ )
1715
+ const boxWidth = Math.min(actualMaxWidth + 4, termWidth - 2)
1716
+ console2.printSeparator(`远程输出 (${remoteName})`, boxWidth)
1717
+
1718
+ try {
1719
+ const result = await sshExec(remoteName, actualCommand, {
1720
+ onStdout: (data) => {
1721
+ stdout += data
1722
+ process.stdout.write(data)
1723
+ },
1724
+ onStderr: (data) => {
1725
+ stderr += data
1726
+ process.stderr.write(data)
1727
+ },
1728
+ })
1729
+
1730
+ if (stdout || stderr) {
1731
+ console2.printSeparator('', boxWidth)
1732
+ }
1733
+
1734
+ return {
1735
+ exitCode: result.exitCode,
1736
+ output: stdout + stderr,
1737
+ stdout,
1738
+ stderr,
1739
+ }
1740
+ } catch (error: any) {
1741
+ console2.printSeparator('', boxWidth)
1742
+ console2.error(error.message)
1743
+ return {
1744
+ exitCode: 1,
1745
+ output: error.message,
1746
+ stdout: '',
1747
+ stderr: error.message,
1748
+ }
1749
+ }
1750
+ }
1751
+
630
1752
  // 自定义帮助信息
631
1753
  program.addHelpText(
632
1754
  'after',
633
1755
  `
634
1756
  ${chalk.bold('示例:')}
635
- ${chalk.hex('#00D9FF')('pls 安装 git')} 让 AI 生成安装 git 的命令
636
- ${chalk.hex('#00D9FF')('pls 查找大于 100MB 的文件')} 查找大文件
637
- ${chalk.hex('#00D9FF')('pls 删除刚才创建的文件')} AI 会参考历史记录
638
- ${chalk.hex('#00D9FF')('pls --debug 压缩 logs 目录')} 显示调试信息
639
- ${chalk.hex('#00D9FF')('pls -m 删除当前目录的空文件夹')} 多步骤模式(AI 自动规划)
640
- ${chalk.hex('#00D9FF')('pls chat tar 命令怎么用')} AI 对话模式
641
- ${chalk.hex('#00D9FF')('pls chat clear')} 清空对话历史
642
- ${chalk.hex('#00D9FF')('pls history')} 查看 pls 命令历史
643
- ${chalk.hex('#00D9FF')('pls history clear')} 清空历史记录
644
- ${chalk.hex('#00D9FF')('pls hook')} 查看 shell hook 状态
645
- ${chalk.hex('#00D9FF')('pls hook install')} 安装 shell hook(增强功能)
646
- ${chalk.hex('#00D9FF')('pls hook uninstall')} 卸载 shell hook
647
- ${chalk.hex('#00D9FF')('pls config')} 交互式配置
648
- ${chalk.hex('#00D9FF')('pls config list')} 查看当前配置
1757
+ ${chalk.hex(getThemeColors().primary)('pls 安装 git')} 让 AI 生成安装 git 的命令
1758
+ ${chalk.hex(getThemeColors().primary)('pls 查找大于 100MB 的文件')} 查找大文件
1759
+ ${chalk.hex(getThemeColors().primary)('pls 删除刚才创建的文件')} AI 会参考历史记录
1760
+ ${chalk.hex(getThemeColors().primary)('pls --debug 压缩 logs 目录')} 显示调试信息
1761
+ ${chalk.hex(getThemeColors().primary)('pls chat tar 命令怎么用')} AI 对话模式
1762
+ ${chalk.hex(getThemeColors().primary)('pls chat clear')} 清空对话历史
1763
+ ${chalk.hex(getThemeColors().primary)('pls history')} 查看 pls 命令历史
1764
+ ${chalk.hex(getThemeColors().primary)('pls history clear')} 清空历史记录
1765
+ ${chalk.hex(getThemeColors().primary)('pls alias')} 查看命令别名
1766
+ ${chalk.hex(getThemeColors().primary)('pls alias add disk "查看磁盘"')} 添加别名
1767
+ ${chalk.hex(getThemeColors().primary)('pls disk')} 使用别名(等同于 pls @disk)
1768
+ ${chalk.hex(getThemeColors().primary)('pls hook')} 查看 shell hook 状态
1769
+ ${chalk.hex(getThemeColors().primary)('pls hook install')} 安装 shell hook(增强功能)
1770
+ ${chalk.hex(getThemeColors().primary)('pls hook uninstall')} 卸载 shell hook
1771
+ ${chalk.hex(getThemeColors().primary)('pls upgrade')} 升级到最新版本
1772
+ ${chalk.hex(getThemeColors().primary)('pls config')} 交互式配置
1773
+ ${chalk.hex(getThemeColors().primary)('pls config list')} 查看当前配置
1774
+
1775
+ ${chalk.bold('远程执行:')}
1776
+ ${chalk.hex(getThemeColors().primary)('pls remote')} 查看远程服务器列表
1777
+ ${chalk.hex(getThemeColors().primary)('pls remote add myserver root@1.2.3.4')} 添加服务器
1778
+ ${chalk.hex(getThemeColors().primary)('pls remote test myserver')} 测试连接
1779
+ ${chalk.hex(getThemeColors().primary)('pls -r myserver 查看磁盘')} 在远程服务器执行
1780
+ ${chalk.hex(getThemeColors().primary)('pls remote hook install myserver')} 安装远程 Shell Hook
649
1781
  `
650
1782
  )
651
1783