@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.
- package/README.md +390 -1
- package/bin/pls.tsx +1255 -123
- package/dist/bin/pls.js +1098 -103
- package/dist/package.json +4 -4
- package/dist/src/alias.d.ts +41 -0
- package/dist/src/alias.js +240 -0
- package/dist/src/chat-history.js +10 -1
- package/dist/src/components/Chat.js +54 -26
- package/dist/src/components/CodeColorizer.js +26 -20
- package/dist/src/components/CommandBox.js +19 -8
- package/dist/src/components/ConfirmationPrompt.js +2 -1
- package/dist/src/components/Duration.js +2 -1
- package/dist/src/components/InlineRenderer.js +2 -1
- package/dist/src/components/MarkdownDisplay.js +2 -1
- package/dist/src/components/MultiStepCommandGenerator.d.ts +3 -1
- package/dist/src/components/MultiStepCommandGenerator.js +20 -10
- package/dist/src/components/TableRenderer.js +2 -1
- package/dist/src/config.d.ts +33 -3
- package/dist/src/config.js +83 -34
- package/dist/src/mastra-agent.d.ts +1 -0
- package/dist/src/mastra-agent.js +3 -11
- package/dist/src/mastra-chat.d.ts +13 -6
- package/dist/src/mastra-chat.js +31 -31
- package/dist/src/multi-step.d.ts +23 -7
- package/dist/src/multi-step.js +45 -26
- package/dist/src/prompts.d.ts +30 -4
- package/dist/src/prompts.js +218 -70
- package/dist/src/remote-history.d.ts +63 -0
- package/dist/src/remote-history.js +315 -0
- package/dist/src/remote.d.ts +113 -0
- package/dist/src/remote.js +634 -0
- package/dist/src/shell-hook.d.ts +58 -0
- package/dist/src/shell-hook.js +295 -26
- package/dist/src/ui/theme.d.ts +60 -23
- package/dist/src/ui/theme.js +544 -22
- package/dist/src/upgrade.d.ts +41 -0
- package/dist/src/upgrade.js +348 -0
- package/dist/src/utils/console.d.ts +4 -0
- package/dist/src/utils/console.js +89 -17
- package/package.json +4 -4
- package/src/alias.ts +301 -0
- package/src/chat-history.ts +11 -1
- package/src/components/Chat.tsx +71 -26
- package/src/components/CodeColorizer.tsx +27 -19
- package/src/components/CommandBox.tsx +26 -8
- package/src/components/ConfirmationPrompt.tsx +2 -1
- package/src/components/Duration.tsx +2 -1
- package/src/components/InlineRenderer.tsx +2 -1
- package/src/components/MarkdownDisplay.tsx +2 -1
- package/src/components/MultiStepCommandGenerator.tsx +25 -11
- package/src/components/TableRenderer.tsx +2 -1
- package/src/config.ts +126 -35
- package/src/mastra-agent.ts +3 -12
- package/src/mastra-chat.ts +40 -34
- package/src/multi-step.ts +62 -30
- package/src/prompts.ts +236 -78
- package/src/remote-history.ts +390 -0
- package/src/remote.ts +800 -0
- package/src/shell-hook.ts +339 -26
- package/src/ui/theme.ts +632 -23
- package/src/upgrade.ts +397 -0
- 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 {
|
|
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 {
|
|
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
|
-
{
|
|
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 {
|
|
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:
|
|
55
|
-
commandHistoryLimit:
|
|
56
|
-
shellHistoryLimit:
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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 === '
|
|
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.
|
|
158
|
-
console.log(` ${chalk.
|
|
159
|
-
console.log(` ${chalk.
|
|
160
|
-
console.log(` ${chalk.
|
|
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.
|
|
239
|
+
` ${chalk.hex(colors.primary)('shellHook')}: ${config.shellHook ? chalk.hex(colors.success)('已启用') : chalk.gray('未启用')}`
|
|
163
240
|
)
|
|
164
241
|
console.log(
|
|
165
|
-
` ${chalk.
|
|
166
|
-
config.editMode === 'auto' ? chalk.hex(
|
|
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.
|
|
170
|
-
console.log(` ${chalk.
|
|
171
|
-
console.log(` ${chalk.
|
|
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(
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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(
|
|
395
|
+
console.log(chalk.hex(getColors().error)(`\n✗ 配置失败: ${message}`))
|
|
305
396
|
console.log()
|
|
306
397
|
} finally {
|
|
307
398
|
rl.close()
|
package/src/mastra-agent.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Agent } from '@mastra/core'
|
|
2
2
|
import { getConfig } from './config.js'
|
|
3
|
-
import {
|
|
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:
|
|
18
|
+
instructions: SHELL_COMMAND_SYSTEM_PROMPT,
|
|
28
19
|
model: {
|
|
29
20
|
url: config.baseUrl,
|
|
30
21
|
id: modelId,
|
package/src/mastra-chat.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Agent } from '@mastra/core'
|
|
2
2
|
import { getConfig } from './config.js'
|
|
3
|
-
import {
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
120
|
+
sysinfo,
|
|
115
121
|
model: config.model,
|
|
116
|
-
systemPrompt:
|
|
122
|
+
systemPrompt: CHAT_SYSTEM_PROMPT,
|
|
117
123
|
chatHistory,
|
|
118
|
-
|
|
124
|
+
userContext: latestUserContext,
|
|
119
125
|
},
|
|
120
126
|
}
|
|
121
127
|
}
|