@yivan-lab/pretty-please 1.0.0 → 1.1.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 +98 -27
- package/bin/pls.tsx +135 -24
- package/dist/bin/pls.d.ts +1 -1
- package/dist/bin/pls.js +117 -24
- package/dist/package.json +80 -0
- package/dist/src/ai.d.ts +1 -41
- package/dist/src/ai.js +9 -190
- package/dist/src/builtin-detector.d.ts +14 -8
- package/dist/src/builtin-detector.js +36 -16
- package/dist/src/chat-history.d.ts +16 -11
- package/dist/src/chat-history.js +26 -4
- package/dist/src/components/Chat.js +3 -3
- package/dist/src/components/CommandBox.js +1 -16
- package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
- package/dist/src/components/ConfirmationPrompt.js +7 -3
- package/dist/src/components/MultiStepCommandGenerator.d.ts +2 -0
- package/dist/src/components/MultiStepCommandGenerator.js +110 -7
- package/dist/src/config.d.ts +27 -8
- package/dist/src/config.js +92 -33
- package/dist/src/history.d.ts +19 -5
- package/dist/src/history.js +26 -11
- package/dist/src/mastra-agent.d.ts +0 -1
- package/dist/src/mastra-agent.js +3 -4
- package/dist/src/mastra-chat.d.ts +28 -0
- package/dist/src/mastra-chat.js +93 -0
- package/dist/src/multi-step.d.ts +2 -2
- package/dist/src/multi-step.js +2 -2
- package/dist/src/prompts.d.ts +11 -0
- package/dist/src/prompts.js +140 -0
- package/dist/src/shell-hook.d.ts +35 -13
- package/dist/src/shell-hook.js +82 -7
- package/dist/src/sysinfo.d.ts +9 -5
- package/dist/src/sysinfo.js +2 -2
- package/dist/src/utils/console.d.ts +11 -11
- package/dist/src/utils/console.js +4 -6
- package/package.json +8 -6
- package/src/builtin-detector.ts +126 -0
- package/src/chat-history.ts +130 -0
- package/src/components/Chat.tsx +4 -4
- package/src/components/CommandBox.tsx +1 -16
- package/src/components/ConfirmationPrompt.tsx +9 -2
- package/src/components/MultiStepCommandGenerator.tsx +144 -7
- package/src/config.ts +309 -0
- package/src/history.ts +160 -0
- package/src/mastra-agent.ts +3 -4
- package/src/mastra-chat.ts +124 -0
- package/src/multi-step.ts +2 -2
- package/src/prompts.ts +154 -0
- package/src/shell-hook.ts +502 -0
- package/src/{sysinfo.js → sysinfo.ts} +28 -16
- package/src/utils/{console.js → console.ts} +16 -18
- package/bin/pls.js +0 -681
- package/src/ai.js +0 -324
- package/src/builtin-detector.js +0 -98
- package/src/chat-history.js +0 -94
- package/src/components/ChatStatus.tsx +0 -53
- package/src/components/CommandGenerator.tsx +0 -184
- package/src/components/ConfigDisplay.tsx +0 -64
- package/src/components/ConfigWizard.tsx +0 -101
- package/src/components/HistoryDisplay.tsx +0 -69
- package/src/components/HookManager.tsx +0 -150
- package/src/config.js +0 -221
- package/src/history.js +0 -131
- package/src/shell-hook.js +0 -393
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import Spinner from 'ink-spinner'
|
|
4
|
-
import { generateCommand } from '../ai.js'
|
|
5
|
-
import { detectBuiltin, formatBuiltins } from '../builtin-detector.js'
|
|
6
|
-
import { CommandBox } from './CommandBox.js'
|
|
7
|
-
import { ConfirmationPrompt } from './ConfirmationPrompt.js'
|
|
8
|
-
import { Duration } from './Duration.js'
|
|
9
|
-
import { theme } from '../ui/theme.js'
|
|
10
|
-
|
|
11
|
-
interface CommandGeneratorProps {
|
|
12
|
-
prompt: string
|
|
13
|
-
debug?: boolean
|
|
14
|
-
onComplete: (result: {
|
|
15
|
-
command?: string
|
|
16
|
-
confirmed?: boolean
|
|
17
|
-
cancelled?: boolean
|
|
18
|
-
hasBuiltin?: boolean
|
|
19
|
-
builtins?: string[]
|
|
20
|
-
debugInfo?: any
|
|
21
|
-
error?: string
|
|
22
|
-
}) => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type State =
|
|
26
|
-
| { type: 'thinking' }
|
|
27
|
-
| { type: 'showing_command'; command: string; hasBuiltin: boolean; builtins: string[] }
|
|
28
|
-
| { type: 'cancelled'; command: string }
|
|
29
|
-
| { type: 'error'; error: string }
|
|
30
|
-
|
|
31
|
-
interface DebugInfo {
|
|
32
|
-
sysinfo: string
|
|
33
|
-
model: string
|
|
34
|
-
systemPrompt: string
|
|
35
|
-
userPrompt: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* CommandGenerator 组件 - 命令生成和确认(仅用于交互)
|
|
40
|
-
* 不执行命令,执行交给调用方用原生方式处理
|
|
41
|
-
*/
|
|
42
|
-
export const CommandGenerator: React.FC<CommandGeneratorProps> = ({ prompt, debug, onComplete }) => {
|
|
43
|
-
const [state, setState] = useState<State>({ type: 'thinking' })
|
|
44
|
-
const [thinkDuration, setThinkDuration] = useState(0)
|
|
45
|
-
const [debugInfo, setDebugInfo] = useState<DebugInfo | null>(null)
|
|
46
|
-
|
|
47
|
-
// 初始化:调用 AI 生成命令
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
const thinkStart = Date.now()
|
|
50
|
-
|
|
51
|
-
generateCommand(prompt, { debug: debug || false })
|
|
52
|
-
.then((result: any) => {
|
|
53
|
-
const command = debug && typeof result === 'object' ? result.command : result
|
|
54
|
-
const thinkEnd = Date.now()
|
|
55
|
-
setThinkDuration(thinkEnd - thinkStart)
|
|
56
|
-
|
|
57
|
-
if (debug && typeof result === 'object' && 'debug' in result) {
|
|
58
|
-
setDebugInfo(result.debug)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 检测 builtin
|
|
62
|
-
const { hasBuiltin, builtins } = detectBuiltin(command)
|
|
63
|
-
|
|
64
|
-
setState({
|
|
65
|
-
type: 'showing_command',
|
|
66
|
-
command,
|
|
67
|
-
hasBuiltin,
|
|
68
|
-
builtins,
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
// 如果是 builtin,直接完成(不执行)
|
|
72
|
-
if (hasBuiltin) {
|
|
73
|
-
setTimeout(() => {
|
|
74
|
-
onComplete({
|
|
75
|
-
command,
|
|
76
|
-
confirmed: false,
|
|
77
|
-
hasBuiltin: true,
|
|
78
|
-
builtins,
|
|
79
|
-
debugInfo: debugInfo || undefined,
|
|
80
|
-
})
|
|
81
|
-
}, 100)
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
.catch((error: any) => {
|
|
85
|
-
setState({ type: 'error', error: error.message })
|
|
86
|
-
setTimeout(() => {
|
|
87
|
-
onComplete({ error: error.message })
|
|
88
|
-
}, 100)
|
|
89
|
-
})
|
|
90
|
-
}, [prompt, debug])
|
|
91
|
-
|
|
92
|
-
// 处理确认
|
|
93
|
-
const handleConfirm = () => {
|
|
94
|
-
if (state.type === 'showing_command') {
|
|
95
|
-
// 返回命令和确认状态,让调用方执行
|
|
96
|
-
onComplete({
|
|
97
|
-
command: state.command,
|
|
98
|
-
confirmed: true,
|
|
99
|
-
debugInfo: debugInfo || undefined,
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 处理取消
|
|
105
|
-
const handleCancel = () => {
|
|
106
|
-
if (state.type === 'showing_command') {
|
|
107
|
-
setState({ type: 'cancelled', command: state.command })
|
|
108
|
-
setTimeout(() => {
|
|
109
|
-
onComplete({
|
|
110
|
-
command: state.command,
|
|
111
|
-
cancelled: true,
|
|
112
|
-
})
|
|
113
|
-
}, 100)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<Box flexDirection="column">
|
|
119
|
-
{/* 思考阶段 */}
|
|
120
|
-
{state.type === 'thinking' && (
|
|
121
|
-
<Box>
|
|
122
|
-
<Text color={theme.info}>
|
|
123
|
-
<Spinner type="dots" /> 正在思考...
|
|
124
|
-
</Text>
|
|
125
|
-
</Box>
|
|
126
|
-
)}
|
|
127
|
-
|
|
128
|
-
{/* 思考完成 */}
|
|
129
|
-
{state.type !== 'thinking' && thinkDuration > 0 && (
|
|
130
|
-
<Box>
|
|
131
|
-
<Text color={theme.success}>✓ 思考完成 </Text>
|
|
132
|
-
<Duration ms={thinkDuration} />
|
|
133
|
-
</Box>
|
|
134
|
-
)}
|
|
135
|
-
|
|
136
|
-
{/* 调试信息 */}
|
|
137
|
-
{debugInfo && (
|
|
138
|
-
<Box flexDirection="column" marginY={1}>
|
|
139
|
-
<Text color={theme.accent}>━━━ 调试信息 ━━━</Text>
|
|
140
|
-
<Text color={theme.text.secondary}>系统信息: {debugInfo.sysinfo}</Text>
|
|
141
|
-
<Text color={theme.text.secondary}>模型: {debugInfo.model}</Text>
|
|
142
|
-
<Text color={theme.text.secondary}>System Prompt:</Text>
|
|
143
|
-
<Text dimColor>{debugInfo.systemPrompt}</Text>
|
|
144
|
-
<Text color={theme.text.secondary}>User Prompt: {debugInfo.userPrompt}</Text>
|
|
145
|
-
<Text color={theme.accent}>━━━━━━━━━━━━━━━━</Text>
|
|
146
|
-
</Box>
|
|
147
|
-
)}
|
|
148
|
-
|
|
149
|
-
{/* 显示命令 */}
|
|
150
|
-
{(state.type === 'showing_command' || state.type === 'cancelled') && (
|
|
151
|
-
<CommandBox command={state.command} />
|
|
152
|
-
)}
|
|
153
|
-
|
|
154
|
-
{/* Builtin 警告 */}
|
|
155
|
-
{state.type === 'showing_command' && state.hasBuiltin && (
|
|
156
|
-
<Box flexDirection="column" marginY={1}>
|
|
157
|
-
<Text color={theme.error}>
|
|
158
|
-
⚠️ 此命令包含 shell 内置命令({formatBuiltins(state.builtins)}),无法在子进程中生效
|
|
159
|
-
</Text>
|
|
160
|
-
<Text color={theme.warning}>💡 请手动复制到终端执行</Text>
|
|
161
|
-
</Box>
|
|
162
|
-
)}
|
|
163
|
-
|
|
164
|
-
{/* 确认提示 */}
|
|
165
|
-
{state.type === 'showing_command' && !state.hasBuiltin && (
|
|
166
|
-
<ConfirmationPrompt prompt="执行?" onConfirm={handleConfirm} onCancel={handleCancel} />
|
|
167
|
-
)}
|
|
168
|
-
|
|
169
|
-
{/* 取消 */}
|
|
170
|
-
{state.type === 'cancelled' && (
|
|
171
|
-
<Box marginTop={1}>
|
|
172
|
-
<Text color={theme.text.secondary}>已取消执行</Text>
|
|
173
|
-
</Box>
|
|
174
|
-
)}
|
|
175
|
-
|
|
176
|
-
{/* 错误 */}
|
|
177
|
-
{state.type === 'error' && (
|
|
178
|
-
<Box marginTop={1}>
|
|
179
|
-
<Text color={theme.error}>❌ 错误: {state.error}</Text>
|
|
180
|
-
</Box>
|
|
181
|
-
)}
|
|
182
|
-
</Box>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import { getConfig, maskApiKey } from '../config.js'
|
|
4
|
-
import { theme } from '../ui/theme.js'
|
|
5
|
-
import path from 'path'
|
|
6
|
-
import os from 'os'
|
|
7
|
-
|
|
8
|
-
const CONFIG_FILE = path.join(os.homedir(), '.please', 'config.json')
|
|
9
|
-
|
|
10
|
-
interface ConfigDisplayProps {
|
|
11
|
-
onComplete?: () => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* ConfigDisplay 组件 - 显示当前配置
|
|
16
|
-
*/
|
|
17
|
-
export const ConfigDisplay: React.FC<ConfigDisplayProps> = ({ onComplete }) => {
|
|
18
|
-
const config = getConfig()
|
|
19
|
-
|
|
20
|
-
React.useEffect(() => {
|
|
21
|
-
if (onComplete) {
|
|
22
|
-
setTimeout(onComplete, 100)
|
|
23
|
-
}
|
|
24
|
-
}, [onComplete])
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<Box flexDirection="column" marginY={1}>
|
|
28
|
-
<Text bold>当前配置:</Text>
|
|
29
|
-
<Text color={theme.text.secondary}>{'━'.repeat(40)}</Text>
|
|
30
|
-
|
|
31
|
-
<Box>
|
|
32
|
-
<Text color={theme.primary}> apiKey: </Text>
|
|
33
|
-
<Text>{maskApiKey(config.apiKey)}</Text>
|
|
34
|
-
</Box>
|
|
35
|
-
|
|
36
|
-
<Box>
|
|
37
|
-
<Text color={theme.primary}> baseUrl: </Text>
|
|
38
|
-
<Text>{config.baseUrl}</Text>
|
|
39
|
-
</Box>
|
|
40
|
-
|
|
41
|
-
<Box>
|
|
42
|
-
<Text color={theme.primary}> model: </Text>
|
|
43
|
-
<Text>{config.model}</Text>
|
|
44
|
-
</Box>
|
|
45
|
-
|
|
46
|
-
<Box>
|
|
47
|
-
<Text color={theme.primary}> shellHook: </Text>
|
|
48
|
-
{config.shellHook ? (
|
|
49
|
-
<Text color={theme.success}>已启用</Text>
|
|
50
|
-
) : (
|
|
51
|
-
<Text color={theme.text.secondary}>未启用</Text>
|
|
52
|
-
)}
|
|
53
|
-
</Box>
|
|
54
|
-
|
|
55
|
-
<Box>
|
|
56
|
-
<Text color={theme.primary}> chatHistoryLimit: </Text>
|
|
57
|
-
<Text>{config.chatHistoryLimit} 轮</Text>
|
|
58
|
-
</Box>
|
|
59
|
-
|
|
60
|
-
<Text color={theme.text.secondary}>{'━'.repeat(40)}</Text>
|
|
61
|
-
<Text color={theme.text.secondary}>配置文件: {CONFIG_FILE}</Text>
|
|
62
|
-
</Box>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import TextInput from 'ink-text-input'
|
|
4
|
-
import { getConfig, saveConfig, maskApiKey } from '../config.js'
|
|
5
|
-
import { theme } from '../ui/theme.js'
|
|
6
|
-
import path from 'path'
|
|
7
|
-
import os from 'os'
|
|
8
|
-
|
|
9
|
-
const CONFIG_FILE = path.join(os.homedir(), '.please', 'config.json')
|
|
10
|
-
|
|
11
|
-
interface ConfigWizardProps {
|
|
12
|
-
onComplete: () => void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type Step = 'apiKey' | 'baseUrl' | 'model' | 'done'
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* ConfigWizard 组件 - 交互式配置向导
|
|
19
|
-
*/
|
|
20
|
-
export const ConfigWizard: React.FC<ConfigWizardProps> = ({ onComplete }) => {
|
|
21
|
-
const config = getConfig()
|
|
22
|
-
const [step, setStep] = useState<Step>('apiKey')
|
|
23
|
-
const [apiKey, setApiKey] = useState(config.apiKey)
|
|
24
|
-
const [baseUrl, setBaseUrl] = useState(config.baseUrl)
|
|
25
|
-
const [model, setModel] = useState(config.model)
|
|
26
|
-
|
|
27
|
-
const handleApiKeySubmit = (value: string) => {
|
|
28
|
-
if (value.trim()) {
|
|
29
|
-
setApiKey(value.trim())
|
|
30
|
-
}
|
|
31
|
-
setStep('baseUrl')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const handleBaseUrlSubmit = (value: string) => {
|
|
35
|
-
if (value.trim()) {
|
|
36
|
-
setBaseUrl(value.trim())
|
|
37
|
-
}
|
|
38
|
-
setStep('model')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const handleModelSubmit = (value: string) => {
|
|
42
|
-
if (value.trim()) {
|
|
43
|
-
setModel(value.trim())
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 保存配置
|
|
47
|
-
saveConfig({
|
|
48
|
-
...config,
|
|
49
|
-
apiKey: apiKey || config.apiKey,
|
|
50
|
-
baseUrl: baseUrl || config.baseUrl,
|
|
51
|
-
model: model.trim() || config.model,
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
setStep('done')
|
|
55
|
-
setTimeout(onComplete, 100)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<Box flexDirection="column" marginY={1}>
|
|
60
|
-
<Text bold color={theme.accent}>
|
|
61
|
-
🔧 Pretty Please 配置向导
|
|
62
|
-
</Text>
|
|
63
|
-
<Text color={theme.text.secondary}>{'━'.repeat(40)}</Text>
|
|
64
|
-
|
|
65
|
-
{step === 'apiKey' && (
|
|
66
|
-
<Box marginTop={1}>
|
|
67
|
-
<Text color={theme.primary}>
|
|
68
|
-
请输入 API Key{config.apiKey ? ` (当前: ${maskApiKey(config.apiKey)})` : ''}:{' '}
|
|
69
|
-
</Text>
|
|
70
|
-
<TextInput value="" onChange={() => {}} onSubmit={handleApiKeySubmit} />
|
|
71
|
-
</Box>
|
|
72
|
-
)}
|
|
73
|
-
|
|
74
|
-
{step === 'baseUrl' && (
|
|
75
|
-
<Box marginTop={1}>
|
|
76
|
-
<Text color={theme.primary}>
|
|
77
|
-
请输入 API Base URL (回车使用 {baseUrl}):{' '}
|
|
78
|
-
</Text>
|
|
79
|
-
<TextInput value="" onChange={() => {}} onSubmit={handleBaseUrlSubmit} />
|
|
80
|
-
</Box>
|
|
81
|
-
)}
|
|
82
|
-
|
|
83
|
-
{step === 'model' && (
|
|
84
|
-
<Box marginTop={1}>
|
|
85
|
-
<Text color={theme.primary}>
|
|
86
|
-
请输入模型名称 (回车使用 {model}):{' '}
|
|
87
|
-
</Text>
|
|
88
|
-
<TextInput value="" onChange={() => {}} onSubmit={handleModelSubmit} />
|
|
89
|
-
</Box>
|
|
90
|
-
)}
|
|
91
|
-
|
|
92
|
-
{step === 'done' && (
|
|
93
|
-
<Box flexDirection="column" marginTop={1}>
|
|
94
|
-
<Text color={theme.text.secondary}>{'━'.repeat(40)}</Text>
|
|
95
|
-
<Text color={theme.success}>✅ 配置已保存到 </Text>
|
|
96
|
-
<Text color={theme.text.secondary}>{CONFIG_FILE}</Text>
|
|
97
|
-
</Box>
|
|
98
|
-
)}
|
|
99
|
-
</Box>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import { getHistory, getHistoryFilePath } from '../history.js'
|
|
4
|
-
import { theme } from '../ui/theme.js'
|
|
5
|
-
|
|
6
|
-
interface HistoryDisplayProps {
|
|
7
|
-
onComplete?: () => void
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* HistoryDisplay 组件 - 显示历史记录
|
|
12
|
-
*/
|
|
13
|
-
export const HistoryDisplay: React.FC<HistoryDisplayProps> = ({ onComplete }) => {
|
|
14
|
-
const history = getHistory()
|
|
15
|
-
|
|
16
|
-
React.useEffect(() => {
|
|
17
|
-
if (onComplete) {
|
|
18
|
-
setTimeout(onComplete, 100)
|
|
19
|
-
}
|
|
20
|
-
}, [onComplete])
|
|
21
|
-
|
|
22
|
-
if (history.length === 0) {
|
|
23
|
-
return (
|
|
24
|
-
<Box marginY={1}>
|
|
25
|
-
<Text color={theme.text.secondary}>暂无历史记录</Text>
|
|
26
|
-
</Box>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<Box flexDirection="column" marginY={1}>
|
|
32
|
-
<Text bold>📜 命令历史:</Text>
|
|
33
|
-
<Text color={theme.text.secondary}>{'━'.repeat(50)}</Text>
|
|
34
|
-
|
|
35
|
-
{history.map((item, index) => {
|
|
36
|
-
const status = item.executed
|
|
37
|
-
? item.exitCode === 0
|
|
38
|
-
? '✓'
|
|
39
|
-
: `✗ 退出码:${item.exitCode}`
|
|
40
|
-
: '(未执行)'
|
|
41
|
-
|
|
42
|
-
const statusColor = item.executed
|
|
43
|
-
? item.exitCode === 0
|
|
44
|
-
? theme.success
|
|
45
|
-
: theme.error
|
|
46
|
-
: theme.text.secondary
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Box key={index} flexDirection="column" marginY={1}>
|
|
50
|
-
<Box>
|
|
51
|
-
<Text color={theme.text.secondary}>{index + 1}. </Text>
|
|
52
|
-
<Text color={theme.primary}>{item.userPrompt}</Text>
|
|
53
|
-
</Box>
|
|
54
|
-
<Box marginLeft={3}>
|
|
55
|
-
<Text dimColor>→ </Text>
|
|
56
|
-
<Text>{item.command} </Text>
|
|
57
|
-
<Text color={statusColor}>{status}</Text>
|
|
58
|
-
</Box>
|
|
59
|
-
<Box marginLeft={3}>
|
|
60
|
-
<Text color={theme.text.secondary}>{item.timestamp}</Text>
|
|
61
|
-
</Box>
|
|
62
|
-
</Box>
|
|
63
|
-
)
|
|
64
|
-
})}
|
|
65
|
-
|
|
66
|
-
<Text color={theme.text.secondary}>历史文件: {getHistoryFilePath()}</Text>
|
|
67
|
-
</Box>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import {
|
|
4
|
-
getHookStatus,
|
|
5
|
-
installShellHook,
|
|
6
|
-
uninstallShellHook,
|
|
7
|
-
detectShell,
|
|
8
|
-
getShellConfigPath,
|
|
9
|
-
} from '../shell-hook.js'
|
|
10
|
-
import { theme } from '../ui/theme.js'
|
|
11
|
-
|
|
12
|
-
interface HookManagerProps {
|
|
13
|
-
action: 'status' | 'install' | 'uninstall'
|
|
14
|
-
onComplete: () => void
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* HookManager 组件 - Hook 管理界面
|
|
19
|
-
*/
|
|
20
|
-
export const HookManager: React.FC<HookManagerProps> = ({ action, onComplete }) => {
|
|
21
|
-
const [status, setStatus] = useState(getHookStatus())
|
|
22
|
-
const [message, setMessage] = useState<string | null>(null)
|
|
23
|
-
const [isProcessing, setIsProcessing] = useState(false)
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
const execute = async () => {
|
|
27
|
-
if (action === 'install') {
|
|
28
|
-
setIsProcessing(true)
|
|
29
|
-
const shellType = detectShell()
|
|
30
|
-
const configPath = getShellConfigPath(shellType)
|
|
31
|
-
|
|
32
|
-
if (shellType === 'unknown') {
|
|
33
|
-
setMessage('❌ 不支持的 shell 类型')
|
|
34
|
-
setIsProcessing(false)
|
|
35
|
-
setTimeout(onComplete, 2000)
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const result = await installShellHook()
|
|
40
|
-
setStatus(getHookStatus())
|
|
41
|
-
setIsProcessing(false)
|
|
42
|
-
|
|
43
|
-
if (result) {
|
|
44
|
-
setMessage(
|
|
45
|
-
`✅ Shell hook 已安装\n⚠️ 请重启终端或执行: source ${configPath}`
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
setTimeout(onComplete, 3000)
|
|
50
|
-
} else if (action === 'uninstall') {
|
|
51
|
-
setIsProcessing(true)
|
|
52
|
-
uninstallShellHook()
|
|
53
|
-
setStatus(getHookStatus())
|
|
54
|
-
setMessage('✅ Shell hook 已卸载\n⚠️ 请重启终端使其生效')
|
|
55
|
-
setIsProcessing(false)
|
|
56
|
-
setTimeout(onComplete, 3000)
|
|
57
|
-
} else {
|
|
58
|
-
// status
|
|
59
|
-
setTimeout(onComplete, 100)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
execute()
|
|
64
|
-
}, [action, onComplete])
|
|
65
|
-
|
|
66
|
-
if (action === 'install' || action === 'uninstall') {
|
|
67
|
-
return (
|
|
68
|
-
<Box flexDirection="column" marginY={1}>
|
|
69
|
-
<Text bold color={theme.accent}>
|
|
70
|
-
🔧 Shell Hook {action === 'install' ? '安装' : '卸载'}向导
|
|
71
|
-
</Text>
|
|
72
|
-
<Text color={theme.text.secondary}>{'━'.repeat(40)}</Text>
|
|
73
|
-
|
|
74
|
-
{isProcessing && <Text color={theme.info}>处理中...</Text>}
|
|
75
|
-
|
|
76
|
-
{message && (
|
|
77
|
-
<Box flexDirection="column" marginTop={1}>
|
|
78
|
-
{message.split('\n').map((line, i) => (
|
|
79
|
-
<Text
|
|
80
|
-
key={i}
|
|
81
|
-
color={
|
|
82
|
-
line.startsWith('✅')
|
|
83
|
-
? theme.success
|
|
84
|
-
: line.startsWith('⚠️')
|
|
85
|
-
? theme.warning
|
|
86
|
-
: line.startsWith('❌')
|
|
87
|
-
? theme.error
|
|
88
|
-
: theme.text.primary
|
|
89
|
-
}
|
|
90
|
-
>
|
|
91
|
-
{line}
|
|
92
|
-
</Text>
|
|
93
|
-
))}
|
|
94
|
-
</Box>
|
|
95
|
-
)}
|
|
96
|
-
</Box>
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Status display
|
|
101
|
-
return (
|
|
102
|
-
<Box flexDirection="column" marginY={1}>
|
|
103
|
-
<Text bold>📊 Shell Hook 状态</Text>
|
|
104
|
-
<Text color={theme.text.secondary}>{'━'.repeat(40)}</Text>
|
|
105
|
-
|
|
106
|
-
<Box marginTop={1}>
|
|
107
|
-
<Text color={theme.primary}> Shell 类型: </Text>
|
|
108
|
-
<Text>{status.shellType}</Text>
|
|
109
|
-
</Box>
|
|
110
|
-
|
|
111
|
-
<Box>
|
|
112
|
-
<Text color={theme.primary}> 配置文件: </Text>
|
|
113
|
-
<Text>{status.configPath || '未知'}</Text>
|
|
114
|
-
</Box>
|
|
115
|
-
|
|
116
|
-
<Box>
|
|
117
|
-
<Text color={theme.primary}> 已安装: </Text>
|
|
118
|
-
{status.installed ? (
|
|
119
|
-
<Text color={theme.success}>是</Text>
|
|
120
|
-
) : (
|
|
121
|
-
<Text color={theme.text.secondary}>否</Text>
|
|
122
|
-
)}
|
|
123
|
-
</Box>
|
|
124
|
-
|
|
125
|
-
<Box>
|
|
126
|
-
<Text color={theme.primary}> 已启用: </Text>
|
|
127
|
-
{status.enabled ? (
|
|
128
|
-
<Text color={theme.success}>是</Text>
|
|
129
|
-
) : (
|
|
130
|
-
<Text color={theme.text.secondary}>否</Text>
|
|
131
|
-
)}
|
|
132
|
-
</Box>
|
|
133
|
-
|
|
134
|
-
<Box>
|
|
135
|
-
<Text color={theme.primary}> 历史文件: </Text>
|
|
136
|
-
<Text>{status.historyFile}</Text>
|
|
137
|
-
</Box>
|
|
138
|
-
|
|
139
|
-
<Text color={theme.text.secondary}>{'━'.repeat(40)}</Text>
|
|
140
|
-
|
|
141
|
-
{!status.installed && (
|
|
142
|
-
<Box marginTop={1}>
|
|
143
|
-
<Text color={theme.text.secondary}>
|
|
144
|
-
提示: 运行 <Text color={theme.primary}>pls hook install</Text> 安装 shell hook
|
|
145
|
-
</Text>
|
|
146
|
-
</Box>
|
|
147
|
-
)}
|
|
148
|
-
</Box>
|
|
149
|
-
)
|
|
150
|
-
}
|