@yivan-lab/pretty-please 1.3.1 → 1.5.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 +250 -620
- package/bin/pls.tsx +178 -40
- package/dist/bin/pls.js +149 -27
- package/dist/package.json +10 -2
- package/dist/src/__integration__/command-generation.test.d.ts +5 -0
- package/dist/src/__integration__/command-generation.test.js +508 -0
- package/dist/src/__integration__/error-recovery.test.d.ts +5 -0
- package/dist/src/__integration__/error-recovery.test.js +511 -0
- package/dist/src/__integration__/shell-hook-workflow.test.d.ts +5 -0
- package/dist/src/__integration__/shell-hook-workflow.test.js +375 -0
- package/dist/src/__tests__/alias.test.d.ts +5 -0
- package/dist/src/__tests__/alias.test.js +421 -0
- package/dist/src/__tests__/chat-history.test.d.ts +5 -0
- package/dist/src/__tests__/chat-history.test.js +372 -0
- package/dist/src/__tests__/config.test.d.ts +5 -0
- package/dist/src/__tests__/config.test.js +822 -0
- package/dist/src/__tests__/history.test.d.ts +5 -0
- package/dist/src/__tests__/history.test.js +439 -0
- package/dist/src/__tests__/remote-history.test.d.ts +5 -0
- package/dist/src/__tests__/remote-history.test.js +641 -0
- package/dist/src/__tests__/remote.test.d.ts +5 -0
- package/dist/src/__tests__/remote.test.js +689 -0
- package/dist/src/__tests__/shell-hook-install.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook-install.test.js +413 -0
- package/dist/src/__tests__/shell-hook-remote.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook-remote.test.js +507 -0
- package/dist/src/__tests__/shell-hook.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook.test.js +440 -0
- package/dist/src/__tests__/sysinfo.test.d.ts +5 -0
- package/dist/src/__tests__/sysinfo.test.js +572 -0
- package/dist/src/__tests__/system-history.test.d.ts +5 -0
- package/dist/src/__tests__/system-history.test.js +457 -0
- package/dist/src/components/Chat.js +9 -28
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +30 -2
- package/dist/src/mastra-chat.js +10 -6
- package/dist/src/multi-step.js +10 -8
- package/dist/src/project-context.d.ts +22 -0
- package/dist/src/project-context.js +168 -0
- package/dist/src/prompts.d.ts +4 -4
- package/dist/src/prompts.js +23 -6
- package/dist/src/shell-hook.d.ts +32 -0
- package/dist/src/shell-hook.js +226 -33
- package/dist/src/sysinfo.d.ts +38 -9
- package/dist/src/sysinfo.js +245 -21
- package/dist/src/system-history.d.ts +18 -0
- package/dist/src/system-history.js +151 -0
- package/dist/src/ui/__tests__/theme.test.d.ts +5 -0
- package/dist/src/ui/__tests__/theme.test.js +688 -0
- package/dist/src/upgrade.js +3 -0
- package/dist/src/user-preferences.d.ts +44 -0
- package/dist/src/user-preferences.js +147 -0
- package/dist/src/utils/__tests__/platform-capabilities.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-capabilities.test.js +214 -0
- package/dist/src/utils/__tests__/platform-exec.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-exec.test.js +212 -0
- package/dist/src/utils/__tests__/platform-shell.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-shell.test.js +300 -0
- package/dist/src/utils/__tests__/platform.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform.test.js +137 -0
- package/dist/src/utils/platform.d.ts +88 -0
- package/dist/src/utils/platform.js +331 -0
- package/package.json +10 -2
- package/src/__integration__/command-generation.test.ts +602 -0
- package/src/__integration__/error-recovery.test.ts +620 -0
- package/src/__integration__/shell-hook-workflow.test.ts +457 -0
- package/src/__tests__/alias.test.ts +545 -0
- package/src/__tests__/chat-history.test.ts +462 -0
- package/src/__tests__/config.test.ts +1043 -0
- package/src/__tests__/history.test.ts +538 -0
- package/src/__tests__/remote-history.test.ts +791 -0
- package/src/__tests__/remote.test.ts +866 -0
- package/src/__tests__/shell-hook-install.test.ts +510 -0
- package/src/__tests__/shell-hook-remote.test.ts +679 -0
- package/src/__tests__/shell-hook.test.ts +564 -0
- package/src/__tests__/sysinfo.test.ts +718 -0
- package/src/__tests__/system-history.test.ts +608 -0
- package/src/components/Chat.tsx +10 -37
- package/src/config.ts +29 -2
- package/src/mastra-chat.ts +12 -5
- package/src/multi-step.ts +11 -5
- package/src/project-context.ts +191 -0
- package/src/prompts.ts +26 -5
- package/src/shell-hook.ts +254 -32
- package/src/sysinfo.ts +326 -25
- package/src/system-history.ts +170 -0
- package/src/ui/__tests__/theme.test.ts +869 -0
- package/src/upgrade.ts +5 -0
- package/src/user-preferences.ts +178 -0
- package/src/utils/__tests__/platform-capabilities.test.ts +265 -0
- package/src/utils/__tests__/platform-exec.test.ts +278 -0
- package/src/utils/__tests__/platform-shell.test.ts +353 -0
- package/src/utils/__tests__/platform.test.ts +170 -0
- package/src/utils/platform.ts +431 -0
package/src/config.ts
CHANGED
|
@@ -12,7 +12,8 @@ function getColors() {
|
|
|
12
12
|
primary: theme.primary,
|
|
13
13
|
secondary: theme.secondary,
|
|
14
14
|
success: theme.success,
|
|
15
|
-
error: theme.error
|
|
15
|
+
error: theme.error,
|
|
16
|
+
warning: theme.warning,
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -82,11 +83,13 @@ export interface Config {
|
|
|
82
83
|
chatHistoryLimit: number
|
|
83
84
|
commandHistoryLimit: number
|
|
84
85
|
shellHistoryLimit: number
|
|
86
|
+
userPreferencesTopK: number // 用户偏好显示的命令数量(默认 20)
|
|
85
87
|
editMode: EditMode
|
|
86
88
|
theme: ThemeName
|
|
87
89
|
aliases: Record<string, AliasConfig>
|
|
88
90
|
remotes: Record<string, RemoteConfig> // 远程服务器配置
|
|
89
91
|
defaultRemote?: string // 默认远程服务器名称
|
|
92
|
+
systemCacheExpireDays?: number // 系统信息缓存过期天数(默认 7 天)
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
/**
|
|
@@ -101,11 +104,13 @@ const DEFAULT_CONFIG: Config = {
|
|
|
101
104
|
chatHistoryLimit: 5,
|
|
102
105
|
commandHistoryLimit: 5,
|
|
103
106
|
shellHistoryLimit: 10,
|
|
107
|
+
userPreferencesTopK: 20, // 默认显示 Top 20
|
|
104
108
|
editMode: 'manual',
|
|
105
109
|
theme: 'dark',
|
|
106
110
|
aliases: {},
|
|
107
111
|
remotes: {},
|
|
108
112
|
defaultRemote: '',
|
|
113
|
+
systemCacheExpireDays: 7,
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
/**
|
|
@@ -169,7 +174,7 @@ export function setConfigValue(key: string, value: string | boolean | number): C
|
|
|
169
174
|
// 处理特殊类型
|
|
170
175
|
if (key === 'shellHook') {
|
|
171
176
|
config.shellHook = value === 'true' || value === true
|
|
172
|
-
} else if (key === 'chatHistoryLimit' || key === 'commandHistoryLimit' || key === 'shellHistoryLimit') {
|
|
177
|
+
} else if (key === 'chatHistoryLimit' || key === 'commandHistoryLimit' || key === 'shellHistoryLimit' || key === 'userPreferencesTopK' || key === 'systemCacheExpireDays') {
|
|
173
178
|
const num = typeof value === 'number' ? value : parseInt(String(value), 10)
|
|
174
179
|
if (isNaN(num) || num < 1) {
|
|
175
180
|
throw new Error(`${key} 必须是大于 0 的整数`)
|
|
@@ -246,6 +251,10 @@ export function displayConfig(): void {
|
|
|
246
251
|
console.log(` ${chalk.hex(colors.primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
|
|
247
252
|
console.log(` ${chalk.hex(colors.primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
|
|
248
253
|
console.log(` ${chalk.hex(colors.primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
|
|
254
|
+
console.log(` ${chalk.hex(colors.primary)('userPreferencesTopK')}: ${config.userPreferencesTopK} 个`)
|
|
255
|
+
if (config.systemCacheExpireDays !== undefined) {
|
|
256
|
+
console.log(` ${chalk.hex(colors.primary)('systemCacheExpireDays')}: ${config.systemCacheExpireDays} 天`)
|
|
257
|
+
}
|
|
249
258
|
|
|
250
259
|
// 动态显示主题信息
|
|
251
260
|
const themeMetadata = getAllThemeMetadata().find((m) => m.name === config.theme)
|
|
@@ -354,6 +363,8 @@ export async function runConfigWizard(): Promise<void> {
|
|
|
354
363
|
const num = parseInt(chatHistoryLimit.trim(), 10)
|
|
355
364
|
if (!isNaN(num) && num > 0) {
|
|
356
365
|
config.chatHistoryLimit = num
|
|
366
|
+
} else {
|
|
367
|
+
console.log(chalk.hex(colors.warning)(' ⚠️ 输入无效,保持原值'))
|
|
357
368
|
}
|
|
358
369
|
}
|
|
359
370
|
|
|
@@ -364,6 +375,8 @@ export async function runConfigWizard(): Promise<void> {
|
|
|
364
375
|
const num = parseInt(commandHistoryLimit.trim(), 10)
|
|
365
376
|
if (!isNaN(num) && num > 0) {
|
|
366
377
|
config.commandHistoryLimit = num
|
|
378
|
+
} else {
|
|
379
|
+
console.log(chalk.hex(colors.warning)(' ⚠️ 输入无效,保持原值'))
|
|
367
380
|
}
|
|
368
381
|
}
|
|
369
382
|
|
|
@@ -375,6 +388,20 @@ export async function runConfigWizard(): Promise<void> {
|
|
|
375
388
|
const num = parseInt(shellHistoryLimit.trim(), 10)
|
|
376
389
|
if (!isNaN(num) && num > 0) {
|
|
377
390
|
config.shellHistoryLimit = num
|
|
391
|
+
} else {
|
|
392
|
+
console.log(chalk.hex(colors.warning)(' ⚠️ 输入无效,保持原值'))
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 10. User Preferences Top K
|
|
397
|
+
const userPrefsPrompt = `${chalk.hex(colors.primary)('用户偏好显示命令数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.userPreferencesTopK)} ${chalk.gray('→')} `
|
|
398
|
+
const userPrefsTopK = await question(rl, userPrefsPrompt)
|
|
399
|
+
if (userPrefsTopK.trim()) {
|
|
400
|
+
const num = parseInt(userPrefsTopK.trim(), 10)
|
|
401
|
+
if (!isNaN(num) && num > 0) {
|
|
402
|
+
config.userPreferencesTopK = num
|
|
403
|
+
} else {
|
|
404
|
+
console.log(chalk.hex(colors.warning)(' ⚠️ 输入无效,保持原值'))
|
|
378
405
|
}
|
|
379
406
|
}
|
|
380
407
|
|
package/src/mastra-chat.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Agent } from '@mastra/core'
|
|
2
2
|
import { getConfig } from './config.js'
|
|
3
3
|
import { CHAT_SYSTEM_PROMPT, buildChatUserContext } from './prompts.js'
|
|
4
|
-
import { formatSystemInfo } from './sysinfo.js'
|
|
4
|
+
import { formatSystemInfo, getSystemInfo } 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'
|
|
@@ -71,17 +71,24 @@ export async function chatWithMastra(
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// 3. 构建最新消息(动态上下文 + 用户问题)
|
|
74
|
-
const sysinfo = formatSystemInfo()
|
|
74
|
+
const sysinfo = formatSystemInfo(await getSystemInfo())
|
|
75
75
|
const plsHistory = formatHistoryForAI()
|
|
76
|
-
|
|
77
|
-
const
|
|
76
|
+
// 使用统一的历史获取接口(自动降级到系统历史)
|
|
77
|
+
const { formatShellHistoryForAIWithFallback } = await import('./shell-hook.js')
|
|
78
|
+
const shellHistory = formatShellHistoryForAIWithFallback()
|
|
79
|
+
const shellHookEnabled = !!shellHistory // 如果有 shell 历史就视为启用
|
|
80
|
+
|
|
81
|
+
// 获取用户偏好
|
|
82
|
+
const { formatUserPreferences } = await import('./user-preferences.js')
|
|
83
|
+
const userPreferencesStr = formatUserPreferences()
|
|
78
84
|
|
|
79
85
|
const latestUserContext = buildChatUserContext(
|
|
80
86
|
prompt,
|
|
81
87
|
sysinfo,
|
|
82
88
|
plsHistory,
|
|
83
89
|
shellHistory,
|
|
84
|
-
shellHookEnabled
|
|
90
|
+
shellHookEnabled,
|
|
91
|
+
userPreferencesStr
|
|
85
92
|
)
|
|
86
93
|
|
|
87
94
|
messages.push(latestUserContext)
|
package/src/multi-step.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { createShellAgent } from './mastra-agent.js'
|
|
3
3
|
import { SHELL_COMMAND_SYSTEM_PROMPT, buildUserContextPrompt } from './prompts.js'
|
|
4
|
-
import { formatSystemInfo } from './sysinfo.js'
|
|
4
|
+
import { formatSystemInfo, getSystemInfo } from './sysinfo.js'
|
|
5
5
|
import { formatHistoryForAI } from './history.js'
|
|
6
6
|
import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js'
|
|
7
7
|
import { getConfig, type RemoteSysInfo } from './config.js'
|
|
@@ -74,18 +74,24 @@ export async function generateMultiStepCommand(
|
|
|
74
74
|
historyStr = options.remoteContext.shellHistory.length > 0 ? shellHistory : plsHistory
|
|
75
75
|
} else {
|
|
76
76
|
// 本地执行:格式化本地系统信息和历史
|
|
77
|
-
sysinfoStr = formatSystemInfo()
|
|
78
|
-
const config = getConfig()
|
|
77
|
+
sysinfoStr = formatSystemInfo(await getSystemInfo())
|
|
79
78
|
const plsHistory = formatHistoryForAI()
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
// 使用统一的历史获取接口(自动降级到系统历史)
|
|
80
|
+
const { formatShellHistoryForAIWithFallback } = await import('./shell-hook.js')
|
|
81
|
+
const shellHistory = formatShellHistoryForAIWithFallback()
|
|
82
|
+
historyStr = shellHistory || plsHistory // 优先使用 shell 历史,降级到 pls 历史
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// 获取用户偏好
|
|
86
|
+
const { formatUserPreferences } = await import('./user-preferences.js')
|
|
87
|
+
const userPreferencesStr = formatUserPreferences()
|
|
88
|
+
|
|
84
89
|
// 构建包含所有动态数据的 User Prompt(XML 格式)
|
|
85
90
|
const userContextPrompt = buildUserContextPrompt(
|
|
86
91
|
userPrompt,
|
|
87
92
|
sysinfoStr,
|
|
88
93
|
historyStr,
|
|
94
|
+
userPreferencesStr,
|
|
89
95
|
previousSteps
|
|
90
96
|
)
|
|
91
97
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { detect as detectPM } from 'detect-package-manager'
|
|
4
|
+
import { execSync } from 'child_process'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 项目上下文信息
|
|
8
|
+
*/
|
|
9
|
+
export interface ProjectContext {
|
|
10
|
+
types: string[] // 项目类型 ['nodejs', 'docker', 'git']
|
|
11
|
+
packageManager?: string // 包管理器 'pnpm' | 'yarn' | 'bun' | 'npm' | 'uv' | 'poetry' | 'cargo'
|
|
12
|
+
git?: {
|
|
13
|
+
branch: string // Git 分支名
|
|
14
|
+
status: 'clean' | 'dirty' | 'unknown'
|
|
15
|
+
}
|
|
16
|
+
scripts?: string[] // 可用脚本(如 package.json scripts)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 检测项目上下文(优化版:< 30ms)
|
|
21
|
+
* 若未识别到任何特征,返回 null
|
|
22
|
+
*/
|
|
23
|
+
export async function detectProjectContext(cwd: string): Promise<ProjectContext | null> {
|
|
24
|
+
const types: string[] = []
|
|
25
|
+
let packageManager: string | undefined
|
|
26
|
+
let git: ProjectContext['git'] | undefined
|
|
27
|
+
let scripts: string[] | undefined
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// 1. 快速读取目录文件列表(同步,< 5ms)
|
|
31
|
+
const files = fs.readdirSync(cwd)
|
|
32
|
+
|
|
33
|
+
// 2. 检测项目类型(纯文件检查,< 10ms)
|
|
34
|
+
if (files.includes('package.json')) {
|
|
35
|
+
types.push('nodejs')
|
|
36
|
+
|
|
37
|
+
// 检测 Node.js 包管理器
|
|
38
|
+
try {
|
|
39
|
+
packageManager = await detectPM({ cwd })
|
|
40
|
+
} catch {
|
|
41
|
+
// 降级:根据 lock 文件推断
|
|
42
|
+
if (files.includes('pnpm-lock.yaml')) packageManager = 'pnpm'
|
|
43
|
+
else if (files.includes('yarn.lock')) packageManager = 'yarn'
|
|
44
|
+
else if (files.includes('bun.lockb')) packageManager = 'bun'
|
|
45
|
+
else packageManager = 'npm'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 读取 package.json scripts(最多 5 个)
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'))
|
|
51
|
+
if (pkg.scripts) {
|
|
52
|
+
scripts = Object.keys(pkg.scripts).slice(0, 5)
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// 忽略读取错误
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Python
|
|
60
|
+
if (files.some(f => ['pyproject.toml', 'requirements.txt', 'Pipfile', 'uv.lock', 'poetry.lock'].includes(f))) {
|
|
61
|
+
types.push('python')
|
|
62
|
+
if (files.includes('uv.lock')) packageManager = 'uv'
|
|
63
|
+
else if (files.includes('poetry.lock')) packageManager = 'poetry'
|
|
64
|
+
else if (files.includes('Pipfile')) packageManager = 'pipenv'
|
|
65
|
+
else packageManager = 'pip'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Rust
|
|
69
|
+
if (files.includes('Cargo.toml')) {
|
|
70
|
+
types.push('rust')
|
|
71
|
+
packageManager = 'cargo'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Go
|
|
75
|
+
if (files.includes('go.mod')) {
|
|
76
|
+
types.push('go')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Docker
|
|
80
|
+
if (files.some(f => f.includes('docker') || f === 'Dockerfile' || f === 'compose.yaml')) {
|
|
81
|
+
types.push('docker')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Makefile
|
|
85
|
+
if (files.includes('Makefile')) {
|
|
86
|
+
types.push('make')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 3. Git 检测优化(< 20ms)
|
|
90
|
+
if (files.includes('.git') || fs.existsSync(path.join(cwd, '.git'))) {
|
|
91
|
+
types.push('git')
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// 读取 .git/HEAD 文件获取分支(最快,< 5ms)
|
|
95
|
+
const headFile = fs.readFileSync(path.join(cwd, '.git/HEAD'), 'utf-8')
|
|
96
|
+
const refMatch = headFile.match(/ref: refs\/heads\/(.+)/)
|
|
97
|
+
|
|
98
|
+
if (refMatch) {
|
|
99
|
+
// 正常分支
|
|
100
|
+
git = {
|
|
101
|
+
branch: refMatch[1].trim(),
|
|
102
|
+
status: quickGitStatus(cwd),
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// detached HEAD 状态(直接 commit hash)
|
|
106
|
+
const shortHash = headFile.trim().substring(0, 7)
|
|
107
|
+
git = {
|
|
108
|
+
branch: `HEAD (${shortHash})`,
|
|
109
|
+
status: quickGitStatus(cwd),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Git 检测失败,只标记类型,不提供详细信息
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// 检测失败,返回空上下文
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const hasContext =
|
|
122
|
+
types.length > 0 ||
|
|
123
|
+
!!packageManager ||
|
|
124
|
+
!!git ||
|
|
125
|
+
(scripts && scripts.length > 0)
|
|
126
|
+
|
|
127
|
+
if (!hasContext) {
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { types, packageManager, git, scripts }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Git status 快速检测(带超时)
|
|
136
|
+
*/
|
|
137
|
+
function quickGitStatus(cwd: string): 'clean' | 'dirty' | 'unknown' {
|
|
138
|
+
try {
|
|
139
|
+
// 使用 git status --porcelain,带超时保护
|
|
140
|
+
const result = execSync('git status --porcelain', {
|
|
141
|
+
cwd,
|
|
142
|
+
stdio: 'pipe',
|
|
143
|
+
timeout: 100, // 100ms 超时
|
|
144
|
+
encoding: 'utf-8',
|
|
145
|
+
})
|
|
146
|
+
return result.trim() === '' ? 'clean' : 'dirty'
|
|
147
|
+
} catch {
|
|
148
|
+
// 超时或失败,返回 unknown
|
|
149
|
+
return 'unknown'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 格式化项目上下文为字符串(供 AI 使用)
|
|
155
|
+
* 格式:当前项目: nodejs+docker+git | pnpm | Git分支: main (有改动) | 脚本: dev, build, test
|
|
156
|
+
*/
|
|
157
|
+
export function formatProjectContext(project: ProjectContext): string {
|
|
158
|
+
if (project.types.length === 0) {
|
|
159
|
+
return ''
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const parts: string[] = []
|
|
163
|
+
|
|
164
|
+
// 项目类型
|
|
165
|
+
parts.push(`当前项目: ${project.types.join('+')}`)
|
|
166
|
+
|
|
167
|
+
// 包管理器
|
|
168
|
+
if (project.packageManager) {
|
|
169
|
+
parts.push(` | ${project.packageManager}`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Git 信息
|
|
173
|
+
if (project.git) {
|
|
174
|
+
let statusDesc = ''
|
|
175
|
+
if (project.git.status === 'clean') {
|
|
176
|
+
statusDesc = '干净'
|
|
177
|
+
} else if (project.git.status === 'dirty') {
|
|
178
|
+
statusDesc = '有改动'
|
|
179
|
+
} else {
|
|
180
|
+
statusDesc = '未知'
|
|
181
|
+
}
|
|
182
|
+
parts.push(` | Git分支: ${project.git.branch} (${statusDesc})`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 可用脚本
|
|
186
|
+
if (project.scripts && project.scripts.length > 0) {
|
|
187
|
+
parts.push(` | 脚本: ${project.scripts.join(', ')}`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return parts.join('')
|
|
191
|
+
}
|
package/src/prompts.ts
CHANGED
|
@@ -14,8 +14,11 @@ export const SHELL_COMMAND_SYSTEM_PROMPT = `你是一个专业的 shell 脚本
|
|
|
14
14
|
|
|
15
15
|
### 📋 输入数据格式说明
|
|
16
16
|
你会收到以下 XML 标签包裹的上下文信息:
|
|
17
|
-
- <system_info>:用户的操作系统、Shell
|
|
17
|
+
- <system_info>:用户的操作系统、Shell 类型、当前目录、包管理器、可用工具等环境信息
|
|
18
18
|
- <command_history>:用户最近执行的命令历史(用于理解上下文引用,如"刚才的文件"、"上一个命令")
|
|
19
|
+
- <user_preferences>:**用户的命令使用偏好**(格式:命令名(使用次数)),帮助你了解用户习惯
|
|
20
|
+
- 例如:git(234), eza(156) 表示用户经常使用 git 和 eza 命令
|
|
21
|
+
- 生成命令时可参考偏好,但最终应结合任务需求和 <system_info> 综合判断
|
|
19
22
|
- <execution_log>:**多步骤任务的关键信息**,记录了之前步骤的命令、退出码和输出结果
|
|
20
23
|
- 如果存在此标签,说明这是一个多步骤任务
|
|
21
24
|
- 必须检查每个 <step> 中的 <exit_code>,0=成功,非0=失败
|
|
@@ -29,6 +32,7 @@ export const SHELL_COMMAND_SYSTEM_PROMPT = `你是一个专业的 shell 脚本
|
|
|
29
32
|
4. 根据 <system_info> 中的信息选择合适的命令(如包管理器)
|
|
30
33
|
5. 如果用户引用了之前的操作(如"刚才的"、"上一个"),请参考 <command_history>
|
|
31
34
|
6. 绝对不要输出 pls 或 please 命令!
|
|
35
|
+
7. **建议优先使用标准命令**(ls/find/grep/cat/ps)以确保兼容性和输出捕获,而不是使用 eza/bat/delta 等现代工具,除非用户明确要求使用相关工具,或者用户特别偏好使用相关工具。
|
|
32
36
|
|
|
33
37
|
### 📤 输出格式 - 非常重要
|
|
34
38
|
|
|
@@ -189,6 +193,7 @@ export function buildUserContextPrompt(
|
|
|
189
193
|
userRequest: string,
|
|
190
194
|
sysInfoStr: string,
|
|
191
195
|
historyStr: string,
|
|
196
|
+
userPreferencesStr: string,
|
|
192
197
|
executedSteps: Array<{ command: string; exitCode: number; output: string }>
|
|
193
198
|
): string {
|
|
194
199
|
const parts: string[] = []
|
|
@@ -205,7 +210,14 @@ export function buildUserContextPrompt(
|
|
|
205
210
|
parts.push(`</command_history>`)
|
|
206
211
|
}
|
|
207
212
|
|
|
208
|
-
// 3.
|
|
213
|
+
// 3. 用户偏好(如果有)
|
|
214
|
+
if (userPreferencesStr && userPreferencesStr.trim()) {
|
|
215
|
+
parts.push(`<user_preferences>`)
|
|
216
|
+
parts.push(userPreferencesStr)
|
|
217
|
+
parts.push(`</user_preferences>`)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 4. 执行日志(多步骤的核心,紧凑 XML 结构)
|
|
209
221
|
if (executedSteps && executedSteps.length > 0) {
|
|
210
222
|
parts.push(`<execution_log>`)
|
|
211
223
|
executedSteps.forEach((step, i) => {
|
|
@@ -230,7 +242,7 @@ export function buildUserContextPrompt(
|
|
|
230
242
|
parts.push(`⚠️ 注意:请检查 <execution_log> 中最后一步的 <exit_code>。如果非 0,请分析 <output> 并修复命令。`)
|
|
231
243
|
}
|
|
232
244
|
|
|
233
|
-
//
|
|
245
|
+
// 5. 用户需求
|
|
234
246
|
parts.push(`<user_request>`)
|
|
235
247
|
parts.push(userRequest)
|
|
236
248
|
parts.push(`</user_request>`)
|
|
@@ -251,6 +263,7 @@ export const CHAT_SYSTEM_PROMPT = `你是一个命令行专家助手,帮助用
|
|
|
251
263
|
- <system_info>:用户的操作系统、Shell 类型、当前目录等环境信息
|
|
252
264
|
- <command_history>:用户最近通过 pls 执行的命令(用于理解上下文引用)
|
|
253
265
|
- <shell_history>:用户最近在终端执行的所有命令(如果启用了 Shell Hook)
|
|
266
|
+
- <user_preferences>:用户的命令使用偏好(命令名(使用次数)),帮助你了解用户习惯
|
|
254
267
|
- <user_question>:用户的具体问题
|
|
255
268
|
|
|
256
269
|
### 🎯 你的能力
|
|
@@ -283,7 +296,8 @@ export function buildChatUserContext(
|
|
|
283
296
|
sysInfoStr: string,
|
|
284
297
|
plsHistory: string,
|
|
285
298
|
shellHistory: string,
|
|
286
|
-
shellHookEnabled: boolean
|
|
299
|
+
shellHookEnabled: boolean,
|
|
300
|
+
userPreferencesStr?: string
|
|
287
301
|
): string {
|
|
288
302
|
const parts: string[] = []
|
|
289
303
|
|
|
@@ -303,7 +317,14 @@ export function buildChatUserContext(
|
|
|
303
317
|
parts.push('</command_history>')
|
|
304
318
|
}
|
|
305
319
|
|
|
306
|
-
// 3.
|
|
320
|
+
// 3. 用户偏好(如果有)
|
|
321
|
+
if (userPreferencesStr && userPreferencesStr.trim()) {
|
|
322
|
+
parts.push('<user_preferences>')
|
|
323
|
+
parts.push(userPreferencesStr)
|
|
324
|
+
parts.push('</user_preferences>')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 4. 用户问题
|
|
307
328
|
parts.push('<user_question>')
|
|
308
329
|
parts.push(userQuestion)
|
|
309
330
|
parts.push('</user_question>')
|