@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
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { Text, Box } from 'ink'
3
- import { theme } from '../ui/theme.js'
3
+ import { getCurrentTheme } from '../ui/theme.js'
4
4
  import { ColorizeCode } from './CodeColorizer.js'
5
5
  import { TableRenderer } from './TableRenderer.js'
6
6
  import { RenderInline } from './InlineRenderer.js'
@@ -18,6 +18,7 @@ interface MarkdownDisplayProps {
18
18
  function MarkdownDisplayInternal({ text, terminalWidth = 80 }: MarkdownDisplayProps) {
19
19
  if (!text) return <></>
20
20
 
21
+ const theme = getCurrentTheme()
21
22
  const lines = text.split(/\r?\n/)
22
23
 
23
24
  // 正则表达式
@@ -2,12 +2,12 @@ import React, { useState, useEffect } from 'react'
2
2
  import { Box, Text, useInput } from 'ink'
3
3
  import TextInput from 'ink-text-input'
4
4
  import Spinner from 'ink-spinner'
5
- import { generateMultiStepCommand, type CommandStep, type ExecutedStep } from '../multi-step.js'
5
+ import { generateMultiStepCommand, type CommandStep, type ExecutedStep, type RemoteContext } from '../multi-step.js'
6
6
  import { detectBuiltin, formatBuiltins } from '../builtin-detector.js'
7
7
  import { CommandBox } from './CommandBox.js'
8
8
  import { ConfirmationPrompt } from './ConfirmationPrompt.js'
9
9
  import { Duration } from './Duration.js'
10
- import { theme } from '../ui/theme.js'
10
+ import { getCurrentTheme } from '../ui/theme.js'
11
11
  import { getConfig } from '../config.js'
12
12
 
13
13
  interface MultiStepCommandGeneratorProps {
@@ -28,6 +28,8 @@ interface MultiStepCommandGeneratorProps {
28
28
  }) => void
29
29
  previousSteps?: ExecutedStep[]
30
30
  currentStepNumber?: number
31
+ remoteContext?: RemoteContext // 远程执行上下文
32
+ isRemote?: boolean // 是否为远程执行(远程执行时不检测 builtin)
31
33
  }
32
34
 
33
35
  type State =
@@ -46,8 +48,11 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
46
48
  debug,
47
49
  previousSteps = [],
48
50
  currentStepNumber = 1,
51
+ remoteContext,
52
+ isRemote = false,
49
53
  onStepComplete,
50
54
  }) => {
55
+ const theme = getCurrentTheme()
51
56
  const [state, setState] = useState<State>({ type: 'thinking' })
52
57
  const [thinkDuration, setThinkDuration] = useState(0)
53
58
  const [debugInfo, setDebugInfo] = useState<any>(null)
@@ -67,7 +72,7 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
67
72
  useEffect(() => {
68
73
  const thinkStart = Date.now()
69
74
 
70
- generateMultiStepCommand(prompt, previousSteps, { debug })
75
+ generateMultiStepCommand(prompt, previousSteps, { debug, remoteContext })
71
76
  .then((result) => {
72
77
  const thinkEnd = Date.now()
73
78
  setThinkDuration(thinkEnd - thinkStart)
@@ -91,11 +96,11 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
91
96
  return
92
97
  }
93
98
 
94
- // 检测 builtin(优先检测)
99
+ // 检测 builtin(优先检测,但远程执行时跳过)
95
100
  const { hasBuiltin, builtins } = detectBuiltin(result.stepData.command)
96
101
 
97
- if (hasBuiltin) {
98
- // 有 builtin,不管什么模式都不编辑,直接提示
102
+ if (hasBuiltin && !isRemote) {
103
+ // 有 builtin 且是本地执行,不管什么模式都不编辑,直接提示
99
104
  setState({
100
105
  type: 'showing_command',
101
106
  stepData: result.stepData,
@@ -142,7 +147,7 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
142
147
  })
143
148
  }, 100)
144
149
  })
145
- }, [prompt, previousSteps, debug])
150
+ }, [prompt, previousSteps, debug, remoteContext])
146
151
 
147
152
  // 处理确认
148
153
  const handleConfirm = () => {
@@ -228,7 +233,10 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
228
233
  <Box>
229
234
  <Text color={theme.info}>
230
235
  <Spinner type="dots" />{' '}
231
- {currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`}
236
+ {remoteContext
237
+ ? (currentStepNumber === 1 ? `正在为 ${remoteContext.name} 思考...` : `正在规划步骤 ${currentStepNumber} (${remoteContext.name})...`)
238
+ : (currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`)
239
+ }
232
240
  </Text>
233
241
  </Box>
234
242
  )}
@@ -262,6 +270,12 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
262
270
  </Box>
263
271
  )}
264
272
 
273
+ {debugInfo.remoteContext && (
274
+ <Box marginTop={1}>
275
+ <Text color={theme.text.secondary}>远程服务器: {debugInfo.remoteContext.name} ({debugInfo.remoteContext.sysInfo.os})</Text>
276
+ </Box>
277
+ )}
278
+
265
279
  <Box marginTop={1}>
266
280
  <Text color={theme.text.secondary}>AI 返回的 JSON:</Text>
267
281
  </Box>
@@ -287,8 +301,8 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
287
301
  {/* 命令框 */}
288
302
  <CommandBox command={state.stepData.command} />
289
303
 
290
- {/* Builtin 警告 */}
291
- {(() => {
304
+ {/* Builtin 警告(仅本地执行时显示) */}
305
+ {!isRemote && (() => {
292
306
  const { hasBuiltin, builtins } = detectBuiltin(state.stepData.command)
293
307
  if (hasBuiltin) {
294
308
  return (
@@ -304,7 +318,7 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
304
318
  })()}
305
319
 
306
320
  {/* 确认提示 */}
307
- {!detectBuiltin(state.stepData.command).hasBuiltin && (
321
+ {(isRemote || !detectBuiltin(state.stepData.command).hasBuiltin) && (
308
322
  <ConfirmationPrompt
309
323
  prompt="执行?"
310
324
  onConfirm={handleConfirm}
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { Box, Text } from 'ink'
3
3
  import stringWidth from 'string-width'
4
- import { theme } from '../ui/theme.js'
4
+ import { getCurrentTheme } from '../ui/theme.js'
5
5
  import { RenderInline } from './InlineRenderer.js'
6
6
 
7
7
  interface TableRendererProps {
@@ -56,6 +56,7 @@ function calculateColumnWidths(
56
56
  * 表格渲染组件
57
57
  */
58
58
  function TableRendererInternal({ headers, rows, terminalWidth }: TableRendererProps) {
59
+ const theme = getCurrentTheme()
59
60
  const columnWidths = calculateColumnWidths(headers, rows, terminalWidth)
60
61
  const baseColor = theme.text.primary
61
62
 
package/src/config.ts CHANGED
@@ -3,6 +3,18 @@ import path from 'path'
3
3
  import os from 'os'
4
4
  import readline from 'readline'
5
5
  import chalk from 'chalk'
6
+ import { getCurrentTheme, isValidTheme, getAllThemeMetadata, type ThemeName } from './ui/theme.js'
7
+
8
+ // 获取主题颜色
9
+ function getColors() {
10
+ const theme = getCurrentTheme()
11
+ return {
12
+ primary: theme.primary,
13
+ secondary: theme.secondary,
14
+ success: theme.success,
15
+ error: theme.error
16
+ }
17
+ }
6
18
 
7
19
  // 配置文件路径
8
20
  export const CONFIG_DIR = path.join(os.homedir(), '.please')
@@ -27,6 +39,37 @@ type Provider = (typeof VALID_PROVIDERS)[number]
27
39
  const VALID_EDIT_MODES = ['manual', 'auto'] as const
28
40
  type EditMode = (typeof VALID_EDIT_MODES)[number]
29
41
 
42
+ /**
43
+ * 别名配置接口
44
+ */
45
+ export interface AliasConfig {
46
+ prompt: string
47
+ description?: string
48
+ }
49
+
50
+ /**
51
+ * 远程服务器配置接口
52
+ */
53
+ export interface RemoteConfig {
54
+ host: string
55
+ user: string
56
+ port: number
57
+ key?: string // SSH 私钥路径
58
+ password?: boolean // 是否使用密码认证(密码不存储,每次交互输入)
59
+ workDir?: string // 默认工作目录
60
+ }
61
+
62
+ /**
63
+ * 远程服务器系统信息缓存
64
+ */
65
+ export interface RemoteSysInfo {
66
+ os: string // 操作系统 (linux, darwin, etc.)
67
+ osVersion: string // 系统版本
68
+ shell: string // 默认 shell (bash, zsh, etc.)
69
+ hostname: string // 主机名
70
+ cachedAt: string // 缓存时间
71
+ }
72
+
30
73
  /**
31
74
  * 配置接口
32
75
  */
@@ -40,6 +83,10 @@ export interface Config {
40
83
  commandHistoryLimit: number
41
84
  shellHistoryLimit: number
42
85
  editMode: EditMode
86
+ theme: ThemeName
87
+ aliases: Record<string, AliasConfig>
88
+ remotes: Record<string, RemoteConfig> // 远程服务器配置
89
+ defaultRemote?: string // 默认远程服务器名称
43
90
  }
44
91
 
45
92
  /**
@@ -51,10 +98,14 @@ const DEFAULT_CONFIG: Config = {
51
98
  model: 'gpt-4-turbo',
52
99
  provider: 'openai',
53
100
  shellHook: false,
54
- chatHistoryLimit: 10,
55
- commandHistoryLimit: 10,
56
- shellHistoryLimit: 15,
101
+ chatHistoryLimit: 5,
102
+ commandHistoryLimit: 5,
103
+ shellHistoryLimit: 10,
57
104
  editMode: 'manual',
105
+ theme: 'dark',
106
+ aliases: {},
107
+ remotes: {},
108
+ defaultRemote: '',
58
109
  }
59
110
 
60
111
  /**
@@ -68,20 +119,33 @@ function ensureConfigDir(): void {
68
119
 
69
120
  /**
70
121
  * 读取配置
122
+ * 优化:添加缓存,避免重复读取文件
71
123
  */
124
+ let cachedConfig: Config | null = null
125
+
72
126
  export function getConfig(): Config {
127
+ // 如果已有缓存,直接返回
128
+ if (cachedConfig !== null) {
129
+ return cachedConfig
130
+ }
131
+
73
132
  ensureConfigDir()
74
133
 
134
+ let config: Config
135
+
75
136
  if (!fs.existsSync(CONFIG_FILE)) {
76
- return { ...DEFAULT_CONFIG }
137
+ config = { ...DEFAULT_CONFIG }
138
+ } else {
139
+ try {
140
+ const content = fs.readFileSync(CONFIG_FILE, 'utf-8')
141
+ config = { ...DEFAULT_CONFIG, ...JSON.parse(content) }
142
+ } catch {
143
+ config = { ...DEFAULT_CONFIG }
144
+ }
77
145
  }
78
146
 
79
- try {
80
- const content = fs.readFileSync(CONFIG_FILE, 'utf-8')
81
- return { ...DEFAULT_CONFIG, ...JSON.parse(content) }
82
- } catch {
83
- return { ...DEFAULT_CONFIG }
84
- }
147
+ cachedConfig = config
148
+ return config
85
149
  }
86
150
 
87
151
  /**
@@ -123,11 +187,23 @@ export function setConfigValue(key: string, value: string | boolean | number): C
123
187
  throw new Error(`editMode 必须是以下之一: ${VALID_EDIT_MODES.join(', ')}`)
124
188
  }
125
189
  config.editMode = strValue as EditMode
126
- } else if (key === 'apiKey' || key === 'baseUrl' || key === 'model') {
190
+ } else if (key === 'theme') {
191
+ const strValue = String(value)
192
+ if (!isValidTheme(strValue)) {
193
+ const allThemes = getAllThemeMetadata()
194
+ const themeNames = allThemes.map((m) => m.name).join(', ')
195
+ throw new Error(`theme 必须是以下之一: ${themeNames}`)
196
+ }
197
+ config.theme = strValue as ThemeName
198
+ } else if (key === 'apiKey' || key === 'baseUrl' || key === 'model' || key === 'defaultRemote') {
127
199
  config[key] = String(value)
128
200
  }
129
201
 
130
202
  saveConfig(config)
203
+
204
+ // 清除缓存,下次读取时会重新加载
205
+ cachedConfig = null
206
+
131
207
  return config
132
208
  }
133
209
 
@@ -152,23 +228,30 @@ export function maskApiKey(apiKey: string): string {
152
228
  */
153
229
  export function displayConfig(): void {
154
230
  const config = getConfig()
231
+ const colors = getColors()
155
232
  console.log(chalk.bold('\n当前配置:'))
156
233
  console.log(chalk.gray('━'.repeat(50)))
157
- console.log(` ${chalk.cyan('apiKey')}: ${maskApiKey(config.apiKey)}`)
158
- console.log(` ${chalk.cyan('baseUrl')}: ${config.baseUrl}`)
159
- console.log(` ${chalk.cyan('provider')}: ${config.provider}`)
160
- console.log(` ${chalk.cyan('model')}: ${config.model}`)
234
+ console.log(` ${chalk.hex(colors.primary)('apiKey')}: ${maskApiKey(config.apiKey)}`)
235
+ console.log(` ${chalk.hex(colors.primary)('baseUrl')}: ${config.baseUrl}`)
236
+ console.log(` ${chalk.hex(colors.primary)('provider')}: ${config.provider}`)
237
+ console.log(` ${chalk.hex(colors.primary)('model')}: ${config.model}`)
161
238
  console.log(
162
- ` ${chalk.cyan('shellHook')}: ${config.shellHook ? chalk.green('已启用') : chalk.gray('未启用')}`
239
+ ` ${chalk.hex(colors.primary)('shellHook')}: ${config.shellHook ? chalk.hex(colors.success)('已启用') : chalk.gray('未启用')}`
163
240
  )
164
241
  console.log(
165
- ` ${chalk.cyan('editMode')}: ${
166
- config.editMode === 'auto' ? chalk.hex('#00D9FF')('auto (自动编辑)') : chalk.gray('manual (按E编辑)')
242
+ ` ${chalk.hex(colors.primary)('editMode')}: ${
243
+ config.editMode === 'auto' ? chalk.hex(colors.primary)('auto (自动编辑)') : chalk.gray('manual (按E编辑)')
167
244
  }`
168
245
  )
169
- console.log(` ${chalk.cyan('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
170
- console.log(` ${chalk.cyan('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
171
- console.log(` ${chalk.cyan('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
246
+ console.log(` ${chalk.hex(colors.primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
247
+ console.log(` ${chalk.hex(colors.primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
248
+ console.log(` ${chalk.hex(colors.primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
249
+
250
+ // 动态显示主题信息
251
+ const themeMetadata = getAllThemeMetadata().find((m) => m.name === config.theme)
252
+ const themeLabel = themeMetadata ? `${themeMetadata.name} (${themeMetadata.displayName})` : config.theme
253
+ console.log(` ${chalk.hex(colors.primary)('theme')}: ${chalk.hex(colors.primary)(themeLabel)}`)
254
+
172
255
  console.log(chalk.gray('━'.repeat(50)))
173
256
  console.log(chalk.gray(`配置文件: ${CONFIG_FILE}\n`))
174
257
  }
@@ -200,19 +283,20 @@ function question(rl: readline.Interface, prompt: string): Promise<string> {
200
283
  export async function runConfigWizard(): Promise<void> {
201
284
  const rl = createReadlineInterface()
202
285
  const config = getConfig()
286
+ const colors = getColors()
203
287
 
204
- console.log(chalk.bold.hex('#00D9FF')('\n🔧 Pretty Please 配置向导'))
288
+ console.log(chalk.bold.hex(colors.primary)('\n🔧 Pretty Please 配置向导'))
205
289
  console.log(chalk.gray('━'.repeat(50)))
206
290
  console.log(chalk.gray('直接回车使用默认值,输入值后回车确认\n'))
207
291
 
208
292
  try {
209
293
  // 1. Provider
210
294
  const providerHint = chalk.gray(`(可选: ${VALID_PROVIDERS.join(', ')})`)
211
- const providerPrompt = `${chalk.cyan('Provider')} ${providerHint}\n${chalk.gray('默认:')} ${chalk.yellow(config.provider)} ${chalk.gray('→')} `
295
+ const providerPrompt = `${chalk.hex(colors.primary)('Provider')} ${providerHint}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.provider)} ${chalk.gray('→')} `
212
296
  const provider = await question(rl, providerPrompt)
213
297
  if (provider.trim()) {
214
298
  if (!VALID_PROVIDERS.includes(provider.trim() as Provider)) {
215
- console.log(chalk.hex('#EF4444')(`\n✗ 无效的 provider,必须是以下之一: ${VALID_PROVIDERS.join(', ')}`))
299
+ console.log(chalk.hex(colors.error)(`\n✗ 无效的 provider,必须是以下之一: ${VALID_PROVIDERS.join(', ')}`))
216
300
  console.log()
217
301
  rl.close()
218
302
  return
@@ -221,7 +305,7 @@ export async function runConfigWizard(): Promise<void> {
221
305
  }
222
306
 
223
307
  // 2. Base URL
224
- const baseUrlPrompt = `${chalk.cyan('API Base URL')}\n${chalk.gray('默认:')} ${chalk.yellow(config.baseUrl)} ${chalk.gray('→')} `
308
+ const baseUrlPrompt = `${chalk.hex(colors.primary)('API Base URL')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.baseUrl)} ${chalk.gray('→')} `
225
309
  const baseUrl = await question(rl, baseUrlPrompt)
226
310
  if (baseUrl.trim()) {
227
311
  config.baseUrl = baseUrl.trim()
@@ -229,21 +313,21 @@ export async function runConfigWizard(): Promise<void> {
229
313
 
230
314
  // 3. API Key
231
315
  const currentKeyDisplay = config.apiKey ? maskApiKey(config.apiKey) : '(未设置)'
232
- const apiKeyPrompt = `${chalk.cyan('API Key')} ${chalk.gray(`(当前: ${currentKeyDisplay})`)}\n${chalk.gray('→')} `
316
+ const apiKeyPrompt = `${chalk.hex(colors.primary)('API Key')} ${chalk.gray(`(当前: ${currentKeyDisplay})`)}\n${chalk.gray('→')} `
233
317
  const apiKey = await question(rl, apiKeyPrompt)
234
318
  if (apiKey.trim()) {
235
319
  config.apiKey = apiKey.trim()
236
320
  }
237
321
 
238
322
  // 4. Model
239
- const modelPrompt = `${chalk.cyan('Model')}\n${chalk.gray('默认:')} ${chalk.yellow(config.model)} ${chalk.gray('→')} `
323
+ const modelPrompt = `${chalk.hex(colors.primary)('Model')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.model)} ${chalk.gray('→')} `
240
324
  const model = await question(rl, modelPrompt)
241
325
  if (model.trim()) {
242
326
  config.model = model.trim()
243
327
  }
244
328
 
245
329
  // 5. Shell Hook
246
- const shellHookPrompt = `${chalk.cyan('启用 Shell Hook')} ${chalk.gray('(记录终端命令历史)')}\n${chalk.gray('默认:')} ${chalk.yellow(config.shellHook ? 'true' : 'false')} ${chalk.gray('→')} `
330
+ const shellHookPrompt = `${chalk.hex(colors.primary)('启用 Shell Hook')} ${chalk.gray('(记录终端命令历史)')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.shellHook ? 'true' : 'false')} ${chalk.gray('→')} `
247
331
  const shellHook = await question(rl, shellHookPrompt)
248
332
  if (shellHook.trim()) {
249
333
  config.shellHook = shellHook.trim() === 'true'
@@ -251,11 +335,11 @@ export async function runConfigWizard(): Promise<void> {
251
335
 
252
336
  // 6. Edit Mode
253
337
  const editModeHint = chalk.gray('(manual=按E编辑, auto=自动编辑)')
254
- const editModePrompt = `${chalk.cyan('编辑模式')} ${editModeHint}\n${chalk.gray('默认:')} ${chalk.yellow(config.editMode)} ${chalk.gray('→')} `
338
+ const editModePrompt = `${chalk.hex(colors.primary)('编辑模式')} ${editModeHint}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.editMode)} ${chalk.gray('→')} `
255
339
  const editMode = await question(rl, editModePrompt)
256
340
  if (editMode.trim()) {
257
341
  if (!VALID_EDIT_MODES.includes(editMode.trim() as EditMode)) {
258
- console.log(chalk.hex('#EF4444')(`\n✗ 无效的 editMode,必须是: manual 或 auto`))
342
+ console.log(chalk.hex(colors.error)(`\n✗ 无效的 editMode,必须是: manual 或 auto`))
259
343
  console.log()
260
344
  rl.close()
261
345
  return
@@ -264,7 +348,7 @@ export async function runConfigWizard(): Promise<void> {
264
348
  }
265
349
 
266
350
  // 7. Chat History Limit
267
- const chatHistoryPrompt = `${chalk.cyan('Chat 历史保留轮数')}\n${chalk.gray('默认:')} ${chalk.yellow(config.chatHistoryLimit)} ${chalk.gray('→')} `
351
+ const chatHistoryPrompt = `${chalk.hex(colors.primary)('Chat 历史保留轮数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.chatHistoryLimit)} ${chalk.gray('→')} `
268
352
  const chatHistoryLimit = await question(rl, chatHistoryPrompt)
269
353
  if (chatHistoryLimit.trim()) {
270
354
  const num = parseInt(chatHistoryLimit.trim(), 10)
@@ -274,7 +358,7 @@ export async function runConfigWizard(): Promise<void> {
274
358
  }
275
359
 
276
360
  // 8. Command History Limit
277
- const commandHistoryPrompt = `${chalk.cyan('命令历史保留条数')}\n${chalk.gray('默认:')} ${chalk.yellow(config.commandHistoryLimit)} ${chalk.gray('→')} `
361
+ const commandHistoryPrompt = `${chalk.hex(colors.primary)('命令历史保留条数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.commandHistoryLimit)} ${chalk.gray('→')} `
278
362
  const commandHistoryLimit = await question(rl, commandHistoryPrompt)
279
363
  if (commandHistoryLimit.trim()) {
280
364
  const num = parseInt(commandHistoryLimit.trim(), 10)
@@ -284,7 +368,8 @@ export async function runConfigWizard(): Promise<void> {
284
368
  }
285
369
 
286
370
  // 9. Shell History Limit
287
- const shellHistoryPrompt = `${chalk.cyan('Shell 历史保留条数')}\n${chalk.gray('默认:')} ${chalk.yellow(config.shellHistoryLimit)} ${chalk.gray('→')} `
371
+ const oldShellHistoryLimit = config.shellHistoryLimit // 保存旧值
372
+ const shellHistoryPrompt = `${chalk.hex(colors.primary)('Shell 历史保留条数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.shellHistoryLimit)} ${chalk.gray('→')} `
288
373
  const shellHistoryLimit = await question(rl, shellHistoryPrompt)
289
374
  if (shellHistoryLimit.trim()) {
290
375
  const num = parseInt(shellHistoryLimit.trim(), 10)
@@ -296,12 +381,18 @@ export async function runConfigWizard(): Promise<void> {
296
381
  saveConfig(config)
297
382
 
298
383
  console.log('\n' + chalk.gray('━'.repeat(50)))
299
- console.log(chalk.hex('#10B981')('✅ 配置已保存'))
384
+ console.log(chalk.hex(getColors().success)('✅ 配置已保存'))
300
385
  console.log(chalk.gray(` ${CONFIG_FILE}`))
301
386
  console.log()
387
+
388
+ // 如果修改了 shellHistoryLimit,自动重装 hook
389
+ if (oldShellHistoryLimit !== config.shellHistoryLimit) {
390
+ const { reinstallHookForLimitChange } = await import('./shell-hook.js')
391
+ await reinstallHookForLimitChange(oldShellHistoryLimit, config.shellHistoryLimit)
392
+ }
302
393
  } catch (error) {
303
394
  const message = error instanceof Error ? error.message : String(error)
304
- console.log(chalk.hex('#EF4444')(`\n✗ 配置失败: ${message}`))
395
+ console.log(chalk.hex(getColors().error)(`\n✗ 配置失败: ${message}`))
305
396
  console.log()
306
397
  } finally {
307
398
  rl.close()
@@ -1,13 +1,11 @@
1
1
  import { Agent } from '@mastra/core'
2
2
  import { getConfig } from './config.js'
3
- import { buildCommandSystemPrompt } from './prompts.js'
4
- import { formatSystemInfo } from './sysinfo.js'
5
- import { formatHistoryForAI } from './history.js'
6
- import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js'
3
+ import { SHELL_COMMAND_SYSTEM_PROMPT } from './prompts.js'
7
4
 
8
5
  /**
9
6
  * 创建 Mastra Shell Agent
10
7
  * 根据用户配置的 API Key、Base URL、Provider 和 Model
8
+ * 使用静态的 System Prompt(不包含动态数据)
11
9
  */
12
10
  export function createShellAgent() {
13
11
  const config = getConfig()
@@ -15,16 +13,9 @@ export function createShellAgent() {
15
13
  // 组合 provider/model 格式(Mastra 要求)
16
14
  const modelId = `${config.provider}/${config.model}` as `${string}/${string}`
17
15
 
18
- // 构建系统提示词
19
- const sysinfo = formatSystemInfo()
20
- const plsHistory = formatHistoryForAI()
21
- const shellHistory = formatShellHistoryForAI()
22
- const shellHookEnabled = config.shellHook && getShellHistory().length > 0
23
- const systemPrompt = buildCommandSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
24
-
25
16
  return new Agent({
26
17
  name: 'shell-commander',
27
- instructions: systemPrompt,
18
+ instructions: SHELL_COMMAND_SYSTEM_PROMPT,
28
19
  model: {
29
20
  url: config.baseUrl,
30
21
  id: modelId,
@@ -1,13 +1,13 @@
1
1
  import { Agent } from '@mastra/core'
2
2
  import { getConfig } from './config.js'
3
- import { buildChatSystemPrompt } from './prompts.js'
3
+ import { CHAT_SYSTEM_PROMPT, buildChatUserContext } from './prompts.js'
4
4
  import { formatSystemInfo } from './sysinfo.js'
5
5
  import { formatHistoryForAI } from './history.js'
6
6
  import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js'
7
7
  import { getChatHistory, addChatMessage } from './chat-history.js'
8
8
 
9
9
  /**
10
- * 创建 Mastra Chat Agent
10
+ * 创建 Mastra Chat Agent(使用静态系统提示词)
11
11
  */
12
12
  export function createChatAgent() {
13
13
  const config = getConfig()
@@ -15,16 +15,9 @@ export function createChatAgent() {
15
15
  // 组合 provider/model 格式(Mastra 要求)
16
16
  const modelId = `${config.provider}/${config.model}` as `${string}/${string}`
17
17
 
18
- // 构建系统提示词
19
- const sysinfo = formatSystemInfo()
20
- const plsHistory = formatHistoryForAI()
21
- const shellHistory = formatShellHistoryForAI()
22
- const shellHookEnabled = config.shellHook && getShellHistory().length > 0
23
- const systemPrompt = buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
24
-
25
18
  return new Agent({
26
19
  name: 'chat-assistant',
27
- instructions: systemPrompt,
20
+ instructions: CHAT_SYSTEM_PROMPT, // 只包含静态规则
28
21
  model: {
29
22
  url: config.baseUrl,
30
23
  id: modelId,
@@ -33,20 +26,19 @@ export function createChatAgent() {
33
26
  })
34
27
  }
35
28
 
36
- /**
37
- * 获取完整的系统提示词(用于调试)
38
- */
39
- export function getChatSystemPrompt(): string {
40
- const config = getConfig()
41
- const sysinfo = formatSystemInfo()
42
- const plsHistory = formatHistoryForAI()
43
- const shellHistory = formatShellHistoryForAI()
44
- const shellHookEnabled = config.shellHook && getShellHistory().length > 0
45
- return buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
46
- }
47
-
48
29
  /**
49
30
  * 使用 Mastra 进行 AI 对话(支持流式输出)
31
+ *
32
+ * 消息结构:
33
+ * [
34
+ * "历史问题1", // user (纯粹的问题)
35
+ * "历史回答1", // assistant
36
+ * "历史问题2", // user
37
+ * "历史回答2", // assistant
38
+ * "<system_info>...\n // user (最新消息,包含完整上下文)
39
+ * <command_history>...\n
40
+ * <user_question>最新问题</user_question>"
41
+ * ]
50
42
  */
51
43
  export async function chatWithMastra(
52
44
  prompt: string,
@@ -61,30 +53,44 @@ export async function chatWithMastra(
61
53
  model: string
62
54
  systemPrompt: string
63
55
  chatHistory: Array<{ role: string; content: string }>
64
- userPrompt: string
56
+ userContext: string
65
57
  }
66
58
  }> {
67
59
  const config = getConfig()
68
60
  const agent = createChatAgent()
69
61
 
70
- // 获取对话历史
62
+ // 1. 获取历史对话(纯粹的问答)
71
63
  const chatHistory = getChatHistory()
72
64
 
73
- // 构建消息数组(将历史和新消息合并)
65
+ // 2. 构建消息数组
74
66
  const messages: string[] = []
75
67
 
76
- // 添加历史对话
68
+ // 加载历史对话
77
69
  for (const msg of chatHistory) {
78
70
  messages.push(msg.content)
79
71
  }
80
72
 
81
- // 添加当前用户消息
82
- messages.push(prompt)
73
+ // 3. 构建最新消息(动态上下文 + 用户问题)
74
+ const sysinfo = formatSystemInfo()
75
+ const plsHistory = formatHistoryForAI()
76
+ const shellHistory = formatShellHistoryForAI()
77
+ const shellHookEnabled = config.shellHook && getShellHistory().length > 0
78
+
79
+ const latestUserContext = buildChatUserContext(
80
+ prompt,
81
+ sysinfo,
82
+ plsHistory,
83
+ shellHistory,
84
+ shellHookEnabled
85
+ )
86
+
87
+ messages.push(latestUserContext)
83
88
 
89
+ // 4. 发送给 AI(流式或非流式)
84
90
  let fullContent = ''
85
91
 
86
- // 流式输出模式
87
92
  if (options.onChunk) {
93
+ // 流式输出模式
88
94
  const stream = await agent.stream(messages)
89
95
 
90
96
  for await (const chunk of stream.textStream) {
@@ -103,19 +109,19 @@ export async function chatWithMastra(
103
109
  throw new Error('AI 返回了空的响应')
104
110
  }
105
111
 
106
- // 保存对话历史
112
+ // 5. 保存对话历史(只保存纯粹的问题和回答,不保存 XML)
107
113
  addChatMessage(prompt, fullContent)
108
114
 
109
- // 返回结果
115
+ // 6. 返回结果
110
116
  if (options.debug) {
111
117
  return {
112
118
  reply: fullContent,
113
119
  debug: {
114
- sysinfo: formatSystemInfo(),
120
+ sysinfo,
115
121
  model: config.model,
116
- systemPrompt: getChatSystemPrompt(),
122
+ systemPrompt: CHAT_SYSTEM_PROMPT,
117
123
  chatHistory,
118
- userPrompt: prompt,
124
+ userContext: latestUserContext,
119
125
  },
120
126
  }
121
127
  }