@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/dist/bin/pls.js
CHANGED
|
@@ -5,30 +5,34 @@ import { Command } from 'commander';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { dirname, join } from 'path';
|
|
7
7
|
import { exec } from 'child_process';
|
|
8
|
-
import fs from 'fs';
|
|
9
8
|
import os from 'os';
|
|
10
9
|
import chalk from 'chalk';
|
|
11
10
|
import { MultiStepCommandGenerator } from '../src/components/MultiStepCommandGenerator.js';
|
|
12
11
|
import { Chat } from '../src/components/Chat.js';
|
|
13
12
|
import { isConfigValid, setConfigValue, getConfig, maskApiKey } from '../src/config.js';
|
|
14
13
|
import { clearHistory, addHistory, getHistory, getHistoryFilePath } from '../src/history.js';
|
|
15
|
-
import { clearChatHistory, getChatRoundCount, getChatHistoryFilePath } from '../src/chat-history.js';
|
|
16
|
-
import { installShellHook, uninstallShellHook, getHookStatus, detectShell, getShellConfigPath, } from '../src/shell-hook.js';
|
|
14
|
+
import { clearChatHistory, getChatRoundCount, getChatHistoryFilePath, displayChatHistory } from '../src/chat-history.js';
|
|
15
|
+
import { installShellHook, uninstallShellHook, getHookStatus, detectShell, getShellConfigPath, displayShellHistory, clearShellHistory, } from '../src/shell-hook.js';
|
|
17
16
|
import * as console2 from '../src/utils/console.js';
|
|
18
|
-
//
|
|
17
|
+
// 导入 package.json(Bun 会自动打包进二进制)
|
|
18
|
+
import packageJson from '../package.json';
|
|
19
|
+
// 保留这些用于其他可能的用途
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
21
|
const __dirname = dirname(__filename);
|
|
21
|
-
const packageJson = JSON.parse(fs.readFileSync(join(__dirname, '../package.json'), 'utf-8'));
|
|
22
22
|
const program = new Command();
|
|
23
23
|
/**
|
|
24
24
|
* 执行命令(原生版本)
|
|
25
25
|
*/
|
|
26
|
-
function executeCommand(command
|
|
26
|
+
function executeCommand(command) {
|
|
27
27
|
return new Promise((resolve) => {
|
|
28
28
|
let output = '';
|
|
29
29
|
let hasOutput = false;
|
|
30
30
|
console.log(''); // 空行
|
|
31
|
-
|
|
31
|
+
// 计算命令框宽度,让分隔线长度一致
|
|
32
|
+
const lines = command.split('\n');
|
|
33
|
+
const maxContentWidth = Math.max(...lines.map(l => console2.getDisplayWidth(l)));
|
|
34
|
+
const boxWidth = Math.max(maxContentWidth + 4, console2.getDisplayWidth('生成命令') + 6, 20);
|
|
35
|
+
console2.printSeparator('输出', boxWidth);
|
|
32
36
|
const child = exec(command);
|
|
33
37
|
child.stdout?.on('data', (data) => {
|
|
34
38
|
output += data;
|
|
@@ -42,16 +46,16 @@ function executeCommand(command, prompt) {
|
|
|
42
46
|
});
|
|
43
47
|
child.on('close', (code) => {
|
|
44
48
|
if (hasOutput) {
|
|
45
|
-
console2.printSeparator('');
|
|
49
|
+
console2.printSeparator('', boxWidth);
|
|
46
50
|
}
|
|
47
51
|
resolve({ exitCode: code || 0, output });
|
|
48
52
|
});
|
|
49
53
|
child.on('error', (err) => {
|
|
50
54
|
if (!hasOutput) {
|
|
51
|
-
console2.printSeparator('');
|
|
55
|
+
console2.printSeparator('', boxWidth);
|
|
52
56
|
}
|
|
53
57
|
console2.error(err.message);
|
|
54
|
-
console2.printSeparator('');
|
|
58
|
+
console2.printSeparator('', boxWidth);
|
|
55
59
|
resolve({ exitCode: 1, output: err.message });
|
|
56
60
|
});
|
|
57
61
|
});
|
|
@@ -73,14 +77,17 @@ configCmd
|
|
|
73
77
|
const CONFIG_FILE = join(os.homedir(), '.please', 'config.json');
|
|
74
78
|
console.log('');
|
|
75
79
|
console2.title('当前配置:');
|
|
76
|
-
console2.muted('━'.repeat(
|
|
77
|
-
console.log(` ${chalk.hex('#00D9FF')('apiKey')}:
|
|
78
|
-
console.log(` ${chalk.hex('#00D9FF')('baseUrl')}:
|
|
79
|
-
console.log(` ${chalk.hex('#00D9FF')('provider')}:
|
|
80
|
-
console.log(` ${chalk.hex('#00D9FF')('model')}:
|
|
81
|
-
console.log(` ${chalk.hex('#00D9FF')('shellHook')}:
|
|
82
|
-
console.log(` ${chalk.hex('#00D9FF')('
|
|
83
|
-
|
|
80
|
+
console2.muted('━'.repeat(50));
|
|
81
|
+
console.log(` ${chalk.hex('#00D9FF')('apiKey')}: ${maskApiKey(config.apiKey)}`);
|
|
82
|
+
console.log(` ${chalk.hex('#00D9FF')('baseUrl')}: ${config.baseUrl}`);
|
|
83
|
+
console.log(` ${chalk.hex('#00D9FF')('provider')}: ${config.provider}`);
|
|
84
|
+
console.log(` ${chalk.hex('#00D9FF')('model')}: ${config.model}`);
|
|
85
|
+
console.log(` ${chalk.hex('#00D9FF')('shellHook')}: ${config.shellHook ? chalk.hex('#10B981')('已启用') : chalk.gray('未启用')}`);
|
|
86
|
+
console.log(` ${chalk.hex('#00D9FF')('editMode')}: ${config.editMode === 'auto' ? chalk.hex('#00D9FF')('auto (自动编辑)') : chalk.gray('manual (按E编辑)')}`);
|
|
87
|
+
console.log(` ${chalk.hex('#00D9FF')('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`);
|
|
88
|
+
console.log(` ${chalk.hex('#00D9FF')('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`);
|
|
89
|
+
console.log(` ${chalk.hex('#00D9FF')('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`);
|
|
90
|
+
console2.muted('━'.repeat(50));
|
|
84
91
|
console2.muted(`配置文件: ${CONFIG_FILE}`);
|
|
85
92
|
console.log('');
|
|
86
93
|
});
|
|
@@ -129,7 +136,14 @@ historyCmd
|
|
|
129
136
|
: chalk.hex('#EF4444')(`✗ 退出码:${item.exitCode}`)
|
|
130
137
|
: chalk.gray('(未执行)');
|
|
131
138
|
console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex('#00D9FF')(item.userPrompt)}`);
|
|
132
|
-
|
|
139
|
+
// 显示用户修改信息
|
|
140
|
+
if (item.userModified && item.aiGeneratedCommand) {
|
|
141
|
+
console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(item.aiGeneratedCommand)}`);
|
|
142
|
+
console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.yellow('(已修改)')}`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log(` ${chalk.dim('→')} ${item.command} ${status}`);
|
|
146
|
+
}
|
|
133
147
|
console.log(` ${chalk.gray(item.timestamp)}`);
|
|
134
148
|
});
|
|
135
149
|
console.log('');
|
|
@@ -145,7 +159,32 @@ historyCmd
|
|
|
145
159
|
console2.success('历史记录已清空');
|
|
146
160
|
console.log('');
|
|
147
161
|
});
|
|
148
|
-
//
|
|
162
|
+
// history chat 子命令
|
|
163
|
+
const historyChatCmd = historyCmd.command('chat').description('查看或管理对话历史');
|
|
164
|
+
historyChatCmd.action(() => {
|
|
165
|
+
displayChatHistory();
|
|
166
|
+
});
|
|
167
|
+
historyChatCmd
|
|
168
|
+
.command('clear')
|
|
169
|
+
.description('清空对话历史')
|
|
170
|
+
.action(() => {
|
|
171
|
+
clearChatHistory();
|
|
172
|
+
console.log('');
|
|
173
|
+
console2.success('对话历史已清空');
|
|
174
|
+
console.log('');
|
|
175
|
+
});
|
|
176
|
+
// history shell 子命令
|
|
177
|
+
const historyShellCmd = historyCmd.command('shell').description('查看或管理 Shell 历史');
|
|
178
|
+
historyShellCmd.action(() => {
|
|
179
|
+
displayShellHistory();
|
|
180
|
+
});
|
|
181
|
+
historyShellCmd
|
|
182
|
+
.command('clear')
|
|
183
|
+
.description('清空 Shell 历史')
|
|
184
|
+
.action(() => {
|
|
185
|
+
clearShellHistory();
|
|
186
|
+
});
|
|
187
|
+
// 默认 history 命令(显示命令历史)
|
|
149
188
|
historyCmd.action(() => {
|
|
150
189
|
const history = getHistory();
|
|
151
190
|
if (history.length === 0) {
|
|
@@ -164,7 +203,14 @@ historyCmd.action(() => {
|
|
|
164
203
|
: chalk.hex('#EF4444')(`✗ 退出码:${item.exitCode}`)
|
|
165
204
|
: chalk.gray('(未执行)');
|
|
166
205
|
console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex('#00D9FF')(item.userPrompt)}`);
|
|
167
|
-
|
|
206
|
+
// 显示用户修改信息
|
|
207
|
+
if (item.userModified && item.aiGeneratedCommand) {
|
|
208
|
+
console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(item.aiGeneratedCommand)}`);
|
|
209
|
+
console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.yellow('(已修改)')}`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
console.log(` ${chalk.dim('→')} ${item.command} ${status}`);
|
|
213
|
+
}
|
|
168
214
|
console.log(` ${chalk.gray(item.timestamp)}`);
|
|
169
215
|
});
|
|
170
216
|
console.log('');
|
|
@@ -324,6 +370,7 @@ program
|
|
|
324
370
|
(async () => {
|
|
325
371
|
const executedSteps = [];
|
|
326
372
|
let currentStepNumber = 1;
|
|
373
|
+
let lastStepFailed = false; // 跟踪上一步是否失败
|
|
327
374
|
while (true) {
|
|
328
375
|
let stepResult = null;
|
|
329
376
|
// 使用 Ink 渲染命令生成
|
|
@@ -344,6 +391,8 @@ program
|
|
|
344
391
|
addHistory({
|
|
345
392
|
userPrompt: currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${prompt}`,
|
|
346
393
|
command: stepResult.command,
|
|
394
|
+
aiGeneratedCommand: stepResult.aiGeneratedCommand, // AI 原始命令
|
|
395
|
+
userModified: stepResult.userModified || false,
|
|
347
396
|
executed: false,
|
|
348
397
|
exitCode: null,
|
|
349
398
|
output: '',
|
|
@@ -352,9 +401,31 @@ program
|
|
|
352
401
|
process.exit(0);
|
|
353
402
|
}
|
|
354
403
|
if (stepResult.confirmed) {
|
|
404
|
+
// 如果命令为空,说明 AI 决定放弃
|
|
405
|
+
if (!stepResult.command || stepResult.command.trim() === '') {
|
|
406
|
+
console.log('');
|
|
407
|
+
if (stepResult.reasoning) {
|
|
408
|
+
console2.info(`💡 AI 分析: ${stepResult.reasoning}`);
|
|
409
|
+
}
|
|
410
|
+
console2.muted('❌ AI 决定停止尝试,任务失败');
|
|
411
|
+
console.log('');
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
// 特殊处理:如果上一步失败,且 AI 决定放弃(continue: false),直接显示原因并退出
|
|
415
|
+
if (lastStepFailed &&
|
|
416
|
+
stepResult.needsContinue === false &&
|
|
417
|
+
stepResult.command.startsWith('echo')) {
|
|
418
|
+
console.log('');
|
|
419
|
+
if (stepResult.reasoning) {
|
|
420
|
+
console2.info(`💡 AI 分析: ${stepResult.reasoning}`);
|
|
421
|
+
}
|
|
422
|
+
console2.muted('❌ AI 决定停止尝试,任务失败');
|
|
423
|
+
console.log('');
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
355
426
|
// 执行命令
|
|
356
427
|
const execStart = Date.now();
|
|
357
|
-
const { exitCode, output } = await executeCommand(stepResult.command
|
|
428
|
+
const { exitCode, output } = await executeCommand(stepResult.command);
|
|
358
429
|
const execDuration = Date.now() - execStart;
|
|
359
430
|
// 保存到执行历史
|
|
360
431
|
const executedStep = {
|
|
@@ -370,6 +441,8 @@ program
|
|
|
370
441
|
addHistory({
|
|
371
442
|
userPrompt: currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${stepResult.reasoning || prompt}`,
|
|
372
443
|
command: stepResult.command,
|
|
444
|
+
aiGeneratedCommand: stepResult.aiGeneratedCommand, // AI 原始命令
|
|
445
|
+
userModified: stepResult.userModified || false,
|
|
373
446
|
executed: true,
|
|
374
447
|
exitCode,
|
|
375
448
|
output,
|
|
@@ -385,13 +458,18 @@ program
|
|
|
385
458
|
// 多步命令
|
|
386
459
|
console2.success(`步骤 ${currentStepNumber} 执行完成 ${console2.formatDuration(execDuration)}`);
|
|
387
460
|
}
|
|
461
|
+
lastStepFailed = false;
|
|
388
462
|
}
|
|
389
463
|
else {
|
|
390
|
-
//
|
|
464
|
+
// 执行失败,标记状态
|
|
391
465
|
console2.error(`步骤 ${currentStepNumber} 执行失败,退出码: ${exitCode} ${console2.formatDuration(execDuration)}`);
|
|
392
466
|
console.log('');
|
|
393
467
|
console2.warning('正在请 AI 分析错误并调整策略...');
|
|
394
|
-
|
|
468
|
+
lastStepFailed = true;
|
|
469
|
+
// 继续循环,让 AI 分析错误
|
|
470
|
+
console.log('');
|
|
471
|
+
currentStepNumber++;
|
|
472
|
+
continue;
|
|
395
473
|
}
|
|
396
474
|
// 判断是否继续
|
|
397
475
|
if (stepResult.needsContinue !== true) {
|
|
@@ -405,6 +483,21 @@ program
|
|
|
405
483
|
console.log('');
|
|
406
484
|
currentStepNumber++;
|
|
407
485
|
}
|
|
486
|
+
else if (!stepResult.confirmed && !stepResult.cancelled) {
|
|
487
|
+
// AI 返回了结果但没有确认(空命令的情况)
|
|
488
|
+
if (lastStepFailed && stepResult.reasoning) {
|
|
489
|
+
console.log('');
|
|
490
|
+
console2.info(`💡 AI 分析: ${stepResult.reasoning}`);
|
|
491
|
+
console2.muted('❌ AI 决定停止尝试,任务失败');
|
|
492
|
+
console.log('');
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
// 其他情况也退出
|
|
496
|
+
console.log('');
|
|
497
|
+
console2.muted('任务结束');
|
|
498
|
+
console.log('');
|
|
499
|
+
process.exit(0);
|
|
500
|
+
}
|
|
408
501
|
}
|
|
409
502
|
})();
|
|
410
503
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yivan-lab/pretty-please",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "AI 驱动的命令行工具,将自然语言转换为可执行的 Shell 命令",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pls": "./dist/bin/pls.js",
|
|
8
|
+
"please": "./dist/bin/pls.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "tsx bin/pls.tsx",
|
|
12
|
+
"build": "tsc && node scripts/postbuild.js",
|
|
13
|
+
"start": "node dist/bin/pls.js",
|
|
14
|
+
"link:dev": "mkdir -p ~/.local/bin && ln -sf \"$(pwd)/bin/pls.tsx\" ~/.local/bin/pls-dev && echo '✅ pls-dev 已链接到 ~/.local/bin/pls-dev'",
|
|
15
|
+
"unlink:dev": "rm -f ~/.local/bin/pls-dev && echo '✅ pls-dev 已移除'",
|
|
16
|
+
"prepublishOnly": "pnpm build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"ai",
|
|
21
|
+
"shell",
|
|
22
|
+
"openai",
|
|
23
|
+
"command-line",
|
|
24
|
+
"natural-language",
|
|
25
|
+
"terminal",
|
|
26
|
+
"assistant",
|
|
27
|
+
"mastra",
|
|
28
|
+
"deepseek"
|
|
29
|
+
],
|
|
30
|
+
"author": "yivan-lab",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/yivan-lab/pretty-please.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/yivan-lab/pretty-please/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/yivan-lab/pretty-please#readme",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"bin",
|
|
45
|
+
"dist/bin",
|
|
46
|
+
"dist/src",
|
|
47
|
+
"dist/package.json",
|
|
48
|
+
"src",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE",
|
|
51
|
+
"tsconfig.json"
|
|
52
|
+
],
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@mastra/core": "^0.24.8",
|
|
55
|
+
"chalk": "^5.6.2",
|
|
56
|
+
"cli-highlight": "^2.1.11",
|
|
57
|
+
"commander": "^14.0.2",
|
|
58
|
+
"ink": "^6.5.1",
|
|
59
|
+
"ink-box": "^2.0.0",
|
|
60
|
+
"ink-markdown": "^1.0.4",
|
|
61
|
+
"ink-select-input": "^6.2.0",
|
|
62
|
+
"ink-spinner": "^5.0.0",
|
|
63
|
+
"ink-text-input": "^6.0.0",
|
|
64
|
+
"lowlight": "^3.3.0",
|
|
65
|
+
"marked": "^17.0.1",
|
|
66
|
+
"react": "^19.2.3",
|
|
67
|
+
"shiki": "^3.20.0",
|
|
68
|
+
"string-width": "^8.1.0",
|
|
69
|
+
"wrap-ansi": "^9.0.2",
|
|
70
|
+
"zod": "^3.25.76"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/hast": "^3.0.4",
|
|
74
|
+
"@types/node": "^25.0.2",
|
|
75
|
+
"@types/react": "^19.2.7",
|
|
76
|
+
"react-devtools-core": "^7.0.1",
|
|
77
|
+
"tsx": "^4.21.0",
|
|
78
|
+
"typescript": "^5.9.3"
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/src/ai.d.ts
CHANGED
|
@@ -1,45 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @param {string} prompt 用户输入的自然语言描述
|
|
4
|
-
* @param {object} options 选项
|
|
5
|
-
* @param {boolean} options.debug 是否返回调试信息
|
|
6
|
-
*/
|
|
7
|
-
export function generateCommand(prompt: string, options?: {
|
|
8
|
-
debug: boolean;
|
|
9
|
-
}): Promise<string | {
|
|
10
|
-
command: string;
|
|
11
|
-
debug: {
|
|
12
|
-
sysinfo: string;
|
|
13
|
-
model: any;
|
|
14
|
-
systemPrompt: string;
|
|
15
|
-
userPrompt: string;
|
|
16
|
-
};
|
|
17
|
-
}>;
|
|
18
|
-
/**
|
|
19
|
-
* 调用 AI 进行对话(chat 模式,支持流式输出)
|
|
20
|
-
* @param {string} prompt 用户输入的问题
|
|
21
|
-
* @param {object} options 选项
|
|
22
|
-
* @param {boolean} options.debug 是否返回调试信息
|
|
23
|
-
* @param {function} options.onChunk 流式输出回调,接收每个文本片段
|
|
24
|
-
*/
|
|
25
|
-
export function chatWithAI(prompt: string, options?: {
|
|
26
|
-
debug: boolean;
|
|
27
|
-
onChunk: Function;
|
|
28
|
-
}): Promise<string | {
|
|
29
|
-
reply: string;
|
|
30
|
-
debug: {
|
|
31
|
-
sysinfo: string;
|
|
32
|
-
model: any;
|
|
33
|
-
systemPrompt: string;
|
|
34
|
-
chatHistory: {
|
|
35
|
-
role: string;
|
|
36
|
-
content: string;
|
|
37
|
-
}[];
|
|
38
|
-
userPrompt: string;
|
|
39
|
-
};
|
|
40
|
-
}>;
|
|
41
|
-
/**
|
|
42
|
-
* 生成系统提示词
|
|
2
|
+
* 生成命令生成模式的系统提示词
|
|
43
3
|
* @param {string} sysinfo - 系统信息
|
|
44
4
|
* @param {string} plsHistory - pls 命令历史
|
|
45
5
|
* @param {string} shellHistory - shell 终端历史
|
package/dist/src/ai.js
CHANGED
|
@@ -1,27 +1,11 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
import { getConfig } from './config.js';
|
|
3
|
-
import { formatSystemInfo } from './sysinfo.js';
|
|
4
|
-
import { formatHistoryForAI } from './history.js';
|
|
5
|
-
import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js';
|
|
6
|
-
import { getChatHistory, addChatMessage } from './chat-history.js';
|
|
7
1
|
/**
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
function createClient() {
|
|
11
|
-
const config = getConfig();
|
|
12
|
-
return new OpenAI({
|
|
13
|
-
apiKey: config.apiKey,
|
|
14
|
-
baseURL: config.baseUrl
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* 生成系统提示词
|
|
2
|
+
* 生成命令生成模式的系统提示词
|
|
19
3
|
* @param {string} sysinfo - 系统信息
|
|
20
4
|
* @param {string} plsHistory - pls 命令历史
|
|
21
5
|
* @param {string} shellHistory - shell 终端历史
|
|
22
6
|
* @param {boolean} shellHookEnabled - 是否启用了 shell hook
|
|
23
7
|
*/
|
|
24
|
-
function buildSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled) {
|
|
8
|
+
export function buildSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled) {
|
|
25
9
|
let prompt = `你是一个专业的 shell 脚本生成器。用户会提供他们的系统信息和一个命令需求。
|
|
26
10
|
你的任务是返回一个可执行的、原始的 shell 命令或脚本来完成他们的目标。
|
|
27
11
|
|
|
@@ -51,8 +35,8 @@ function buildSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
|
|
|
51
35
|
{
|
|
52
36
|
"command": "find . -name '*.log' -size +100M",
|
|
53
37
|
"continue": true,
|
|
54
|
-
"reasoning": "查找大日志",
|
|
55
|
-
"nextStepHint": "压缩找到的文件"
|
|
38
|
+
"reasoning": "查找大日志", (精简即可)
|
|
39
|
+
"nextStepHint": "压缩找到的文件" (精简即可)
|
|
56
40
|
}
|
|
57
41
|
|
|
58
42
|
执行后你会收到:
|
|
@@ -102,13 +86,15 @@ mv: rename ./test.zip to ./c/test.zip: No such file or directory
|
|
|
102
86
|
"reasoning": "改用 cp 复制而非 mv"
|
|
103
87
|
}
|
|
104
88
|
|
|
105
|
-
|
|
89
|
+
或者如果决定放弃(无法修正),返回:
|
|
106
90
|
{
|
|
107
|
-
"command": "
|
|
91
|
+
"command": "",
|
|
108
92
|
"continue": false,
|
|
109
|
-
"reasoning": "
|
|
93
|
+
"reasoning": "文件不存在且无法恢复,任务无法继续"
|
|
110
94
|
}
|
|
111
95
|
|
|
96
|
+
重要:当 continue: false 且决定放弃时,command 可以留空,重点是在 reasoning 中说明为什么放弃。
|
|
97
|
+
|
|
112
98
|
关于 pls/please 工具:
|
|
113
99
|
用户正在使用 pls(pretty-please)工具,这是一个将自然语言转换为 shell 命令的 AI 助手。
|
|
114
100
|
当用户输入 "pls <描述>" 时,AI(也就是你)会生成对应的 shell 命令供用户确认执行。
|
|
@@ -126,170 +112,3 @@ mv: rename ./test.zip to ./c/test.zip: No such file or directory
|
|
|
126
112
|
}
|
|
127
113
|
return prompt;
|
|
128
114
|
}
|
|
129
|
-
// 导出给其他模块使用
|
|
130
|
-
export { buildSystemPrompt };
|
|
131
|
-
/**
|
|
132
|
-
* 调用 AI 生成命令
|
|
133
|
-
* @param {string} prompt 用户输入的自然语言描述
|
|
134
|
-
* @param {object} options 选项
|
|
135
|
-
* @param {boolean} options.debug 是否返回调试信息
|
|
136
|
-
*/
|
|
137
|
-
export async function generateCommand(prompt, options = {}) {
|
|
138
|
-
const config = getConfig();
|
|
139
|
-
const client = createClient();
|
|
140
|
-
const sysinfo = formatSystemInfo();
|
|
141
|
-
const plsHistory = formatHistoryForAI();
|
|
142
|
-
const shellHistory = formatShellHistoryForAI();
|
|
143
|
-
const shellHookEnabled = config.shellHook && getShellHistory().length > 0;
|
|
144
|
-
const systemPrompt = buildSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled);
|
|
145
|
-
const response = await client.chat.completions.create({
|
|
146
|
-
model: config.model,
|
|
147
|
-
messages: [
|
|
148
|
-
{
|
|
149
|
-
role: 'system',
|
|
150
|
-
content: systemPrompt
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
role: 'user',
|
|
154
|
-
content: prompt
|
|
155
|
-
}
|
|
156
|
-
],
|
|
157
|
-
max_tokens: 1024,
|
|
158
|
-
temperature: 0.2
|
|
159
|
-
});
|
|
160
|
-
const command = response.choices[0]?.message?.content?.trim();
|
|
161
|
-
if (!command) {
|
|
162
|
-
throw new Error('AI 返回了空的响应');
|
|
163
|
-
}
|
|
164
|
-
if (options.debug) {
|
|
165
|
-
return {
|
|
166
|
-
command,
|
|
167
|
-
debug: {
|
|
168
|
-
sysinfo,
|
|
169
|
-
model: config.model,
|
|
170
|
-
systemPrompt,
|
|
171
|
-
userPrompt: prompt
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
return command;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* 生成 chat 模式的系统提示词
|
|
179
|
-
* @param {string} sysinfo - 系统信息
|
|
180
|
-
* @param {string} plsHistory - pls 命令历史
|
|
181
|
-
* @param {string} shellHistory - shell 终端历史
|
|
182
|
-
* @param {boolean} shellHookEnabled - 是否启用了 shell hook
|
|
183
|
-
*/
|
|
184
|
-
function buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled) {
|
|
185
|
-
let prompt = `你是一个命令行专家助手,帮助用户理解和使用命令行工具。
|
|
186
|
-
|
|
187
|
-
【你的能力】
|
|
188
|
-
- 解释命令的含义、参数、用法
|
|
189
|
-
- 分析命令的执行效果和潜在风险
|
|
190
|
-
- 回答命令行、Shell、系统管理相关问题
|
|
191
|
-
- 根据用户需求推荐合适的命令并解释
|
|
192
|
-
|
|
193
|
-
【回答要求】
|
|
194
|
-
- 简洁清晰,避免冗余
|
|
195
|
-
- 危险操作要明确警告
|
|
196
|
-
- 适当给出示例命令
|
|
197
|
-
- 结合用户的系统环境给出针对性建议
|
|
198
|
-
|
|
199
|
-
【用户系统信息】
|
|
200
|
-
${sysinfo}`;
|
|
201
|
-
// 根据是否启用 shell hook 决定展示哪个历史
|
|
202
|
-
if (shellHookEnabled && shellHistory) {
|
|
203
|
-
prompt += `\n\n${shellHistory}`;
|
|
204
|
-
}
|
|
205
|
-
else if (plsHistory) {
|
|
206
|
-
prompt += `\n\n${plsHistory}`;
|
|
207
|
-
}
|
|
208
|
-
return prompt;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* 调用 AI 进行对话(chat 模式,支持流式输出)
|
|
212
|
-
* @param {string} prompt 用户输入的问题
|
|
213
|
-
* @param {object} options 选项
|
|
214
|
-
* @param {boolean} options.debug 是否返回调试信息
|
|
215
|
-
* @param {function} options.onChunk 流式输出回调,接收每个文本片段
|
|
216
|
-
*/
|
|
217
|
-
export async function chatWithAI(prompt, options = {}) {
|
|
218
|
-
const config = getConfig();
|
|
219
|
-
const client = createClient();
|
|
220
|
-
const sysinfo = formatSystemInfo();
|
|
221
|
-
const plsHistory = formatHistoryForAI();
|
|
222
|
-
const shellHistory = formatShellHistoryForAI();
|
|
223
|
-
const shellHookEnabled = config.shellHook && getShellHistory().length > 0;
|
|
224
|
-
const systemPrompt = buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled);
|
|
225
|
-
// 获取对话历史
|
|
226
|
-
const chatHistory = getChatHistory();
|
|
227
|
-
// 构建消息数组
|
|
228
|
-
const messages = [
|
|
229
|
-
{ role: 'system', content: systemPrompt },
|
|
230
|
-
...chatHistory,
|
|
231
|
-
{ role: 'user', content: prompt }
|
|
232
|
-
];
|
|
233
|
-
// 流式输出模式
|
|
234
|
-
if (options.onChunk) {
|
|
235
|
-
const stream = await client.chat.completions.create({
|
|
236
|
-
model: config.model,
|
|
237
|
-
messages,
|
|
238
|
-
max_tokens: 2048,
|
|
239
|
-
temperature: 0.7,
|
|
240
|
-
stream: true
|
|
241
|
-
});
|
|
242
|
-
let fullContent = '';
|
|
243
|
-
for await (const chunk of stream) {
|
|
244
|
-
const content = chunk.choices[0]?.delta?.content || '';
|
|
245
|
-
if (content) {
|
|
246
|
-
fullContent += content;
|
|
247
|
-
options.onChunk(content);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
if (!fullContent) {
|
|
251
|
-
throw new Error('AI 返回了空的响应');
|
|
252
|
-
}
|
|
253
|
-
// 保存对话历史
|
|
254
|
-
addChatMessage(prompt, fullContent);
|
|
255
|
-
if (options.debug) {
|
|
256
|
-
return {
|
|
257
|
-
reply: fullContent,
|
|
258
|
-
debug: {
|
|
259
|
-
sysinfo,
|
|
260
|
-
model: config.model,
|
|
261
|
-
systemPrompt,
|
|
262
|
-
chatHistory,
|
|
263
|
-
userPrompt: prompt
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
return fullContent;
|
|
268
|
-
}
|
|
269
|
-
// 非流式模式(保持兼容)
|
|
270
|
-
const response = await client.chat.completions.create({
|
|
271
|
-
model: config.model,
|
|
272
|
-
messages,
|
|
273
|
-
max_tokens: 2048,
|
|
274
|
-
temperature: 0.7
|
|
275
|
-
});
|
|
276
|
-
const reply = response.choices[0]?.message?.content?.trim();
|
|
277
|
-
if (!reply) {
|
|
278
|
-
throw new Error('AI 返回了空的响应');
|
|
279
|
-
}
|
|
280
|
-
// 保存对话历史
|
|
281
|
-
addChatMessage(prompt, reply);
|
|
282
|
-
if (options.debug) {
|
|
283
|
-
return {
|
|
284
|
-
reply,
|
|
285
|
-
debug: {
|
|
286
|
-
sysinfo,
|
|
287
|
-
model: config.model,
|
|
288
|
-
systemPrompt,
|
|
289
|
-
chatHistory,
|
|
290
|
-
userPrompt: prompt
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
return reply;
|
|
295
|
-
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Shell builtin 命令检测器
|
|
3
|
+
*
|
|
4
|
+
* 用于检测命令中是否包含 shell 内置命令(builtin)
|
|
5
|
+
* 这些命令在子进程中执行可能无效或行为异常
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Builtin 检测结果
|
|
5
9
|
*/
|
|
6
|
-
export
|
|
10
|
+
export interface BuiltinResult {
|
|
7
11
|
hasBuiltin: boolean;
|
|
8
12
|
builtins: string[];
|
|
9
|
-
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 检测命令中是否包含 builtin
|
|
16
|
+
*/
|
|
17
|
+
export declare function detectBuiltin(command: string): BuiltinResult;
|
|
10
18
|
/**
|
|
11
19
|
* 格式化 builtin 列表为易读的字符串
|
|
12
|
-
* @param {string[]} builtins - builtin 命令数组
|
|
13
|
-
* @returns {string} 格式化后的字符串
|
|
14
20
|
*/
|
|
15
|
-
export function formatBuiltins(builtins: string[]): string;
|
|
21
|
+
export declare function formatBuiltins(builtins: string[]): string;
|