@yivan-lab/pretty-please 1.0.0 → 1.2.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 (89) hide show
  1. package/README.md +381 -28
  2. package/bin/pls.tsx +1138 -109
  3. package/dist/bin/pls.d.ts +1 -1
  4. package/dist/bin/pls.js +994 -91
  5. package/dist/package.json +80 -0
  6. package/dist/src/ai.d.ts +1 -41
  7. package/dist/src/ai.js +9 -190
  8. package/dist/src/alias.d.ts +41 -0
  9. package/dist/src/alias.js +240 -0
  10. package/dist/src/builtin-detector.d.ts +14 -8
  11. package/dist/src/builtin-detector.js +36 -16
  12. package/dist/src/chat-history.d.ts +16 -11
  13. package/dist/src/chat-history.js +35 -4
  14. package/dist/src/components/Chat.js +5 -4
  15. package/dist/src/components/CodeColorizer.js +26 -20
  16. package/dist/src/components/CommandBox.js +3 -17
  17. package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
  18. package/dist/src/components/ConfirmationPrompt.js +9 -4
  19. package/dist/src/components/Duration.js +2 -1
  20. package/dist/src/components/InlineRenderer.js +2 -1
  21. package/dist/src/components/MarkdownDisplay.js +2 -1
  22. package/dist/src/components/MultiStepCommandGenerator.d.ts +5 -1
  23. package/dist/src/components/MultiStepCommandGenerator.js +127 -14
  24. package/dist/src/components/TableRenderer.js +2 -1
  25. package/dist/src/config.d.ts +59 -9
  26. package/dist/src/config.js +147 -48
  27. package/dist/src/history.d.ts +19 -5
  28. package/dist/src/history.js +26 -11
  29. package/dist/src/mastra-agent.d.ts +0 -1
  30. package/dist/src/mastra-agent.js +3 -4
  31. package/dist/src/mastra-chat.d.ts +28 -0
  32. package/dist/src/mastra-chat.js +93 -0
  33. package/dist/src/multi-step.d.ts +23 -7
  34. package/dist/src/multi-step.js +29 -6
  35. package/dist/src/prompts.d.ts +11 -0
  36. package/dist/src/prompts.js +140 -0
  37. package/dist/src/remote-history.d.ts +63 -0
  38. package/dist/src/remote-history.js +315 -0
  39. package/dist/src/remote.d.ts +113 -0
  40. package/dist/src/remote.js +634 -0
  41. package/dist/src/shell-hook.d.ts +87 -12
  42. package/dist/src/shell-hook.js +315 -17
  43. package/dist/src/sysinfo.d.ts +9 -5
  44. package/dist/src/sysinfo.js +2 -2
  45. package/dist/src/ui/theme.d.ts +27 -24
  46. package/dist/src/ui/theme.js +71 -21
  47. package/dist/src/upgrade.d.ts +41 -0
  48. package/dist/src/upgrade.js +348 -0
  49. package/dist/src/utils/console.d.ts +11 -11
  50. package/dist/src/utils/console.js +26 -17
  51. package/package.json +11 -9
  52. package/src/alias.ts +301 -0
  53. package/src/builtin-detector.ts +126 -0
  54. package/src/chat-history.ts +140 -0
  55. package/src/components/Chat.tsx +6 -5
  56. package/src/components/CodeColorizer.tsx +27 -19
  57. package/src/components/CommandBox.tsx +3 -17
  58. package/src/components/ConfirmationPrompt.tsx +11 -3
  59. package/src/components/Duration.tsx +2 -1
  60. package/src/components/InlineRenderer.tsx +2 -1
  61. package/src/components/MarkdownDisplay.tsx +2 -1
  62. package/src/components/MultiStepCommandGenerator.tsx +167 -16
  63. package/src/components/TableRenderer.tsx +2 -1
  64. package/src/config.ts +394 -0
  65. package/src/history.ts +160 -0
  66. package/src/mastra-agent.ts +3 -4
  67. package/src/mastra-chat.ts +124 -0
  68. package/src/multi-step.ts +45 -8
  69. package/src/prompts.ts +154 -0
  70. package/src/remote-history.ts +390 -0
  71. package/src/remote.ts +800 -0
  72. package/src/shell-hook.ts +754 -0
  73. package/src/{sysinfo.js → sysinfo.ts} +28 -16
  74. package/src/ui/theme.ts +101 -24
  75. package/src/upgrade.ts +397 -0
  76. package/src/utils/{console.js → console.ts} +36 -27
  77. package/bin/pls.js +0 -681
  78. package/src/ai.js +0 -324
  79. package/src/builtin-detector.js +0 -98
  80. package/src/chat-history.js +0 -94
  81. package/src/components/ChatStatus.tsx +0 -53
  82. package/src/components/CommandGenerator.tsx +0 -184
  83. package/src/components/ConfigDisplay.tsx +0 -64
  84. package/src/components/ConfigWizard.tsx +0 -101
  85. package/src/components/HistoryDisplay.tsx +0 -69
  86. package/src/components/HookManager.tsx +0 -150
  87. package/src/config.js +0 -221
  88. package/src/history.js +0 -131
  89. package/src/shell-hook.js +0 -393
@@ -2,23 +2,27 @@ import React from 'react'
2
2
  import { Text, Box } from 'ink'
3
3
  import { common, createLowlight } from 'lowlight'
4
4
  import type { Root, Element, Text as HastText, ElementContent, RootContent } from 'hast'
5
- import { theme } from '../ui/theme.js'
5
+ import { getCurrentTheme, type Theme } from '../ui/theme.js'
6
6
 
7
7
  // 创建 lowlight 实例
8
8
  const lowlight = createLowlight(common)
9
9
 
10
- // 语法高亮颜色映射
11
- const syntaxColors: Record<string, string> = {
12
- 'hljs-keyword': theme.code.keyword,
13
- 'hljs-string': theme.code.string,
14
- 'hljs-function': theme.code.function,
15
- 'hljs-comment': theme.code.comment,
16
- 'hljs-number': theme.primary,
17
- 'hljs-built_in': theme.secondary,
18
- 'hljs-title': theme.accent,
19
- 'hljs-variable': theme.text.primary,
20
- 'hljs-type': theme.info,
21
- 'hljs-operator': theme.text.secondary,
10
+ /**
11
+ * 获取语法高亮颜色映射
12
+ */
13
+ function getSyntaxColors(theme: Theme): Record<string, string> {
14
+ return {
15
+ 'hljs-keyword': theme.code.keyword,
16
+ 'hljs-string': theme.code.string,
17
+ 'hljs-function': theme.code.function,
18
+ 'hljs-comment': theme.code.comment,
19
+ 'hljs-number': theme.primary,
20
+ 'hljs-built_in': theme.secondary,
21
+ 'hljs-title': theme.accent,
22
+ 'hljs-variable': theme.text.primary,
23
+ 'hljs-type': theme.info,
24
+ 'hljs-operator': theme.text.secondary,
25
+ }
22
26
  }
23
27
 
24
28
  /**
@@ -26,7 +30,9 @@ const syntaxColors: Record<string, string> = {
26
30
  */
27
31
  function renderHastNode(
28
32
  node: Root | Element | HastText | RootContent,
29
- inheritedColor: string | undefined
33
+ inheritedColor: string | undefined,
34
+ syntaxColors: Record<string, string>,
35
+ theme: Theme
30
36
  ): React.ReactNode {
31
37
  if (node.type === 'text') {
32
38
  const color = inheritedColor || theme.code.text
@@ -51,7 +57,7 @@ function renderHastNode(
51
57
  // 递归渲染子节点
52
58
  const children = node.children?.map((child: ElementContent, index: number) => (
53
59
  <React.Fragment key={index}>
54
- {renderHastNode(child, colorToPassDown)}
60
+ {renderHastNode(child, colorToPassDown, syntaxColors, theme)}
55
61
  </React.Fragment>
56
62
  ))
57
63
 
@@ -65,7 +71,7 @@ function renderHastNode(
65
71
 
66
72
  return node.children?.map((child: RootContent, index: number) => (
67
73
  <React.Fragment key={index}>
68
- {renderHastNode(child, inheritedColor)}
74
+ {renderHastNode(child, inheritedColor, syntaxColors, theme)}
69
75
  </React.Fragment>
70
76
  ))
71
77
  }
@@ -76,13 +82,13 @@ function renderHastNode(
76
82
  /**
77
83
  * 高亮并渲染一行代码
78
84
  */
79
- function highlightLine(line: string, language: string | null): React.ReactNode {
85
+ function highlightLine(line: string, language: string | null, syntaxColors: Record<string, string>, theme: Theme): React.ReactNode {
80
86
  try {
81
87
  const highlighted = !language || !lowlight.registered(language)
82
88
  ? lowlight.highlightAuto(line)
83
89
  : lowlight.highlight(language, line)
84
90
 
85
- const rendered = renderHastNode(highlighted, undefined)
91
+ const rendered = renderHastNode(highlighted, undefined, syntaxColors, theme)
86
92
  return rendered !== null ? rendered : line
87
93
  } catch {
88
94
  return line
@@ -99,12 +105,14 @@ interface ColorizeCodeProps {
99
105
  * 代码高亮组件
100
106
  */
101
107
  function ColorizeCodeInternal({ code, language = null, showLineNumbers = false }: ColorizeCodeProps) {
108
+ const theme = getCurrentTheme()
109
+ const syntaxColors = getSyntaxColors(theme)
102
110
  const codeToHighlight = code.replace(/\n$/, '')
103
111
  const lines = codeToHighlight.split('\n')
104
112
  const padWidth = String(lines.length).length
105
113
 
106
114
  const renderedLines = lines.map((line, index) => {
107
- const contentToRender = highlightLine(line, language)
115
+ const contentToRender = highlightLine(line, language, syntaxColors, theme)
108
116
 
109
117
  return (
110
118
  <Box key={index} minHeight={1}>
@@ -1,32 +1,18 @@
1
1
  import React from 'react'
2
2
  import { Box, Text } from 'ink'
3
- import { theme } from '../ui/theme.js'
3
+ import { getCurrentTheme } from '../ui/theme.js'
4
+ import { getDisplayWidth } from '../utils/console.js'
4
5
 
5
6
  interface CommandBoxProps {
6
7
  command: string
7
8
  title?: string
8
9
  }
9
10
 
10
- /**
11
- * 计算字符串的显示宽度(中文占2个宽度)
12
- */
13
- function getDisplayWidth(str: string): number {
14
- let width = 0
15
- for (const char of str) {
16
- // 中文、日文、韩文等宽字符占 2 个宽度
17
- if (char.match(/[\u4e00-\u9fff\u3400-\u4dbf\uff00-\uffef\u3000-\u303f]/)) {
18
- width += 2
19
- } else {
20
- width += 1
21
- }
22
- }
23
- return width
24
- }
25
-
26
11
  /**
27
12
  * CommandBox 组件 - 显示带边框和标题的命令框
28
13
  */
29
14
  export const CommandBox: React.FC<CommandBoxProps> = ({ command, title = '生成命令' }) => {
15
+ const theme = getCurrentTheme()
30
16
  const lines = command.split('\n')
31
17
  const titleWidth = getDisplayWidth(title)
32
18
  const maxContentWidth = Math.max(...lines.map(l => getDisplayWidth(l)))
@@ -1,22 +1,25 @@
1
1
  import React from 'react'
2
2
  import { Text, useInput } from 'ink'
3
- import { theme } from '../ui/theme.js'
3
+ import { getCurrentTheme } from '../ui/theme.js'
4
4
 
5
5
  interface ConfirmationPromptProps {
6
6
  prompt: string
7
7
  onConfirm: () => void
8
8
  onCancel: () => void
9
+ onEdit?: () => void // 新增:编辑回调
9
10
  }
10
11
 
11
12
  /**
12
13
  * ConfirmationPrompt 组件 - 单键确认提示
13
- * 回车 = 确认,Esc = 取消,Ctrl+C = 退出
14
+ * 回车 = 确认,E = 编辑,Esc = 取消,Ctrl+C = 退出
14
15
  */
15
16
  export const ConfirmationPrompt: React.FC<ConfirmationPromptProps> = ({
16
17
  prompt,
17
18
  onConfirm,
18
19
  onCancel,
20
+ onEdit,
19
21
  }) => {
22
+ const theme = getCurrentTheme()
20
23
  useInput((input, key) => {
21
24
  if (key.return) {
22
25
  // 回车键
@@ -24,6 +27,9 @@ export const ConfirmationPrompt: React.FC<ConfirmationPromptProps> = ({
24
27
  } else if (key.escape) {
25
28
  // Esc 键
26
29
  onCancel()
30
+ } else if ((input === 'e' || input === 'E') && onEdit) {
31
+ // E 键进入编辑模式
32
+ onEdit()
27
33
  } else if (key.ctrl && input === 'c') {
28
34
  // Ctrl+C
29
35
  process.exit(0)
@@ -35,7 +41,9 @@ export const ConfirmationPrompt: React.FC<ConfirmationPromptProps> = ({
35
41
  <Text bold color={theme.warning}>
36
42
  {prompt}
37
43
  </Text>
38
- <Text color={theme.text.secondary}> [回车执行 / Esc 取消] </Text>
44
+ <Text color={theme.text.secondary}>
45
+ {onEdit ? ' [回车执行 / E 编辑 / Esc 取消] ' : ' [回车执行 / Esc 取消] '}
46
+ </Text>
39
47
  </Text>
40
48
  )
41
49
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { Text } from 'ink'
3
- import { theme } from '../ui/theme.js'
3
+ import { getCurrentTheme } from '../ui/theme.js'
4
4
 
5
5
  interface DurationProps {
6
6
  ms: number
@@ -20,5 +20,6 @@ function formatDuration(ms: number): string {
20
20
  * Duration 组件 - 显示耗时
21
21
  */
22
22
  export const Duration: React.FC<DurationProps> = ({ ms }) => {
23
+ const theme = getCurrentTheme()
23
24
  return <Text color={theme.text.secondary}>({formatDuration(ms)})</Text>
24
25
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { Text } from 'ink'
3
- import { theme } from '../ui/theme.js'
3
+ import { getCurrentTheme } from '../ui/theme.js'
4
4
 
5
5
  interface RenderInlineProps {
6
6
  text: string
@@ -12,6 +12,7 @@ interface RenderInlineProps {
12
12
  * 处理 **粗体**、*斜体*、`代码`、~~删除线~~、<u>下划线</u>、链接
13
13
  */
14
14
  function RenderInlineInternal({ text, defaultColor }: RenderInlineProps) {
15
+ const theme = getCurrentTheme()
15
16
  const baseColor = defaultColor || theme.text.primary
16
17
 
17
18
  // 快速路径:纯文本无 markdown
@@ -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
  // 正则表达式
@@ -1,18 +1,22 @@
1
1
  import React, { useState, useEffect } from 'react'
2
- import { Box, Text } from 'ink'
2
+ import { Box, Text, useInput } from 'ink'
3
+ import TextInput from 'ink-text-input'
3
4
  import Spinner from 'ink-spinner'
4
- import { generateMultiStepCommand, type CommandStep, type ExecutedStep } from '../multi-step.js'
5
+ import { generateMultiStepCommand, type CommandStep, type ExecutedStep, type RemoteContext } from '../multi-step.js'
5
6
  import { detectBuiltin, formatBuiltins } from '../builtin-detector.js'
6
7
  import { CommandBox } from './CommandBox.js'
7
8
  import { ConfirmationPrompt } from './ConfirmationPrompt.js'
8
9
  import { Duration } from './Duration.js'
9
- import { theme } from '../ui/theme.js'
10
+ import { getCurrentTheme } from '../ui/theme.js'
11
+ import { getConfig } from '../config.js'
10
12
 
11
13
  interface MultiStepCommandGeneratorProps {
12
14
  prompt: string
13
15
  debug?: boolean
14
16
  onStepComplete: (step: {
15
17
  command: string
18
+ aiGeneratedCommand?: string // 新增:AI 生成的原始命令
19
+ userModified?: boolean // 新增:用户是否修改
16
20
  confirmed: boolean
17
21
  cancelled?: boolean
18
22
  hasBuiltin?: boolean
@@ -24,11 +28,14 @@ interface MultiStepCommandGeneratorProps {
24
28
  }) => void
25
29
  previousSteps?: ExecutedStep[]
26
30
  currentStepNumber?: number
31
+ remoteContext?: RemoteContext // 远程执行上下文
32
+ isRemote?: boolean // 是否为远程执行(远程执行时不检测 builtin)
27
33
  }
28
34
 
29
35
  type State =
30
36
  | { type: 'thinking' }
31
37
  | { type: 'showing_command'; stepData: CommandStep }
38
+ | { type: 'editing'; stepData: CommandStep } // 新增:编辑状态
32
39
  | { type: 'cancelled'; command: string }
33
40
  | { type: 'error'; error: string }
34
41
 
@@ -41,17 +48,31 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
41
48
  debug,
42
49
  previousSteps = [],
43
50
  currentStepNumber = 1,
51
+ remoteContext,
52
+ isRemote = false,
44
53
  onStepComplete,
45
54
  }) => {
55
+ const theme = getCurrentTheme()
46
56
  const [state, setState] = useState<State>({ type: 'thinking' })
47
57
  const [thinkDuration, setThinkDuration] = useState(0)
48
58
  const [debugInfo, setDebugInfo] = useState<any>(null)
59
+ const [editedCommand, setEditedCommand] = useState('') // 新增:编辑后的命令
60
+
61
+ // 监听编辑模式下的 Esc 键
62
+ useInput(
63
+ (input, key) => {
64
+ if (state.type === 'editing' && key.escape) {
65
+ handleEditCancel()
66
+ }
67
+ },
68
+ { isActive: state.type === 'editing' }
69
+ )
49
70
 
50
71
  // 初始化:调用 Mastra 生成命令
51
72
  useEffect(() => {
52
73
  const thinkStart = Date.now()
53
74
 
54
- generateMultiStepCommand(prompt, previousSteps, { debug })
75
+ generateMultiStepCommand(prompt, previousSteps, { debug, remoteContext })
55
76
  .then((result) => {
56
77
  const thinkEnd = Date.now()
57
78
  setThinkDuration(thinkEnd - thinkStart)
@@ -61,15 +82,29 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
61
82
  setDebugInfo(result.debugInfo)
62
83
  }
63
84
 
64
- setState({
65
- type: 'showing_command',
66
- stepData: result.stepData,
67
- })
85
+ // 如果 AI 返回空命令且决定不继续,说明 AI 放弃了
86
+ // 直接结束,不显示命令框
87
+ if (!result.stepData.command.trim() && result.stepData.continue === false) {
88
+ setTimeout(() => {
89
+ onStepComplete({
90
+ command: '',
91
+ confirmed: false,
92
+ reasoning: result.stepData.reasoning,
93
+ needsContinue: false,
94
+ })
95
+ }, 100)
96
+ return
97
+ }
68
98
 
69
- // 检测 builtin
99
+ // 检测 builtin(优先检测,但远程执行时跳过)
70
100
  const { hasBuiltin, builtins } = detectBuiltin(result.stepData.command)
71
101
 
72
- if (hasBuiltin) {
102
+ if (hasBuiltin && !isRemote) {
103
+ // 有 builtin 且是本地执行,不管什么模式都不编辑,直接提示
104
+ setState({
105
+ type: 'showing_command',
106
+ stepData: result.stepData,
107
+ })
73
108
  setTimeout(() => {
74
109
  onStepComplete({
75
110
  command: result.stepData.command,
@@ -80,6 +115,26 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
80
115
  needsContinue: result.stepData.continue,
81
116
  })
82
117
  }, 100)
118
+ return
119
+ }
120
+
121
+ // 根据 editMode 决定进入哪个状态
122
+ const config = getConfig()
123
+ const autoEdit = config.editMode === 'auto'
124
+
125
+ if (autoEdit) {
126
+ // auto 模式:直接进入编辑状态
127
+ setEditedCommand(result.stepData.command)
128
+ setState({
129
+ type: 'editing',
130
+ stepData: result.stepData,
131
+ })
132
+ } else {
133
+ // manual 模式:显示命令,等待用户操作
134
+ setState({
135
+ type: 'showing_command',
136
+ stepData: result.stepData,
137
+ })
83
138
  }
84
139
  })
85
140
  .catch((error: any) => {
@@ -92,13 +147,15 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
92
147
  })
93
148
  }, 100)
94
149
  })
95
- }, [prompt, previousSteps, debug])
150
+ }, [prompt, previousSteps, debug, remoteContext])
96
151
 
97
152
  // 处理确认
98
153
  const handleConfirm = () => {
99
154
  if (state.type === 'showing_command') {
100
155
  onStepComplete({
101
156
  command: state.stepData.command,
157
+ aiGeneratedCommand: state.stepData.command, // 原始命令
158
+ userModified: false,
102
159
  confirmed: true,
103
160
  reasoning: state.stepData.reasoning,
104
161
  needsContinue: state.stepData.continue,
@@ -108,6 +165,53 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
108
165
  }
109
166
  }
110
167
 
168
+ // 处理编辑
169
+ const handleEdit = () => {
170
+ if (state.type === 'showing_command') {
171
+ setEditedCommand(state.stepData.command) // 初始化为 AI 生成的命令
172
+ setState({ type: 'editing', stepData: state.stepData })
173
+ }
174
+ }
175
+
176
+ // 编辑完成确认
177
+ const handleEditConfirm = () => {
178
+ if (state.type === 'editing') {
179
+ const modified = editedCommand !== state.stepData.command
180
+ onStepComplete({
181
+ command: editedCommand, // 使用编辑后的命令
182
+ aiGeneratedCommand: state.stepData.command, // 保存 AI 原始命令
183
+ userModified: modified,
184
+ confirmed: true,
185
+ reasoning: state.stepData.reasoning,
186
+ needsContinue: state.stepData.continue,
187
+ nextStepHint: state.stepData.nextStepHint,
188
+ debugInfo: debugInfo,
189
+ })
190
+ }
191
+ }
192
+
193
+ // 取消编辑
194
+ const handleEditCancel = () => {
195
+ if (state.type === 'editing') {
196
+ const config = getConfig()
197
+
198
+ if (config.editMode === 'auto') {
199
+ // auto 模式:Esc 直接取消整个操作
200
+ setState({ type: 'cancelled', command: state.stepData.command })
201
+ setTimeout(() => {
202
+ onStepComplete({
203
+ command: state.stepData.command,
204
+ confirmed: false,
205
+ cancelled: true,
206
+ })
207
+ }, 100)
208
+ } else {
209
+ // manual 模式:Esc 返回到 showing_command 状态
210
+ setState({ type: 'showing_command', stepData: state.stepData })
211
+ }
212
+ }
213
+ }
214
+
111
215
  // 处理取消
112
216
  const handleCancel = () => {
113
217
  if (state.type === 'showing_command') {
@@ -129,7 +233,10 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
129
233
  <Box>
130
234
  <Text color={theme.info}>
131
235
  <Spinner type="dots" />{' '}
132
- {currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`}
236
+ {remoteContext
237
+ ? (currentStepNumber === 1 ? `正在为 ${remoteContext.name} 思考...` : `正在规划步骤 ${currentStepNumber} (${remoteContext.name})...`)
238
+ : (currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`)
239
+ }
133
240
  </Text>
134
241
  </Box>
135
242
  )}
@@ -163,6 +270,12 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
163
270
  </Box>
164
271
  )}
165
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
+
166
279
  <Box marginTop={1}>
167
280
  <Text color={theme.text.secondary}>AI 返回的 JSON:</Text>
168
281
  </Box>
@@ -188,8 +301,8 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
188
301
  {/* 命令框 */}
189
302
  <CommandBox command={state.stepData.command} />
190
303
 
191
- {/* Builtin 警告 */}
192
- {(() => {
304
+ {/* Builtin 警告(仅本地执行时显示) */}
305
+ {!isRemote && (() => {
193
306
  const { hasBuiltin, builtins } = detectBuiltin(state.stepData.command)
194
307
  if (hasBuiltin) {
195
308
  return (
@@ -205,9 +318,47 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
205
318
  })()}
206
319
 
207
320
  {/* 确认提示 */}
208
- {!detectBuiltin(state.stepData.command).hasBuiltin && (
209
- <ConfirmationPrompt prompt="执行?" onConfirm={handleConfirm} onCancel={handleCancel} />
321
+ {(isRemote || !detectBuiltin(state.stepData.command).hasBuiltin) && (
322
+ <ConfirmationPrompt
323
+ prompt="执行?"
324
+ onConfirm={handleConfirm}
325
+ onCancel={handleCancel}
326
+ onEdit={handleEdit} // 新增:编辑回调
327
+ />
328
+ )}
329
+ </>
330
+ )}
331
+
332
+ {/* 编辑模式 */}
333
+ {state.type === 'editing' && (
334
+ <>
335
+ {/* 步骤信息(仅多步骤时显示) */}
336
+ {state.stepData.continue === true && (
337
+ <Box flexDirection="column" marginTop={1}>
338
+ <Text color={theme.text.secondary}>步骤 {currentStepNumber}/?</Text>
339
+ {state.stepData.reasoning && (
340
+ <Text color={theme.text.muted}>原因: {state.stepData.reasoning}</Text>
341
+ )}
342
+ </Box>
210
343
  )}
344
+
345
+ {/* 命令框(AI 建议) */}
346
+ <CommandBox command={state.stepData.command} />
347
+
348
+ {/* 编辑框 */}
349
+ <Box flexDirection="row">
350
+ <Text color={theme.primary}>{'> '}</Text>
351
+ <TextInput
352
+ value={editedCommand}
353
+ onChange={setEditedCommand}
354
+ onSubmit={handleEditConfirm}
355
+ />
356
+ </Box>
357
+ <Box marginTop={1}>
358
+ <Text color={theme.text.secondary}>
359
+ {getConfig().editMode === 'auto' ? '[回车执行 / Esc 取消]' : '[回车执行 / Esc 返回]'}
360
+ </Text>
361
+ </Box>
211
362
  </>
212
363
  )}
213
364
 
@@ -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