@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.
Files changed (64) hide show
  1. package/README.md +98 -27
  2. package/bin/pls.tsx +135 -24
  3. package/dist/bin/pls.d.ts +1 -1
  4. package/dist/bin/pls.js +117 -24
  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/builtin-detector.d.ts +14 -8
  9. package/dist/src/builtin-detector.js +36 -16
  10. package/dist/src/chat-history.d.ts +16 -11
  11. package/dist/src/chat-history.js +26 -4
  12. package/dist/src/components/Chat.js +3 -3
  13. package/dist/src/components/CommandBox.js +1 -16
  14. package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
  15. package/dist/src/components/ConfirmationPrompt.js +7 -3
  16. package/dist/src/components/MultiStepCommandGenerator.d.ts +2 -0
  17. package/dist/src/components/MultiStepCommandGenerator.js +110 -7
  18. package/dist/src/config.d.ts +27 -8
  19. package/dist/src/config.js +92 -33
  20. package/dist/src/history.d.ts +19 -5
  21. package/dist/src/history.js +26 -11
  22. package/dist/src/mastra-agent.d.ts +0 -1
  23. package/dist/src/mastra-agent.js +3 -4
  24. package/dist/src/mastra-chat.d.ts +28 -0
  25. package/dist/src/mastra-chat.js +93 -0
  26. package/dist/src/multi-step.d.ts +2 -2
  27. package/dist/src/multi-step.js +2 -2
  28. package/dist/src/prompts.d.ts +11 -0
  29. package/dist/src/prompts.js +140 -0
  30. package/dist/src/shell-hook.d.ts +35 -13
  31. package/dist/src/shell-hook.js +82 -7
  32. package/dist/src/sysinfo.d.ts +9 -5
  33. package/dist/src/sysinfo.js +2 -2
  34. package/dist/src/utils/console.d.ts +11 -11
  35. package/dist/src/utils/console.js +4 -6
  36. package/package.json +8 -6
  37. package/src/builtin-detector.ts +126 -0
  38. package/src/chat-history.ts +130 -0
  39. package/src/components/Chat.tsx +4 -4
  40. package/src/components/CommandBox.tsx +1 -16
  41. package/src/components/ConfirmationPrompt.tsx +9 -2
  42. package/src/components/MultiStepCommandGenerator.tsx +144 -7
  43. package/src/config.ts +309 -0
  44. package/src/history.ts +160 -0
  45. package/src/mastra-agent.ts +3 -4
  46. package/src/mastra-chat.ts +124 -0
  47. package/src/multi-step.ts +2 -2
  48. package/src/prompts.ts +154 -0
  49. package/src/shell-hook.ts +502 -0
  50. package/src/{sysinfo.js → sysinfo.ts} +28 -16
  51. package/src/utils/{console.js → console.ts} +16 -18
  52. package/bin/pls.js +0 -681
  53. package/src/ai.js +0 -324
  54. package/src/builtin-detector.js +0 -98
  55. package/src/chat-history.js +0 -94
  56. package/src/components/ChatStatus.tsx +0 -53
  57. package/src/components/CommandGenerator.tsx +0 -184
  58. package/src/components/ConfigDisplay.tsx +0 -64
  59. package/src/components/ConfigWizard.tsx +0 -101
  60. package/src/components/HistoryDisplay.tsx +0 -69
  61. package/src/components/HookManager.tsx +0 -150
  62. package/src/config.js +0 -221
  63. package/src/history.js +0 -131
  64. 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
- }