@yivan-lab/pretty-please 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +381 -28
- package/bin/pls.tsx +1138 -109
- package/dist/bin/pls.d.ts +1 -1
- package/dist/bin/pls.js +994 -91
- package/dist/package.json +80 -0
- package/dist/src/ai.d.ts +1 -41
- package/dist/src/ai.js +9 -190
- package/dist/src/alias.d.ts +41 -0
- package/dist/src/alias.js +240 -0
- 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 +35 -4
- package/dist/src/components/Chat.js +5 -4
- package/dist/src/components/CodeColorizer.js +26 -20
- package/dist/src/components/CommandBox.js +3 -17
- package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
- package/dist/src/components/ConfirmationPrompt.js +9 -4
- 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 +5 -1
- package/dist/src/components/MultiStepCommandGenerator.js +127 -14
- package/dist/src/components/TableRenderer.js +2 -1
- package/dist/src/config.d.ts +59 -9
- package/dist/src/config.js +147 -48
- 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 +23 -7
- package/dist/src/multi-step.js +29 -6
- package/dist/src/prompts.d.ts +11 -0
- package/dist/src/prompts.js +140 -0
- 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 +87 -12
- package/dist/src/shell-hook.js +315 -17
- package/dist/src/sysinfo.d.ts +9 -5
- package/dist/src/sysinfo.js +2 -2
- package/dist/src/ui/theme.d.ts +27 -24
- package/dist/src/ui/theme.js +71 -21
- package/dist/src/upgrade.d.ts +41 -0
- package/dist/src/upgrade.js +348 -0
- package/dist/src/utils/console.d.ts +11 -11
- package/dist/src/utils/console.js +26 -17
- package/package.json +11 -9
- package/src/alias.ts +301 -0
- package/src/builtin-detector.ts +126 -0
- package/src/chat-history.ts +140 -0
- package/src/components/Chat.tsx +6 -5
- package/src/components/CodeColorizer.tsx +27 -19
- package/src/components/CommandBox.tsx +3 -17
- package/src/components/ConfirmationPrompt.tsx +11 -3
- 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 +167 -16
- package/src/components/TableRenderer.tsx +2 -1
- package/src/config.ts +394 -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 +45 -8
- package/src/prompts.ts +154 -0
- package/src/remote-history.ts +390 -0
- package/src/remote.ts +800 -0
- package/src/shell-hook.ts +754 -0
- package/src/{sysinfo.js → sysinfo.ts} +28 -16
- package/src/ui/theme.ts +101 -24
- package/src/upgrade.ts +397 -0
- package/src/utils/{console.js → console.ts} +36 -27
- 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,24 +1,34 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
3
4
|
import Spinner from 'ink-spinner';
|
|
4
5
|
import { generateMultiStepCommand } from '../multi-step.js';
|
|
5
6
|
import { detectBuiltin, formatBuiltins } from '../builtin-detector.js';
|
|
6
7
|
import { CommandBox } from './CommandBox.js';
|
|
7
8
|
import { ConfirmationPrompt } from './ConfirmationPrompt.js';
|
|
8
9
|
import { Duration } from './Duration.js';
|
|
9
|
-
import {
|
|
10
|
+
import { getCurrentTheme } from '../ui/theme.js';
|
|
11
|
+
import { getConfig } from '../config.js';
|
|
10
12
|
/**
|
|
11
13
|
* MultiStepCommandGenerator 组件 - 多步骤命令生成
|
|
12
14
|
* 每次只生成一个命令,支持 continue 机制
|
|
13
15
|
*/
|
|
14
|
-
export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], currentStepNumber = 1, onStepComplete, }) => {
|
|
16
|
+
export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], currentStepNumber = 1, remoteContext, isRemote = false, onStepComplete, }) => {
|
|
17
|
+
const theme = getCurrentTheme();
|
|
15
18
|
const [state, setState] = useState({ type: 'thinking' });
|
|
16
19
|
const [thinkDuration, setThinkDuration] = useState(0);
|
|
17
20
|
const [debugInfo, setDebugInfo] = useState(null);
|
|
21
|
+
const [editedCommand, setEditedCommand] = useState(''); // 新增:编辑后的命令
|
|
22
|
+
// 监听编辑模式下的 Esc 键
|
|
23
|
+
useInput((input, key) => {
|
|
24
|
+
if (state.type === 'editing' && key.escape) {
|
|
25
|
+
handleEditCancel();
|
|
26
|
+
}
|
|
27
|
+
}, { isActive: state.type === 'editing' });
|
|
18
28
|
// 初始化:调用 Mastra 生成命令
|
|
19
29
|
useEffect(() => {
|
|
20
30
|
const thinkStart = Date.now();
|
|
21
|
-
generateMultiStepCommand(prompt, previousSteps, { debug })
|
|
31
|
+
generateMultiStepCommand(prompt, previousSteps, { debug, remoteContext })
|
|
22
32
|
.then((result) => {
|
|
23
33
|
const thinkEnd = Date.now();
|
|
24
34
|
setThinkDuration(thinkEnd - thinkStart);
|
|
@@ -26,13 +36,27 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
|
|
|
26
36
|
if (debug && result.debugInfo) {
|
|
27
37
|
setDebugInfo(result.debugInfo);
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
// 如果 AI 返回空命令且决定不继续,说明 AI 放弃了
|
|
40
|
+
// 直接结束,不显示命令框
|
|
41
|
+
if (!result.stepData.command.trim() && result.stepData.continue === false) {
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
onStepComplete({
|
|
44
|
+
command: '',
|
|
45
|
+
confirmed: false,
|
|
46
|
+
reasoning: result.stepData.reasoning,
|
|
47
|
+
needsContinue: false,
|
|
48
|
+
});
|
|
49
|
+
}, 100);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// 检测 builtin(优先检测,但远程执行时跳过)
|
|
34
53
|
const { hasBuiltin, builtins } = detectBuiltin(result.stepData.command);
|
|
35
|
-
if (hasBuiltin) {
|
|
54
|
+
if (hasBuiltin && !isRemote) {
|
|
55
|
+
// 有 builtin 且是本地执行,不管什么模式都不编辑,直接提示
|
|
56
|
+
setState({
|
|
57
|
+
type: 'showing_command',
|
|
58
|
+
stepData: result.stepData,
|
|
59
|
+
});
|
|
36
60
|
setTimeout(() => {
|
|
37
61
|
onStepComplete({
|
|
38
62
|
command: result.stepData.command,
|
|
@@ -43,6 +67,25 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
|
|
|
43
67
|
needsContinue: result.stepData.continue,
|
|
44
68
|
});
|
|
45
69
|
}, 100);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// 根据 editMode 决定进入哪个状态
|
|
73
|
+
const config = getConfig();
|
|
74
|
+
const autoEdit = config.editMode === 'auto';
|
|
75
|
+
if (autoEdit) {
|
|
76
|
+
// auto 模式:直接进入编辑状态
|
|
77
|
+
setEditedCommand(result.stepData.command);
|
|
78
|
+
setState({
|
|
79
|
+
type: 'editing',
|
|
80
|
+
stepData: result.stepData,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// manual 模式:显示命令,等待用户操作
|
|
85
|
+
setState({
|
|
86
|
+
type: 'showing_command',
|
|
87
|
+
stepData: result.stepData,
|
|
88
|
+
});
|
|
46
89
|
}
|
|
47
90
|
})
|
|
48
91
|
.catch((error) => {
|
|
@@ -55,12 +98,37 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
|
|
|
55
98
|
});
|
|
56
99
|
}, 100);
|
|
57
100
|
});
|
|
58
|
-
}, [prompt, previousSteps, debug]);
|
|
101
|
+
}, [prompt, previousSteps, debug, remoteContext]);
|
|
59
102
|
// 处理确认
|
|
60
103
|
const handleConfirm = () => {
|
|
61
104
|
if (state.type === 'showing_command') {
|
|
62
105
|
onStepComplete({
|
|
63
106
|
command: state.stepData.command,
|
|
107
|
+
aiGeneratedCommand: state.stepData.command, // 原始命令
|
|
108
|
+
userModified: false,
|
|
109
|
+
confirmed: true,
|
|
110
|
+
reasoning: state.stepData.reasoning,
|
|
111
|
+
needsContinue: state.stepData.continue,
|
|
112
|
+
nextStepHint: state.stepData.nextStepHint,
|
|
113
|
+
debugInfo: debugInfo,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
// 处理编辑
|
|
118
|
+
const handleEdit = () => {
|
|
119
|
+
if (state.type === 'showing_command') {
|
|
120
|
+
setEditedCommand(state.stepData.command); // 初始化为 AI 生成的命令
|
|
121
|
+
setState({ type: 'editing', stepData: state.stepData });
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
// 编辑完成确认
|
|
125
|
+
const handleEditConfirm = () => {
|
|
126
|
+
if (state.type === 'editing') {
|
|
127
|
+
const modified = editedCommand !== state.stepData.command;
|
|
128
|
+
onStepComplete({
|
|
129
|
+
command: editedCommand, // 使用编辑后的命令
|
|
130
|
+
aiGeneratedCommand: state.stepData.command, // 保存 AI 原始命令
|
|
131
|
+
userModified: modified,
|
|
64
132
|
confirmed: true,
|
|
65
133
|
reasoning: state.stepData.reasoning,
|
|
66
134
|
needsContinue: state.stepData.continue,
|
|
@@ -69,6 +137,27 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
|
|
|
69
137
|
});
|
|
70
138
|
}
|
|
71
139
|
};
|
|
140
|
+
// 取消编辑
|
|
141
|
+
const handleEditCancel = () => {
|
|
142
|
+
if (state.type === 'editing') {
|
|
143
|
+
const config = getConfig();
|
|
144
|
+
if (config.editMode === 'auto') {
|
|
145
|
+
// auto 模式:Esc 直接取消整个操作
|
|
146
|
+
setState({ type: 'cancelled', command: state.stepData.command });
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
onStepComplete({
|
|
149
|
+
command: state.stepData.command,
|
|
150
|
+
confirmed: false,
|
|
151
|
+
cancelled: true,
|
|
152
|
+
});
|
|
153
|
+
}, 100);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// manual 模式:Esc 返回到 showing_command 状态
|
|
157
|
+
setState({ type: 'showing_command', stepData: state.stepData });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
72
161
|
// 处理取消
|
|
73
162
|
const handleCancel = () => {
|
|
74
163
|
if (state.type === 'showing_command') {
|
|
@@ -87,7 +176,9 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
|
|
|
87
176
|
React.createElement(Text, { color: theme.info },
|
|
88
177
|
React.createElement(Spinner, { type: "dots" }),
|
|
89
178
|
' ',
|
|
90
|
-
|
|
179
|
+
remoteContext
|
|
180
|
+
? (currentStepNumber === 1 ? `正在为 ${remoteContext.name} 思考...` : `正在规划步骤 ${currentStepNumber} (${remoteContext.name})...`)
|
|
181
|
+
: (currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`)))),
|
|
91
182
|
state.type !== 'thinking' && thinkDuration > 0 && (React.createElement(Box, null,
|
|
92
183
|
React.createElement(Text, { color: theme.success }, "\u2713 \u601D\u8003\u5B8C\u6210 "),
|
|
93
184
|
React.createElement(Duration, { ms: thinkDuration }))),
|
|
@@ -104,6 +195,13 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
|
|
|
104
195
|
React.createElement(Text, { color: theme.text.secondary },
|
|
105
196
|
"\u5DF2\u6267\u884C\u6B65\u9AA4\u6570: ",
|
|
106
197
|
debugInfo.previousStepsCount))),
|
|
198
|
+
debugInfo.remoteContext && (React.createElement(Box, { marginTop: 1 },
|
|
199
|
+
React.createElement(Text, { color: theme.text.secondary },
|
|
200
|
+
"\u8FDC\u7A0B\u670D\u52A1\u5668: ",
|
|
201
|
+
debugInfo.remoteContext.name,
|
|
202
|
+
" (",
|
|
203
|
+
debugInfo.remoteContext.sysInfo.os,
|
|
204
|
+
")"))),
|
|
107
205
|
React.createElement(Box, { marginTop: 1 },
|
|
108
206
|
React.createElement(Text, { color: theme.text.secondary }, "AI \u8FD4\u56DE\u7684 JSON:")),
|
|
109
207
|
React.createElement(Text, { color: theme.text.dim }, JSON.stringify(debugInfo.response, null, 2)),
|
|
@@ -120,7 +218,7 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
|
|
|
120
218
|
"\u4E0B\u4E00\u6B65: ",
|
|
121
219
|
state.stepData.nextStepHint)))),
|
|
122
220
|
React.createElement(CommandBox, { command: state.stepData.command }),
|
|
123
|
-
(() => {
|
|
221
|
+
!isRemote && (() => {
|
|
124
222
|
const { hasBuiltin, builtins } = detectBuiltin(state.stepData.command);
|
|
125
223
|
if (hasBuiltin) {
|
|
126
224
|
return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
|
@@ -132,7 +230,22 @@ export const MultiStepCommandGenerator = ({ prompt, debug, previousSteps = [], c
|
|
|
132
230
|
}
|
|
133
231
|
return null;
|
|
134
232
|
})(),
|
|
135
|
-
!detectBuiltin(state.stepData.command).hasBuiltin && (React.createElement(ConfirmationPrompt, { prompt: "\u6267\u884C\uFF1F", onConfirm: handleConfirm, onCancel: handleCancel })))),
|
|
233
|
+
(isRemote || !detectBuiltin(state.stepData.command).hasBuiltin) && (React.createElement(ConfirmationPrompt, { prompt: "\u6267\u884C\uFF1F", onConfirm: handleConfirm, onCancel: handleCancel, onEdit: handleEdit })))),
|
|
234
|
+
state.type === 'editing' && (React.createElement(React.Fragment, null,
|
|
235
|
+
state.stepData.continue === true && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
236
|
+
React.createElement(Text, { color: theme.text.secondary },
|
|
237
|
+
"\u6B65\u9AA4 ",
|
|
238
|
+
currentStepNumber,
|
|
239
|
+
"/?"),
|
|
240
|
+
state.stepData.reasoning && (React.createElement(Text, { color: theme.text.muted },
|
|
241
|
+
"\u539F\u56E0: ",
|
|
242
|
+
state.stepData.reasoning)))),
|
|
243
|
+
React.createElement(CommandBox, { command: state.stepData.command }),
|
|
244
|
+
React.createElement(Box, { flexDirection: "row" },
|
|
245
|
+
React.createElement(Text, { color: theme.primary }, '> '),
|
|
246
|
+
React.createElement(TextInput, { value: editedCommand, onChange: setEditedCommand, onSubmit: handleEditConfirm })),
|
|
247
|
+
React.createElement(Box, { marginTop: 1 },
|
|
248
|
+
React.createElement(Text, { color: theme.text.secondary }, getConfig().editMode === 'auto' ? '[回车执行 / Esc 取消]' : '[回车执行 / Esc 返回]')))),
|
|
136
249
|
state.type === 'cancelled' && (React.createElement(Box, { marginTop: 1 },
|
|
137
250
|
React.createElement(Text, { color: theme.text.secondary }, "\u5DF2\u53D6\u6D88\u6267\u884C"))),
|
|
138
251
|
state.type === 'error' && (React.createElement(Box, { marginTop: 1 },
|
|
@@ -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
|
* 计算纯文本长度(去除 markdown 标记)
|
|
@@ -40,6 +40,7 @@ function calculateColumnWidths(headers, rows, terminalWidth) {
|
|
|
40
40
|
* 表格渲染组件
|
|
41
41
|
*/
|
|
42
42
|
function TableRendererInternal({ headers, rows, terminalWidth }) {
|
|
43
|
+
const theme = getCurrentTheme();
|
|
43
44
|
const columnWidths = calculateColumnWidths(headers, rows, terminalWidth);
|
|
44
45
|
const baseColor = theme.text.primary;
|
|
45
46
|
return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,29 +1,79 @@
|
|
|
1
|
+
export declare const CONFIG_DIR: string;
|
|
2
|
+
declare const VALID_PROVIDERS: readonly ["openai", "anthropic", "deepseek", "google", "groq", "mistral", "cohere", "fireworks", "together"];
|
|
3
|
+
type Provider = (typeof VALID_PROVIDERS)[number];
|
|
4
|
+
declare const VALID_EDIT_MODES: readonly ["manual", "auto"];
|
|
5
|
+
type EditMode = (typeof VALID_EDIT_MODES)[number];
|
|
6
|
+
declare const VALID_THEMES: readonly ["dark", "light"];
|
|
7
|
+
export type ThemeName = (typeof VALID_THEMES)[number];
|
|
1
8
|
/**
|
|
2
|
-
*
|
|
9
|
+
* 别名配置接口
|
|
3
10
|
*/
|
|
4
|
-
export
|
|
11
|
+
export interface AliasConfig {
|
|
12
|
+
prompt: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 远程服务器配置接口
|
|
17
|
+
*/
|
|
18
|
+
export interface RemoteConfig {
|
|
19
|
+
host: string;
|
|
20
|
+
user: string;
|
|
21
|
+
port: number;
|
|
22
|
+
key?: string;
|
|
23
|
+
password?: boolean;
|
|
24
|
+
workDir?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 远程服务器系统信息缓存
|
|
28
|
+
*/
|
|
29
|
+
export interface RemoteSysInfo {
|
|
30
|
+
os: string;
|
|
31
|
+
osVersion: string;
|
|
32
|
+
shell: string;
|
|
33
|
+
hostname: string;
|
|
34
|
+
cachedAt: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 配置接口
|
|
38
|
+
*/
|
|
39
|
+
export interface Config {
|
|
40
|
+
apiKey: string;
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
model: string;
|
|
43
|
+
provider: Provider;
|
|
44
|
+
shellHook: boolean;
|
|
45
|
+
chatHistoryLimit: number;
|
|
46
|
+
commandHistoryLimit: number;
|
|
47
|
+
shellHistoryLimit: number;
|
|
48
|
+
editMode: EditMode;
|
|
49
|
+
theme: ThemeName;
|
|
50
|
+
aliases: Record<string, AliasConfig>;
|
|
51
|
+
remotes: Record<string, RemoteConfig>;
|
|
52
|
+
defaultRemote?: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function getConfig(): Config;
|
|
5
55
|
/**
|
|
6
56
|
* 保存配置
|
|
7
57
|
*/
|
|
8
|
-
export function saveConfig(config:
|
|
58
|
+
export declare function saveConfig(config: Config): void;
|
|
9
59
|
/**
|
|
10
60
|
* 设置单个配置项
|
|
11
61
|
*/
|
|
12
|
-
export function setConfigValue(key:
|
|
62
|
+
export declare function setConfigValue(key: string, value: string | boolean | number): Config;
|
|
13
63
|
/**
|
|
14
64
|
* 检查配置是否有效
|
|
15
65
|
*/
|
|
16
|
-
export function isConfigValid():
|
|
66
|
+
export declare function isConfigValid(): boolean;
|
|
17
67
|
/**
|
|
18
68
|
* 隐藏 API Key 中间部分
|
|
19
69
|
*/
|
|
20
|
-
export function maskApiKey(apiKey:
|
|
70
|
+
export declare function maskApiKey(apiKey: string): string;
|
|
21
71
|
/**
|
|
22
72
|
* 显示当前配置
|
|
23
73
|
*/
|
|
24
|
-
export function displayConfig(): void;
|
|
74
|
+
export declare function displayConfig(): void;
|
|
25
75
|
/**
|
|
26
76
|
* 交互式配置向导
|
|
27
77
|
*/
|
|
28
|
-
export function runConfigWizard(): Promise<void>;
|
|
29
|
-
export
|
|
78
|
+
export declare function runConfigWizard(): Promise<void>;
|
|
79
|
+
export {};
|
package/dist/src/config.js
CHANGED
|
@@ -3,18 +3,54 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import readline from 'readline';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
|
|
6
|
+
import { getCurrentTheme } from './ui/theme.js';
|
|
7
|
+
// 获取主题颜色
|
|
8
|
+
function getColors() {
|
|
9
|
+
const theme = getCurrentTheme();
|
|
10
|
+
return {
|
|
11
|
+
primary: theme.primary,
|
|
12
|
+
secondary: theme.secondary,
|
|
13
|
+
success: theme.success,
|
|
14
|
+
error: theme.error
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// 配置文件路径
|
|
18
|
+
export const CONFIG_DIR = path.join(os.homedir(), '.please');
|
|
7
19
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
20
|
+
// 支持的 Provider 列表
|
|
21
|
+
const VALID_PROVIDERS = [
|
|
22
|
+
'openai',
|
|
23
|
+
'anthropic',
|
|
24
|
+
'deepseek',
|
|
25
|
+
'google',
|
|
26
|
+
'groq',
|
|
27
|
+
'mistral',
|
|
28
|
+
'cohere',
|
|
29
|
+
'fireworks',
|
|
30
|
+
'together',
|
|
31
|
+
];
|
|
32
|
+
// 编辑模式
|
|
33
|
+
const VALID_EDIT_MODES = ['manual', 'auto'];
|
|
34
|
+
// 主题
|
|
35
|
+
const VALID_THEMES = ['dark', 'light'];
|
|
36
|
+
/**
|
|
37
|
+
* 默认配置
|
|
38
|
+
*/
|
|
8
39
|
const DEFAULT_CONFIG = {
|
|
9
40
|
apiKey: '',
|
|
10
41
|
baseUrl: 'https://api.openai.com/v1',
|
|
11
42
|
model: 'gpt-4-turbo',
|
|
12
|
-
provider: 'openai',
|
|
13
|
-
shellHook: false,
|
|
14
|
-
chatHistoryLimit: 10
|
|
43
|
+
provider: 'openai',
|
|
44
|
+
shellHook: false,
|
|
45
|
+
chatHistoryLimit: 10,
|
|
46
|
+
commandHistoryLimit: 10,
|
|
47
|
+
shellHistoryLimit: 15,
|
|
48
|
+
editMode: 'manual',
|
|
49
|
+
theme: 'dark',
|
|
50
|
+
aliases: {},
|
|
51
|
+
remotes: {},
|
|
52
|
+
defaultRemote: '',
|
|
15
53
|
};
|
|
16
|
-
// 导出配置目录路径
|
|
17
|
-
export { CONFIG_DIR };
|
|
18
54
|
/**
|
|
19
55
|
* 确保配置目录存在
|
|
20
56
|
*/
|
|
@@ -25,19 +61,30 @@ function ensureConfigDir() {
|
|
|
25
61
|
}
|
|
26
62
|
/**
|
|
27
63
|
* 读取配置
|
|
64
|
+
* 优化:添加缓存,避免重复读取文件
|
|
28
65
|
*/
|
|
66
|
+
let cachedConfig = null;
|
|
29
67
|
export function getConfig() {
|
|
68
|
+
// 如果已有缓存,直接返回
|
|
69
|
+
if (cachedConfig !== null) {
|
|
70
|
+
return cachedConfig;
|
|
71
|
+
}
|
|
30
72
|
ensureConfigDir();
|
|
73
|
+
let config;
|
|
31
74
|
if (!fs.existsSync(CONFIG_FILE)) {
|
|
32
|
-
|
|
75
|
+
config = { ...DEFAULT_CONFIG };
|
|
33
76
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
77
|
+
else {
|
|
78
|
+
try {
|
|
79
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
80
|
+
config = { ...DEFAULT_CONFIG, ...JSON.parse(content) };
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
config = { ...DEFAULT_CONFIG };
|
|
84
|
+
}
|
|
40
85
|
}
|
|
86
|
+
cachedConfig = config;
|
|
87
|
+
return config;
|
|
41
88
|
}
|
|
42
89
|
/**
|
|
43
90
|
* 保存配置
|
|
@@ -56,27 +103,42 @@ export function setConfigValue(key, value) {
|
|
|
56
103
|
}
|
|
57
104
|
// 处理特殊类型
|
|
58
105
|
if (key === 'shellHook') {
|
|
59
|
-
config
|
|
106
|
+
config.shellHook = value === 'true' || value === true;
|
|
60
107
|
}
|
|
61
|
-
else if (key === 'chatHistoryLimit') {
|
|
62
|
-
const num = parseInt(value, 10);
|
|
108
|
+
else if (key === 'chatHistoryLimit' || key === 'commandHistoryLimit' || key === 'shellHistoryLimit') {
|
|
109
|
+
const num = typeof value === 'number' ? value : parseInt(String(value), 10);
|
|
63
110
|
if (isNaN(num) || num < 1) {
|
|
64
|
-
throw new Error(
|
|
111
|
+
throw new Error(`${key} 必须是大于 0 的整数`);
|
|
65
112
|
}
|
|
66
113
|
config[key] = num;
|
|
67
114
|
}
|
|
68
115
|
else if (key === 'provider') {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
throw new Error(`provider 必须是以下之一: ${validProviders.join(', ')}`);
|
|
116
|
+
const strValue = String(value);
|
|
117
|
+
if (!VALID_PROVIDERS.includes(strValue)) {
|
|
118
|
+
throw new Error(`provider 必须是以下之一: ${VALID_PROVIDERS.join(', ')}`);
|
|
73
119
|
}
|
|
74
|
-
config
|
|
120
|
+
config.provider = strValue;
|
|
75
121
|
}
|
|
76
|
-
else {
|
|
77
|
-
|
|
122
|
+
else if (key === 'editMode') {
|
|
123
|
+
const strValue = String(value);
|
|
124
|
+
if (!VALID_EDIT_MODES.includes(strValue)) {
|
|
125
|
+
throw new Error(`editMode 必须是以下之一: ${VALID_EDIT_MODES.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
config.editMode = strValue;
|
|
128
|
+
}
|
|
129
|
+
else if (key === 'theme') {
|
|
130
|
+
const strValue = String(value);
|
|
131
|
+
if (!VALID_THEMES.includes(strValue)) {
|
|
132
|
+
throw new Error(`theme 必须是以下之一: ${VALID_THEMES.join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
config.theme = strValue;
|
|
135
|
+
}
|
|
136
|
+
else if (key === 'apiKey' || key === 'baseUrl' || key === 'model' || key === 'defaultRemote') {
|
|
137
|
+
config[key] = String(value);
|
|
78
138
|
}
|
|
79
139
|
saveConfig(config);
|
|
140
|
+
// 清除缓存,下次读取时会重新加载
|
|
141
|
+
cachedConfig = null;
|
|
80
142
|
return config;
|
|
81
143
|
}
|
|
82
144
|
/**
|
|
@@ -84,7 +146,7 @@ export function setConfigValue(key, value) {
|
|
|
84
146
|
*/
|
|
85
147
|
export function isConfigValid() {
|
|
86
148
|
const config = getConfig();
|
|
87
|
-
return config.apiKey
|
|
149
|
+
return config.apiKey.length > 0;
|
|
88
150
|
}
|
|
89
151
|
/**
|
|
90
152
|
* 隐藏 API Key 中间部分
|
|
@@ -99,15 +161,20 @@ export function maskApiKey(apiKey) {
|
|
|
99
161
|
*/
|
|
100
162
|
export function displayConfig() {
|
|
101
163
|
const config = getConfig();
|
|
164
|
+
const colors = getColors();
|
|
102
165
|
console.log(chalk.bold('\n当前配置:'));
|
|
103
|
-
console.log(chalk.gray('━'.repeat(
|
|
104
|
-
console.log(` ${chalk.
|
|
105
|
-
console.log(` ${chalk.
|
|
106
|
-
console.log(` ${chalk.
|
|
107
|
-
console.log(` ${chalk.
|
|
108
|
-
console.log(` ${chalk.
|
|
109
|
-
console.log(` ${chalk.
|
|
110
|
-
console.log(chalk.
|
|
166
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
167
|
+
console.log(` ${chalk.hex(colors.primary)('apiKey')}: ${maskApiKey(config.apiKey)}`);
|
|
168
|
+
console.log(` ${chalk.hex(colors.primary)('baseUrl')}: ${config.baseUrl}`);
|
|
169
|
+
console.log(` ${chalk.hex(colors.primary)('provider')}: ${config.provider}`);
|
|
170
|
+
console.log(` ${chalk.hex(colors.primary)('model')}: ${config.model}`);
|
|
171
|
+
console.log(` ${chalk.hex(colors.primary)('shellHook')}: ${config.shellHook ? chalk.hex(colors.success)('已启用') : chalk.gray('未启用')}`);
|
|
172
|
+
console.log(` ${chalk.hex(colors.primary)('editMode')}: ${config.editMode === 'auto' ? chalk.hex(colors.primary)('auto (自动编辑)') : chalk.gray('manual (按E编辑)')}`);
|
|
173
|
+
console.log(` ${chalk.hex(colors.primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`);
|
|
174
|
+
console.log(` ${chalk.hex(colors.primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`);
|
|
175
|
+
console.log(` ${chalk.hex(colors.primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`);
|
|
176
|
+
console.log(` ${chalk.hex(colors.primary)('theme')}: ${config.theme === 'dark' ? chalk.hex(colors.primary)('dark (深色)') : chalk.hex(colors.primary)('light (浅色)')}`);
|
|
177
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
111
178
|
console.log(chalk.gray(`配置文件: ${CONFIG_FILE}\n`));
|
|
112
179
|
}
|
|
113
180
|
/**
|
|
@@ -116,7 +183,7 @@ export function displayConfig() {
|
|
|
116
183
|
function createReadlineInterface() {
|
|
117
184
|
return readline.createInterface({
|
|
118
185
|
input: process.stdin,
|
|
119
|
-
output: process.stdout
|
|
186
|
+
output: process.stdout,
|
|
120
187
|
});
|
|
121
188
|
}
|
|
122
189
|
/**
|
|
@@ -135,18 +202,18 @@ function question(rl, prompt) {
|
|
|
135
202
|
export async function runConfigWizard() {
|
|
136
203
|
const rl = createReadlineInterface();
|
|
137
204
|
const config = getConfig();
|
|
138
|
-
|
|
205
|
+
const colors = getColors();
|
|
206
|
+
console.log(chalk.bold.hex(colors.primary)('\n🔧 Pretty Please 配置向导'));
|
|
139
207
|
console.log(chalk.gray('━'.repeat(50)));
|
|
140
208
|
console.log(chalk.gray('直接回车使用默认值,输入值后回车确认\n'));
|
|
141
209
|
try {
|
|
142
210
|
// 1. Provider
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const providerPrompt = `${chalk.cyan('Provider')} ${providerHint}\n${chalk.gray('默认:')} ${chalk.yellow(config.provider)} ${chalk.gray('→')} `;
|
|
211
|
+
const providerHint = chalk.gray(`(可选: ${VALID_PROVIDERS.join(', ')})`);
|
|
212
|
+
const providerPrompt = `${chalk.hex(colors.primary)('Provider')} ${providerHint}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.provider)} ${chalk.gray('→')} `;
|
|
146
213
|
const provider = await question(rl, providerPrompt);
|
|
147
214
|
if (provider.trim()) {
|
|
148
|
-
if (!
|
|
149
|
-
console.log(chalk.hex(
|
|
215
|
+
if (!VALID_PROVIDERS.includes(provider.trim())) {
|
|
216
|
+
console.log(chalk.hex(colors.error)(`\n✗ 无效的 provider,必须是以下之一: ${VALID_PROVIDERS.join(', ')}`));
|
|
150
217
|
console.log();
|
|
151
218
|
rl.close();
|
|
152
219
|
return;
|
|
@@ -154,32 +221,45 @@ export async function runConfigWizard() {
|
|
|
154
221
|
config.provider = provider.trim();
|
|
155
222
|
}
|
|
156
223
|
// 2. Base URL
|
|
157
|
-
const baseUrlPrompt = `${chalk.
|
|
224
|
+
const baseUrlPrompt = `${chalk.hex(colors.primary)('API Base URL')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.baseUrl)} ${chalk.gray('→')} `;
|
|
158
225
|
const baseUrl = await question(rl, baseUrlPrompt);
|
|
159
226
|
if (baseUrl.trim()) {
|
|
160
227
|
config.baseUrl = baseUrl.trim();
|
|
161
228
|
}
|
|
162
229
|
// 3. API Key
|
|
163
230
|
const currentKeyDisplay = config.apiKey ? maskApiKey(config.apiKey) : '(未设置)';
|
|
164
|
-
const apiKeyPrompt = `${chalk.
|
|
231
|
+
const apiKeyPrompt = `${chalk.hex(colors.primary)('API Key')} ${chalk.gray(`(当前: ${currentKeyDisplay})`)}\n${chalk.gray('→')} `;
|
|
165
232
|
const apiKey = await question(rl, apiKeyPrompt);
|
|
166
233
|
if (apiKey.trim()) {
|
|
167
234
|
config.apiKey = apiKey.trim();
|
|
168
235
|
}
|
|
169
236
|
// 4. Model
|
|
170
|
-
const modelPrompt = `${chalk.
|
|
237
|
+
const modelPrompt = `${chalk.hex(colors.primary)('Model')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.model)} ${chalk.gray('→')} `;
|
|
171
238
|
const model = await question(rl, modelPrompt);
|
|
172
239
|
if (model.trim()) {
|
|
173
240
|
config.model = model.trim();
|
|
174
241
|
}
|
|
175
242
|
// 5. Shell Hook
|
|
176
|
-
const shellHookPrompt = `${chalk.
|
|
243
|
+
const shellHookPrompt = `${chalk.hex(colors.primary)('启用 Shell Hook')} ${chalk.gray('(记录终端命令历史)')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.shellHook ? 'true' : 'false')} ${chalk.gray('→')} `;
|
|
177
244
|
const shellHook = await question(rl, shellHookPrompt);
|
|
178
245
|
if (shellHook.trim()) {
|
|
179
246
|
config.shellHook = shellHook.trim() === 'true';
|
|
180
247
|
}
|
|
181
|
-
// 6.
|
|
182
|
-
const
|
|
248
|
+
// 6. Edit Mode
|
|
249
|
+
const editModeHint = chalk.gray('(manual=按E编辑, auto=自动编辑)');
|
|
250
|
+
const editModePrompt = `${chalk.hex(colors.primary)('编辑模式')} ${editModeHint}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.editMode)} ${chalk.gray('→')} `;
|
|
251
|
+
const editMode = await question(rl, editModePrompt);
|
|
252
|
+
if (editMode.trim()) {
|
|
253
|
+
if (!VALID_EDIT_MODES.includes(editMode.trim())) {
|
|
254
|
+
console.log(chalk.hex(colors.error)(`\n✗ 无效的 editMode,必须是: manual 或 auto`));
|
|
255
|
+
console.log();
|
|
256
|
+
rl.close();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
config.editMode = editMode.trim();
|
|
260
|
+
}
|
|
261
|
+
// 7. Chat History Limit
|
|
262
|
+
const chatHistoryPrompt = `${chalk.hex(colors.primary)('Chat 历史保留轮数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.chatHistoryLimit)} ${chalk.gray('→')} `;
|
|
183
263
|
const chatHistoryLimit = await question(rl, chatHistoryPrompt);
|
|
184
264
|
if (chatHistoryLimit.trim()) {
|
|
185
265
|
const num = parseInt(chatHistoryLimit.trim(), 10);
|
|
@@ -187,14 +267,33 @@ export async function runConfigWizard() {
|
|
|
187
267
|
config.chatHistoryLimit = num;
|
|
188
268
|
}
|
|
189
269
|
}
|
|
270
|
+
// 8. Command History Limit
|
|
271
|
+
const commandHistoryPrompt = `${chalk.hex(colors.primary)('命令历史保留条数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.commandHistoryLimit)} ${chalk.gray('→')} `;
|
|
272
|
+
const commandHistoryLimit = await question(rl, commandHistoryPrompt);
|
|
273
|
+
if (commandHistoryLimit.trim()) {
|
|
274
|
+
const num = parseInt(commandHistoryLimit.trim(), 10);
|
|
275
|
+
if (!isNaN(num) && num > 0) {
|
|
276
|
+
config.commandHistoryLimit = num;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// 9. Shell History Limit
|
|
280
|
+
const shellHistoryPrompt = `${chalk.hex(colors.primary)('Shell 历史保留条数')}\n${chalk.gray('默认:')} ${chalk.hex(colors.secondary)(config.shellHistoryLimit)} ${chalk.gray('→')} `;
|
|
281
|
+
const shellHistoryLimit = await question(rl, shellHistoryPrompt);
|
|
282
|
+
if (shellHistoryLimit.trim()) {
|
|
283
|
+
const num = parseInt(shellHistoryLimit.trim(), 10);
|
|
284
|
+
if (!isNaN(num) && num > 0) {
|
|
285
|
+
config.shellHistoryLimit = num;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
190
288
|
saveConfig(config);
|
|
191
289
|
console.log('\n' + chalk.gray('━'.repeat(50)));
|
|
192
|
-
console.log(chalk.hex(
|
|
290
|
+
console.log(chalk.hex(getColors().success)('✅ 配置已保存'));
|
|
193
291
|
console.log(chalk.gray(` ${CONFIG_FILE}`));
|
|
194
292
|
console.log();
|
|
195
293
|
}
|
|
196
294
|
catch (error) {
|
|
197
|
-
|
|
295
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
296
|
+
console.log(chalk.hex(getColors().error)(`\n✗ 配置失败: ${message}`));
|
|
198
297
|
console.log();
|
|
199
298
|
}
|
|
200
299
|
finally {
|