@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
package/src/config.js
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import readline from 'readline';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
|
|
7
|
-
const CONFIG_DIR = path.join(os.homedir(), '.please');
|
|
8
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
|
-
|
|
10
|
-
const DEFAULT_CONFIG = {
|
|
11
|
-
apiKey: '',
|
|
12
|
-
baseUrl: 'https://api.openai.com/v1',
|
|
13
|
-
model: 'gpt-4-turbo',
|
|
14
|
-
provider: 'openai', // Mastra provider: openai, anthropic, deepseek, google, groq, mistral, cohere 等
|
|
15
|
-
shellHook: false, // 是否启用 shell hook 记录终端命令
|
|
16
|
-
chatHistoryLimit: 10 // chat 对话历史保留轮数
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// 导出配置目录路径
|
|
20
|
-
export { CONFIG_DIR };
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 确保配置目录存在
|
|
24
|
-
*/
|
|
25
|
-
function ensureConfigDir() {
|
|
26
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
27
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 读取配置
|
|
33
|
-
*/
|
|
34
|
-
export function getConfig() {
|
|
35
|
-
ensureConfigDir();
|
|
36
|
-
|
|
37
|
-
if (!fs.existsSync(CONFIG_FILE)) {
|
|
38
|
-
return { ...DEFAULT_CONFIG };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
43
|
-
return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
|
|
44
|
-
} catch {
|
|
45
|
-
return { ...DEFAULT_CONFIG };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 保存配置
|
|
51
|
-
*/
|
|
52
|
-
export function saveConfig(config) {
|
|
53
|
-
ensureConfigDir();
|
|
54
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 设置单个配置项
|
|
59
|
-
*/
|
|
60
|
-
export function setConfigValue(key, value) {
|
|
61
|
-
const config = getConfig();
|
|
62
|
-
if (!(key in DEFAULT_CONFIG)) {
|
|
63
|
-
throw new Error(`未知的配置项: ${key}`);
|
|
64
|
-
}
|
|
65
|
-
// 处理特殊类型
|
|
66
|
-
if (key === 'shellHook') {
|
|
67
|
-
config[key] = value === 'true' || value === true;
|
|
68
|
-
} else if (key === 'chatHistoryLimit') {
|
|
69
|
-
const num = parseInt(value, 10);
|
|
70
|
-
if (isNaN(num) || num < 1) {
|
|
71
|
-
throw new Error('chatHistoryLimit 必须是大于 0 的整数');
|
|
72
|
-
}
|
|
73
|
-
config[key] = num;
|
|
74
|
-
} else if (key === 'provider') {
|
|
75
|
-
// 验证 provider 值
|
|
76
|
-
const validProviders = ['openai', 'anthropic', 'deepseek', 'google', 'groq', 'mistral', 'cohere', 'fireworks', 'together'];
|
|
77
|
-
if (!validProviders.includes(value)) {
|
|
78
|
-
throw new Error(`provider 必须是以下之一: ${validProviders.join(', ')}`);
|
|
79
|
-
}
|
|
80
|
-
config[key] = value;
|
|
81
|
-
} else {
|
|
82
|
-
config[key] = value;
|
|
83
|
-
}
|
|
84
|
-
saveConfig(config);
|
|
85
|
-
return config;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* 检查配置是否有效
|
|
90
|
-
*/
|
|
91
|
-
export function isConfigValid() {
|
|
92
|
-
const config = getConfig();
|
|
93
|
-
return config.apiKey && config.apiKey.length > 0;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 隐藏 API Key 中间部分
|
|
98
|
-
*/
|
|
99
|
-
export function maskApiKey(apiKey) {
|
|
100
|
-
if (!apiKey || apiKey.length < 10) return apiKey || '(未设置)';
|
|
101
|
-
return apiKey.slice(0, 6) + '****' + apiKey.slice(-4);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* 显示当前配置
|
|
106
|
-
*/
|
|
107
|
-
export function displayConfig() {
|
|
108
|
-
const config = getConfig();
|
|
109
|
-
console.log(chalk.bold('\n当前配置:'));
|
|
110
|
-
console.log(chalk.gray('━'.repeat(40)));
|
|
111
|
-
console.log(` ${chalk.cyan('apiKey')}: ${maskApiKey(config.apiKey)}`);
|
|
112
|
-
console.log(` ${chalk.cyan('baseUrl')}: ${config.baseUrl}`);
|
|
113
|
-
console.log(` ${chalk.cyan('provider')}: ${config.provider}`);
|
|
114
|
-
console.log(` ${chalk.cyan('model')}: ${config.model}`);
|
|
115
|
-
console.log(` ${chalk.cyan('shellHook')}: ${config.shellHook ? chalk.green('已启用') : chalk.gray('未启用')}`);
|
|
116
|
-
console.log(` ${chalk.cyan('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`);
|
|
117
|
-
console.log(chalk.gray('━'.repeat(40)));
|
|
118
|
-
console.log(chalk.gray(`配置文件: ${CONFIG_FILE}\n`));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 创建 readline 接口
|
|
123
|
-
*/
|
|
124
|
-
function createReadlineInterface() {
|
|
125
|
-
return readline.createInterface({
|
|
126
|
-
input: process.stdin,
|
|
127
|
-
output: process.stdout
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* 异步提问
|
|
133
|
-
*/
|
|
134
|
-
function question(rl, prompt) {
|
|
135
|
-
return new Promise((resolve) => {
|
|
136
|
-
rl.question(prompt, (answer) => {
|
|
137
|
-
resolve(answer);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 交互式配置向导
|
|
144
|
-
*/
|
|
145
|
-
export async function runConfigWizard() {
|
|
146
|
-
const rl = createReadlineInterface();
|
|
147
|
-
const config = getConfig();
|
|
148
|
-
|
|
149
|
-
console.log(chalk.bold.hex('#00D9FF')('\n🔧 Pretty Please 配置向导'));
|
|
150
|
-
console.log(chalk.gray('━'.repeat(50)));
|
|
151
|
-
console.log(chalk.gray('直接回车使用默认值,输入值后回车确认\n'));
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
// 1. Provider
|
|
155
|
-
const validProviders = ['openai', 'anthropic', 'deepseek', 'google', 'groq', 'mistral', 'cohere', 'fireworks', 'together'];
|
|
156
|
-
const providerHint = chalk.gray(`(可选: ${validProviders.join(', ')})`);
|
|
157
|
-
const providerPrompt = `${chalk.cyan('Provider')} ${providerHint}\n${chalk.gray('默认:')} ${chalk.yellow(config.provider)} ${chalk.gray('→')} `;
|
|
158
|
-
const provider = await question(rl, providerPrompt);
|
|
159
|
-
if (provider.trim()) {
|
|
160
|
-
if (!validProviders.includes(provider.trim())) {
|
|
161
|
-
console.log(chalk.hex('#EF4444')(`\n✗ 无效的 provider,必须是以下之一: ${validProviders.join(', ')}`));
|
|
162
|
-
console.log();
|
|
163
|
-
rl.close();
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
config.provider = provider.trim();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// 2. Base URL
|
|
170
|
-
const baseUrlPrompt = `${chalk.cyan('API Base URL')}\n${chalk.gray('默认:')} ${chalk.yellow(config.baseUrl)} ${chalk.gray('→')} `;
|
|
171
|
-
const baseUrl = await question(rl, baseUrlPrompt);
|
|
172
|
-
if (baseUrl.trim()) {
|
|
173
|
-
config.baseUrl = baseUrl.trim();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 3. API Key
|
|
177
|
-
const currentKeyDisplay = config.apiKey ? maskApiKey(config.apiKey) : '(未设置)';
|
|
178
|
-
const apiKeyPrompt = `${chalk.cyan('API Key')} ${chalk.gray(`(当前: ${currentKeyDisplay})`)}\n${chalk.gray('→')} `;
|
|
179
|
-
const apiKey = await question(rl, apiKeyPrompt);
|
|
180
|
-
if (apiKey.trim()) {
|
|
181
|
-
config.apiKey = apiKey.trim();
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 4. Model
|
|
185
|
-
const modelPrompt = `${chalk.cyan('Model')}\n${chalk.gray('默认:')} ${chalk.yellow(config.model)} ${chalk.gray('→')} `;
|
|
186
|
-
const model = await question(rl, modelPrompt);
|
|
187
|
-
if (model.trim()) {
|
|
188
|
-
config.model = model.trim();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// 5. Shell Hook
|
|
192
|
-
const shellHookPrompt = `${chalk.cyan('启用 Shell Hook')} ${chalk.gray('(记录终端命令历史)')}\n${chalk.gray('默认:')} ${chalk.yellow(config.shellHook ? 'true' : 'false')} ${chalk.gray('→')} `;
|
|
193
|
-
const shellHook = await question(rl, shellHookPrompt);
|
|
194
|
-
if (shellHook.trim()) {
|
|
195
|
-
config.shellHook = shellHook.trim() === 'true';
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 6. Chat History Limit
|
|
199
|
-
const chatHistoryPrompt = `${chalk.cyan('Chat 历史保留轮数')}\n${chalk.gray('默认:')} ${chalk.yellow(config.chatHistoryLimit)} ${chalk.gray('→')} `;
|
|
200
|
-
const chatHistoryLimit = await question(rl, chatHistoryPrompt);
|
|
201
|
-
if (chatHistoryLimit.trim()) {
|
|
202
|
-
const num = parseInt(chatHistoryLimit.trim(), 10);
|
|
203
|
-
if (!isNaN(num) && num > 0) {
|
|
204
|
-
config.chatHistoryLimit = num;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
saveConfig(config);
|
|
209
|
-
|
|
210
|
-
console.log('\n' + chalk.gray('━'.repeat(50)));
|
|
211
|
-
console.log(chalk.hex('#10B981')('✅ 配置已保存'));
|
|
212
|
-
console.log(chalk.gray(` ${CONFIG_FILE}`));
|
|
213
|
-
console.log();
|
|
214
|
-
|
|
215
|
-
} catch (error) {
|
|
216
|
-
console.log(chalk.hex('#EF4444')(`\n✗ 配置失败: ${error.message}`));
|
|
217
|
-
console.log();
|
|
218
|
-
} finally {
|
|
219
|
-
rl.close();
|
|
220
|
-
}
|
|
221
|
-
}
|
package/src/history.js
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
|
|
5
|
-
const CONFIG_DIR = path.join(os.homedir(), '.please');
|
|
6
|
-
const HISTORY_FILE = path.join(CONFIG_DIR, 'history.json');
|
|
7
|
-
const MAX_HISTORY = 10;
|
|
8
|
-
const MAX_OUTPUT_LENGTH = 500;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 确保配置目录存在
|
|
12
|
-
*/
|
|
13
|
-
function ensureConfigDir() {
|
|
14
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
15
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 读取历史记录
|
|
21
|
-
*/
|
|
22
|
-
export function getHistory() {
|
|
23
|
-
ensureConfigDir();
|
|
24
|
-
|
|
25
|
-
if (!fs.existsSync(HISTORY_FILE)) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const content = fs.readFileSync(HISTORY_FILE, 'utf-8');
|
|
31
|
-
return JSON.parse(content);
|
|
32
|
-
} catch {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 保存历史记录
|
|
39
|
-
*/
|
|
40
|
-
function saveHistory(history) {
|
|
41
|
-
ensureConfigDir();
|
|
42
|
-
fs.writeFileSync(HISTORY_FILE, JSON.stringify(history, null, 2));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 添加一条历史记录
|
|
47
|
-
*/
|
|
48
|
-
export function addHistory(record) {
|
|
49
|
-
const history = getHistory();
|
|
50
|
-
|
|
51
|
-
// 截断输出
|
|
52
|
-
if (record.output && record.output.length > MAX_OUTPUT_LENGTH) {
|
|
53
|
-
record.output = record.output.slice(0, MAX_OUTPUT_LENGTH) + '...(截断)';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 添加时间戳
|
|
57
|
-
record.timestamp = new Date().toISOString();
|
|
58
|
-
|
|
59
|
-
// 添加到开头
|
|
60
|
-
history.unshift(record);
|
|
61
|
-
|
|
62
|
-
// 保留最近 N 条
|
|
63
|
-
if (history.length > MAX_HISTORY) {
|
|
64
|
-
history.length = MAX_HISTORY;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
saveHistory(history);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 清空历史记录
|
|
72
|
-
*/
|
|
73
|
-
export function clearHistory() {
|
|
74
|
-
saveHistory([]);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 格式化历史记录供 AI 使用
|
|
79
|
-
*/
|
|
80
|
-
export function formatHistoryForAI() {
|
|
81
|
-
const history = getHistory();
|
|
82
|
-
|
|
83
|
-
if (history.length === 0) {
|
|
84
|
-
return '';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const lines = history.map((item, index) => {
|
|
88
|
-
const timeAgo = getTimeAgo(item.timestamp);
|
|
89
|
-
|
|
90
|
-
let status;
|
|
91
|
-
if (item.executed) {
|
|
92
|
-
status = item.exitCode === 0 ? '✓' : `✗ 退出码:${item.exitCode}`;
|
|
93
|
-
} else if (item.reason === 'builtin') {
|
|
94
|
-
status = '(包含 builtin,未执行)';
|
|
95
|
-
} else {
|
|
96
|
-
status = '(用户取消执行)';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let line = `${index + 1}. [${timeAgo}] "${item.userPrompt}" → ${item.command} ${status}`;
|
|
100
|
-
|
|
101
|
-
// 如果有输出且命令失败,附加输出摘要
|
|
102
|
-
if (item.output && item.exitCode !== 0) {
|
|
103
|
-
line += `\n 输出: ${item.output.split('\n')[0]}`; // 只取第一行
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return line;
|
|
107
|
-
}).reverse(); // 从旧到新排列
|
|
108
|
-
|
|
109
|
-
return `【最近通过 pls 执行的命令】\n${lines.join('\n')}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 计算时间差的友好显示
|
|
114
|
-
*/
|
|
115
|
-
function getTimeAgo(timestamp) {
|
|
116
|
-
const now = Date.now();
|
|
117
|
-
const then = new Date(timestamp).getTime();
|
|
118
|
-
const diff = Math.floor((now - then) / 1000);
|
|
119
|
-
|
|
120
|
-
if (diff < 60) return '刚刚';
|
|
121
|
-
if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
|
|
122
|
-
if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
|
|
123
|
-
return `${Math.floor(diff / 86400)}天前`;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* 获取历史记录文件路径(供显示用)
|
|
128
|
-
*/
|
|
129
|
-
export function getHistoryFilePath() {
|
|
130
|
-
return HISTORY_FILE;
|
|
131
|
-
}
|