@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
package/dist/src/shell-hook.d.ts
CHANGED
|
@@ -1,35 +1,110 @@
|
|
|
1
|
+
type ShellType = 'zsh' | 'bash' | 'powershell' | 'unknown';
|
|
2
|
+
/**
|
|
3
|
+
* Shell 历史记录项
|
|
4
|
+
*/
|
|
5
|
+
export interface ShellHistoryItem {
|
|
6
|
+
cmd: string;
|
|
7
|
+
exit: number;
|
|
8
|
+
time: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Hook 状态
|
|
12
|
+
*/
|
|
13
|
+
export interface HookStatus {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
installed: boolean;
|
|
16
|
+
shellType: ShellType;
|
|
17
|
+
configPath: string | null;
|
|
18
|
+
historyFile: string;
|
|
19
|
+
}
|
|
1
20
|
/**
|
|
2
21
|
* 检测当前 shell 类型
|
|
3
22
|
*/
|
|
4
|
-
export function detectShell():
|
|
23
|
+
export declare function detectShell(): ShellType;
|
|
5
24
|
/**
|
|
6
25
|
* 获取 shell 配置文件路径
|
|
7
26
|
*/
|
|
8
|
-
export function getShellConfigPath(shellType:
|
|
27
|
+
export declare function getShellConfigPath(shellType: ShellType): string | null;
|
|
9
28
|
/**
|
|
10
29
|
* 安装 shell hook
|
|
11
30
|
*/
|
|
12
|
-
export function installShellHook(): Promise<boolean>;
|
|
31
|
+
export declare function installShellHook(): Promise<boolean>;
|
|
13
32
|
/**
|
|
14
33
|
* 卸载 shell hook
|
|
15
34
|
*/
|
|
16
|
-
export function uninstallShellHook(): boolean;
|
|
35
|
+
export declare function uninstallShellHook(): boolean;
|
|
17
36
|
/**
|
|
18
37
|
* 读取 shell 历史记录
|
|
19
38
|
*/
|
|
20
|
-
export function getShellHistory():
|
|
39
|
+
export declare function getShellHistory(): ShellHistoryItem[];
|
|
21
40
|
/**
|
|
22
41
|
* 格式化 shell 历史供 AI 使用
|
|
23
42
|
* 对于 pls 命令,会从 pls history 中查找对应的详细信息
|
|
24
43
|
*/
|
|
25
|
-
export function formatShellHistoryForAI(): string;
|
|
44
|
+
export declare function formatShellHistoryForAI(): string;
|
|
26
45
|
/**
|
|
27
46
|
* 获取 hook 状态
|
|
28
47
|
*/
|
|
29
|
-
export function getHookStatus():
|
|
30
|
-
|
|
48
|
+
export declare function getHookStatus(): HookStatus;
|
|
49
|
+
/**
|
|
50
|
+
* 显示 shell 历史
|
|
51
|
+
*/
|
|
52
|
+
export declare function displayShellHistory(): void;
|
|
53
|
+
/**
|
|
54
|
+
* 清空 shell 历史
|
|
55
|
+
*/
|
|
56
|
+
export declare function clearShellHistory(): void;
|
|
57
|
+
/**
|
|
58
|
+
* 检测远程服务器的 shell 类型
|
|
59
|
+
*/
|
|
60
|
+
export declare function detectRemoteShell(sshExecFn: (cmd: string) => Promise<{
|
|
61
|
+
stdout: string;
|
|
62
|
+
exitCode: number;
|
|
63
|
+
}>): Promise<ShellType>;
|
|
64
|
+
/**
|
|
65
|
+
* 获取远程 shell 配置文件路径
|
|
66
|
+
*/
|
|
67
|
+
export declare function getRemoteShellConfigPath(shellType: ShellType): string;
|
|
68
|
+
/**
|
|
69
|
+
* 生成远程 hook 脚本
|
|
70
|
+
*/
|
|
71
|
+
export declare function generateRemoteHookScript(shellType: ShellType): string | null;
|
|
72
|
+
/**
|
|
73
|
+
* 检查远程 hook 是否已安装
|
|
74
|
+
*/
|
|
75
|
+
export declare function checkRemoteHookInstalled(sshExecFn: (cmd: string) => Promise<{
|
|
76
|
+
stdout: string;
|
|
77
|
+
exitCode: number;
|
|
78
|
+
}>, configPath: string): Promise<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* 在远程服务器安装 shell hook
|
|
81
|
+
*/
|
|
82
|
+
export declare function installRemoteShellHook(sshExecFn: (cmd: string) => Promise<{
|
|
83
|
+
stdout: string;
|
|
84
|
+
exitCode: number;
|
|
85
|
+
}>, shellType: ShellType): Promise<{
|
|
86
|
+
success: boolean;
|
|
87
|
+
message: string;
|
|
88
|
+
}>;
|
|
89
|
+
/**
|
|
90
|
+
* 从远程服务器卸载 shell hook
|
|
91
|
+
*/
|
|
92
|
+
export declare function uninstallRemoteShellHook(sshExecFn: (cmd: string) => Promise<{
|
|
93
|
+
stdout: string;
|
|
94
|
+
exitCode: number;
|
|
95
|
+
}>, shellType: ShellType): Promise<{
|
|
96
|
+
success: boolean;
|
|
97
|
+
message: string;
|
|
98
|
+
}>;
|
|
99
|
+
/**
|
|
100
|
+
* 获取远程 hook 状态
|
|
101
|
+
*/
|
|
102
|
+
export declare function getRemoteHookStatus(sshExecFn: (cmd: string) => Promise<{
|
|
103
|
+
stdout: string;
|
|
104
|
+
exitCode: number;
|
|
105
|
+
}>): Promise<{
|
|
31
106
|
installed: boolean;
|
|
32
|
-
shellType:
|
|
33
|
-
configPath: string
|
|
34
|
-
|
|
35
|
-
};
|
|
107
|
+
shellType: ShellType;
|
|
108
|
+
configPath: string;
|
|
109
|
+
}>;
|
|
110
|
+
export {};
|
package/dist/src/shell-hook.js
CHANGED
|
@@ -4,6 +4,18 @@ import os from 'os';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { CONFIG_DIR, getConfig, setConfigValue } from './config.js';
|
|
6
6
|
import { getHistory } from './history.js';
|
|
7
|
+
import { getCurrentTheme } from './ui/theme.js';
|
|
8
|
+
// 获取主题颜色
|
|
9
|
+
function getColors() {
|
|
10
|
+
const theme = getCurrentTheme();
|
|
11
|
+
return {
|
|
12
|
+
primary: theme.primary,
|
|
13
|
+
success: theme.success,
|
|
14
|
+
error: theme.error,
|
|
15
|
+
warning: theme.warning,
|
|
16
|
+
secondary: theme.secondary,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
7
19
|
const SHELL_HISTORY_FILE = path.join(CONFIG_DIR, 'shell_history.jsonl');
|
|
8
20
|
const MAX_SHELL_HISTORY = 20;
|
|
9
21
|
// Hook 标记,用于识别我们添加的内容
|
|
@@ -157,20 +169,21 @@ function generateHookScript(shellType) {
|
|
|
157
169
|
export async function installShellHook() {
|
|
158
170
|
const shellType = detectShell();
|
|
159
171
|
const configPath = getShellConfigPath(shellType);
|
|
172
|
+
const colors = getColors();
|
|
160
173
|
if (!configPath) {
|
|
161
|
-
console.log(chalk.
|
|
174
|
+
console.log(chalk.hex(colors.error)(`❌ 不支持的 shell 类型: ${shellType}`));
|
|
162
175
|
return false;
|
|
163
176
|
}
|
|
164
177
|
const hookScript = generateHookScript(shellType);
|
|
165
178
|
if (!hookScript) {
|
|
166
|
-
console.log(chalk.
|
|
179
|
+
console.log(chalk.hex(colors.error)(`❌ 无法为 ${shellType} 生成 hook 脚本`));
|
|
167
180
|
return false;
|
|
168
181
|
}
|
|
169
182
|
// 检查是否已安装
|
|
170
183
|
if (fs.existsSync(configPath)) {
|
|
171
184
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
172
185
|
if (content.includes(HOOK_START_MARKER)) {
|
|
173
|
-
console.log(chalk.
|
|
186
|
+
console.log(chalk.hex(colors.warning)('⚠️ Shell hook 已安装,跳过'));
|
|
174
187
|
setConfigValue('shellHook', true);
|
|
175
188
|
return true;
|
|
176
189
|
}
|
|
@@ -189,9 +202,9 @@ export async function installShellHook() {
|
|
|
189
202
|
fs.appendFileSync(configPath, hookScript);
|
|
190
203
|
// 更新配置
|
|
191
204
|
setConfigValue('shellHook', true);
|
|
192
|
-
console.log(chalk.
|
|
193
|
-
console.log(chalk.
|
|
194
|
-
console.log(chalk.
|
|
205
|
+
console.log(chalk.hex(colors.success)(`✅ Shell hook 已安装到: ${configPath}`));
|
|
206
|
+
console.log(chalk.hex(colors.warning)('⚠️ 请重启终端或执行以下命令使其生效:'));
|
|
207
|
+
console.log(chalk.hex(colors.primary)(` source ${configPath}`));
|
|
195
208
|
return true;
|
|
196
209
|
}
|
|
197
210
|
/**
|
|
@@ -200,8 +213,9 @@ export async function installShellHook() {
|
|
|
200
213
|
export function uninstallShellHook() {
|
|
201
214
|
const shellType = detectShell();
|
|
202
215
|
const configPath = getShellConfigPath(shellType);
|
|
216
|
+
const colors = getColors();
|
|
203
217
|
if (!configPath || !fs.existsSync(configPath)) {
|
|
204
|
-
console.log(chalk.
|
|
218
|
+
console.log(chalk.hex(colors.warning)('⚠️ 未找到 shell 配置文件'));
|
|
205
219
|
setConfigValue('shellHook', false);
|
|
206
220
|
return true;
|
|
207
221
|
}
|
|
@@ -210,7 +224,7 @@ export function uninstallShellHook() {
|
|
|
210
224
|
const startIndex = content.indexOf(HOOK_START_MARKER);
|
|
211
225
|
const endIndex = content.indexOf(HOOK_END_MARKER);
|
|
212
226
|
if (startIndex === -1 || endIndex === -1) {
|
|
213
|
-
console.log(chalk.
|
|
227
|
+
console.log(chalk.hex(colors.warning)('⚠️ 未找到已安装的 hook'));
|
|
214
228
|
setConfigValue('shellHook', false);
|
|
215
229
|
return true;
|
|
216
230
|
}
|
|
@@ -224,8 +238,8 @@ export function uninstallShellHook() {
|
|
|
224
238
|
if (fs.existsSync(SHELL_HISTORY_FILE)) {
|
|
225
239
|
fs.unlinkSync(SHELL_HISTORY_FILE);
|
|
226
240
|
}
|
|
227
|
-
console.log(chalk.
|
|
228
|
-
console.log(chalk.
|
|
241
|
+
console.log(chalk.hex(colors.success)('✅ Shell hook 已卸载'));
|
|
242
|
+
console.log(chalk.hex(colors.warning)('⚠️ 请重启终端使其生效'));
|
|
229
243
|
return true;
|
|
230
244
|
}
|
|
231
245
|
/**
|
|
@@ -242,15 +256,20 @@ export function getShellHistory() {
|
|
|
242
256
|
}
|
|
243
257
|
try {
|
|
244
258
|
const content = fs.readFileSync(SHELL_HISTORY_FILE, 'utf-8');
|
|
245
|
-
const lines = content
|
|
246
|
-
|
|
259
|
+
const lines = content
|
|
260
|
+
.trim()
|
|
261
|
+
.split('\n')
|
|
262
|
+
.filter((line) => line.trim());
|
|
263
|
+
return lines
|
|
264
|
+
.map((line) => {
|
|
247
265
|
try {
|
|
248
266
|
return JSON.parse(line);
|
|
249
267
|
}
|
|
250
268
|
catch {
|
|
251
269
|
return null;
|
|
252
270
|
}
|
|
253
|
-
})
|
|
271
|
+
})
|
|
272
|
+
.filter((item) => item !== null);
|
|
254
273
|
}
|
|
255
274
|
catch {
|
|
256
275
|
return [];
|
|
@@ -258,8 +277,6 @@ export function getShellHistory() {
|
|
|
258
277
|
}
|
|
259
278
|
/**
|
|
260
279
|
* 从 pls history 中查找匹配的记录
|
|
261
|
-
* @param {string} prompt - pls 命令后面的 prompt 部分
|
|
262
|
-
* @returns {object|null} 匹配的 pls history 记录
|
|
263
280
|
*/
|
|
264
281
|
function findPlsHistoryMatch(prompt) {
|
|
265
282
|
const plsHistory = getHistory();
|
|
@@ -312,7 +329,13 @@ export function formatShellHistoryForAI() {
|
|
|
312
329
|
}
|
|
313
330
|
else if (plsRecord.executed) {
|
|
314
331
|
const execStatus = plsRecord.exitCode === 0 ? '✓' : `✗ 退出码:${plsRecord.exitCode}`;
|
|
315
|
-
|
|
332
|
+
// 检查用户是否修改了命令
|
|
333
|
+
if (plsRecord.userModified && plsRecord.aiGeneratedCommand) {
|
|
334
|
+
return `${index + 1}. [pls] "${prompt}" → AI 生成: ${plsRecord.aiGeneratedCommand} / 用户修改为: ${plsRecord.command} ${execStatus}`;
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
return `${index + 1}. [pls] "${prompt}" → 实际执行: ${plsRecord.command} ${execStatus}`;
|
|
338
|
+
}
|
|
316
339
|
}
|
|
317
340
|
else {
|
|
318
341
|
return `${index + 1}. [pls] "${prompt}" → 生成命令: ${plsRecord.command} (用户取消执行)`;
|
|
@@ -343,6 +366,281 @@ export function getHookStatus() {
|
|
|
343
366
|
installed,
|
|
344
367
|
shellType,
|
|
345
368
|
configPath,
|
|
346
|
-
historyFile: SHELL_HISTORY_FILE
|
|
369
|
+
historyFile: SHELL_HISTORY_FILE,
|
|
347
370
|
};
|
|
348
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* 显示 shell 历史
|
|
374
|
+
*/
|
|
375
|
+
export function displayShellHistory() {
|
|
376
|
+
const config = getConfig();
|
|
377
|
+
const history = getShellHistory();
|
|
378
|
+
const colors = getColors();
|
|
379
|
+
if (!config.shellHook) {
|
|
380
|
+
console.log('');
|
|
381
|
+
console.log(chalk.hex(colors.warning)('⚠️ Shell Hook 未启用'));
|
|
382
|
+
console.log(chalk.gray('运行 ') + chalk.hex(colors.primary)('pls hook install') + chalk.gray(' 启用 Shell Hook'));
|
|
383
|
+
console.log('');
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (history.length === 0) {
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log(chalk.gray('暂无 Shell 历史记录'));
|
|
389
|
+
console.log('');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
console.log('');
|
|
393
|
+
console.log(chalk.bold(`终端历史(最近 ${history.length} 条):`));
|
|
394
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
395
|
+
history.forEach((item, index) => {
|
|
396
|
+
const num = index + 1;
|
|
397
|
+
const status = item.exit === 0 ? chalk.hex(colors.success)('✓') : chalk.hex(colors.error)(`✗ (${item.exit})`);
|
|
398
|
+
// 检查是否是 pls 命令
|
|
399
|
+
const isPls = item.cmd.startsWith('pls ') || item.cmd.startsWith('please ');
|
|
400
|
+
if (isPls) {
|
|
401
|
+
// pls 命令:尝试从 history 查找详细信息
|
|
402
|
+
const args = item.cmd.replace(/^(pls|please)\s+/, '');
|
|
403
|
+
const plsRecord = findPlsHistoryMatch(args);
|
|
404
|
+
if (plsRecord && plsRecord.executed) {
|
|
405
|
+
// 检查用户是否修改了命令
|
|
406
|
+
if (plsRecord.userModified && plsRecord.aiGeneratedCommand) {
|
|
407
|
+
console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} "${args}"`);
|
|
408
|
+
console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(plsRecord.aiGeneratedCommand)}`);
|
|
409
|
+
console.log(` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.hex(colors.warning)('(已修改)')}`);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} "${args}" → ${plsRecord.command} ${status}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} ${args} ${status}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${item.cmd} ${status}`);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
424
|
+
console.log(chalk.gray(`配置: 保留最近 ${config.shellHistoryLimit} 条`));
|
|
425
|
+
console.log(chalk.gray(`文件: ${SHELL_HISTORY_FILE}`));
|
|
426
|
+
console.log('');
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* 清空 shell 历史
|
|
430
|
+
*/
|
|
431
|
+
export function clearShellHistory() {
|
|
432
|
+
if (fs.existsSync(SHELL_HISTORY_FILE)) {
|
|
433
|
+
fs.unlinkSync(SHELL_HISTORY_FILE);
|
|
434
|
+
}
|
|
435
|
+
const colors = getColors();
|
|
436
|
+
console.log('');
|
|
437
|
+
console.log(chalk.hex(colors.success)('✓ Shell 历史已清空'));
|
|
438
|
+
console.log('');
|
|
439
|
+
}
|
|
440
|
+
// ================== 远程 Shell Hook ==================
|
|
441
|
+
/**
|
|
442
|
+
* 生成远程 zsh hook 脚本
|
|
443
|
+
*/
|
|
444
|
+
function generateRemoteZshHook() {
|
|
445
|
+
return `
|
|
446
|
+
${HOOK_START_MARKER}
|
|
447
|
+
# 记录命令到 pretty-please 历史
|
|
448
|
+
__pls_preexec() {
|
|
449
|
+
__PLS_LAST_CMD="$1"
|
|
450
|
+
__PLS_CMD_START=$(date +%s)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
__pls_precmd() {
|
|
454
|
+
local exit_code=$?
|
|
455
|
+
if [[ -n "$__PLS_LAST_CMD" ]]; then
|
|
456
|
+
local end_time=$(date +%s)
|
|
457
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
458
|
+
# 确保目录存在
|
|
459
|
+
mkdir -p ~/.please
|
|
460
|
+
# 转义命令中的特殊字符
|
|
461
|
+
local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
462
|
+
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
|
|
463
|
+
# 保持文件不超过 50 行
|
|
464
|
+
tail -n 50 ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
|
|
465
|
+
unset __PLS_LAST_CMD
|
|
466
|
+
fi
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
autoload -Uz add-zsh-hook
|
|
470
|
+
add-zsh-hook preexec __pls_preexec
|
|
471
|
+
add-zsh-hook precmd __pls_precmd
|
|
472
|
+
${HOOK_END_MARKER}
|
|
473
|
+
`;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* 生成远程 bash hook 脚本
|
|
477
|
+
*/
|
|
478
|
+
function generateRemoteBashHook() {
|
|
479
|
+
return `
|
|
480
|
+
${HOOK_START_MARKER}
|
|
481
|
+
# 记录命令到 pretty-please 历史
|
|
482
|
+
__pls_prompt_command() {
|
|
483
|
+
local exit_code=$?
|
|
484
|
+
local last_cmd=$(history 1 | sed 's/^ *[0-9]* *//')
|
|
485
|
+
if [[ -n "$last_cmd" && "$last_cmd" != "$__PLS_LAST_CMD" ]]; then
|
|
486
|
+
__PLS_LAST_CMD="$last_cmd"
|
|
487
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
488
|
+
# 确保目录存在
|
|
489
|
+
mkdir -p ~/.please
|
|
490
|
+
local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
491
|
+
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
|
|
492
|
+
tail -n 50 ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
|
|
493
|
+
fi
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if [[ ! "$PROMPT_COMMAND" =~ __pls_prompt_command ]]; then
|
|
497
|
+
PROMPT_COMMAND="__pls_prompt_command;\${PROMPT_COMMAND}"
|
|
498
|
+
fi
|
|
499
|
+
${HOOK_END_MARKER}
|
|
500
|
+
`;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* 检测远程服务器的 shell 类型
|
|
504
|
+
*/
|
|
505
|
+
export async function detectRemoteShell(sshExecFn) {
|
|
506
|
+
try {
|
|
507
|
+
const result = await sshExecFn('basename "$SHELL"');
|
|
508
|
+
if (result.exitCode === 0) {
|
|
509
|
+
const shell = result.stdout.trim();
|
|
510
|
+
if (shell === 'zsh')
|
|
511
|
+
return 'zsh';
|
|
512
|
+
if (shell === 'bash')
|
|
513
|
+
return 'bash';
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
// 忽略错误
|
|
518
|
+
}
|
|
519
|
+
return 'bash'; // 默认 bash
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* 获取远程 shell 配置文件路径
|
|
523
|
+
*/
|
|
524
|
+
export function getRemoteShellConfigPath(shellType) {
|
|
525
|
+
switch (shellType) {
|
|
526
|
+
case 'zsh':
|
|
527
|
+
return '~/.zshrc';
|
|
528
|
+
case 'bash':
|
|
529
|
+
return '~/.bashrc';
|
|
530
|
+
default:
|
|
531
|
+
return '~/.bashrc';
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* 生成远程 hook 脚本
|
|
536
|
+
*/
|
|
537
|
+
export function generateRemoteHookScript(shellType) {
|
|
538
|
+
switch (shellType) {
|
|
539
|
+
case 'zsh':
|
|
540
|
+
return generateRemoteZshHook();
|
|
541
|
+
case 'bash':
|
|
542
|
+
return generateRemoteBashHook();
|
|
543
|
+
default:
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* 检查远程 hook 是否已安装
|
|
549
|
+
*/
|
|
550
|
+
export async function checkRemoteHookInstalled(sshExecFn, configPath) {
|
|
551
|
+
try {
|
|
552
|
+
const result = await sshExecFn(`grep -q "${HOOK_START_MARKER}" ${configPath} 2>/dev/null && echo "installed" || echo "not_installed"`);
|
|
553
|
+
return result.stdout.trim() === 'installed';
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* 在远程服务器安装 shell hook
|
|
561
|
+
*/
|
|
562
|
+
export async function installRemoteShellHook(sshExecFn, shellType) {
|
|
563
|
+
const colors = getColors();
|
|
564
|
+
const configPath = getRemoteShellConfigPath(shellType);
|
|
565
|
+
const hookScript = generateRemoteHookScript(shellType);
|
|
566
|
+
if (!hookScript) {
|
|
567
|
+
return { success: false, message: chalk.hex(colors.error)(`不支持的 shell 类型: ${shellType}`) };
|
|
568
|
+
}
|
|
569
|
+
// 检查是否已安装
|
|
570
|
+
const installed = await checkRemoteHookInstalled(sshExecFn, configPath);
|
|
571
|
+
if (installed) {
|
|
572
|
+
return { success: true, message: chalk.hex(colors.warning)('Shell hook 已安装,跳过') };
|
|
573
|
+
}
|
|
574
|
+
// 备份原配置文件
|
|
575
|
+
try {
|
|
576
|
+
await sshExecFn(`cp ${configPath} ${configPath}.pls-backup 2>/dev/null || true`);
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// 忽略备份错误
|
|
580
|
+
}
|
|
581
|
+
// 安装 hook
|
|
582
|
+
// 使用 cat 和 heredoc 来追加内容
|
|
583
|
+
const escapedScript = hookScript.replace(/'/g, "'\"'\"'");
|
|
584
|
+
const installCmd = `echo '${escapedScript}' >> ${configPath}`;
|
|
585
|
+
try {
|
|
586
|
+
const result = await sshExecFn(installCmd);
|
|
587
|
+
if (result.exitCode !== 0) {
|
|
588
|
+
return { success: false, message: chalk.hex(colors.error)(`安装失败: ${result.stdout}`) };
|
|
589
|
+
}
|
|
590
|
+
// 确保 ~/.please 目录存在
|
|
591
|
+
await sshExecFn('mkdir -p ~/.please');
|
|
592
|
+
return {
|
|
593
|
+
success: true,
|
|
594
|
+
message: chalk.hex(colors.success)(`Shell hook 已安装到 ${configPath}`),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
599
|
+
return { success: false, message: chalk.hex(colors.error)(`安装失败: ${message}`) };
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* 从远程服务器卸载 shell hook
|
|
604
|
+
*/
|
|
605
|
+
export async function uninstallRemoteShellHook(sshExecFn, shellType) {
|
|
606
|
+
const colors = getColors();
|
|
607
|
+
const configPath = getRemoteShellConfigPath(shellType);
|
|
608
|
+
// 检查是否已安装
|
|
609
|
+
const installed = await checkRemoteHookInstalled(sshExecFn, configPath);
|
|
610
|
+
if (!installed) {
|
|
611
|
+
return { success: true, message: chalk.hex(colors.warning)('Shell hook 未安装,跳过') };
|
|
612
|
+
}
|
|
613
|
+
// 使用 sed 删除 hook 代码块
|
|
614
|
+
// 注意:需要处理特殊字符
|
|
615
|
+
const startMarkerEscaped = HOOK_START_MARKER.replace(/[[\]]/g, '\\$&');
|
|
616
|
+
const endMarkerEscaped = HOOK_END_MARKER.replace(/[[\]]/g, '\\$&');
|
|
617
|
+
// 在 macOS 和 Linux 上 sed -i 行为不同,使用 sed + 临时文件
|
|
618
|
+
const uninstallCmd = `
|
|
619
|
+
sed '/${startMarkerEscaped}/,/${endMarkerEscaped}/d' ${configPath} > ${configPath}.tmp && mv ${configPath}.tmp ${configPath}
|
|
620
|
+
`;
|
|
621
|
+
try {
|
|
622
|
+
const result = await sshExecFn(uninstallCmd);
|
|
623
|
+
if (result.exitCode !== 0) {
|
|
624
|
+
return { success: false, message: chalk.hex(colors.error)(`卸载失败: ${result.stdout}`) };
|
|
625
|
+
}
|
|
626
|
+
return {
|
|
627
|
+
success: true,
|
|
628
|
+
message: chalk.hex(colors.success)('Shell hook 已卸载'),
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
633
|
+
return { success: false, message: chalk.hex(colors.error)(`卸载失败: ${message}`) };
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* 获取远程 hook 状态
|
|
638
|
+
*/
|
|
639
|
+
export async function getRemoteHookStatus(sshExecFn) {
|
|
640
|
+
// 检测 shell 类型
|
|
641
|
+
const shellType = await detectRemoteShell(sshExecFn);
|
|
642
|
+
const configPath = getRemoteShellConfigPath(shellType);
|
|
643
|
+
// 检查是否已安装
|
|
644
|
+
const installed = await checkRemoteHookInstalled(sshExecFn, configPath);
|
|
645
|
+
return { installed, shellType, configPath };
|
|
646
|
+
}
|
package/dist/src/sysinfo.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 系统信息
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export interface SystemInfo {
|
|
5
5
|
os: NodeJS.Platform;
|
|
6
|
-
arch:
|
|
6
|
+
arch: string;
|
|
7
7
|
shell: string;
|
|
8
8
|
packageManager: string;
|
|
9
9
|
cwd: string;
|
|
10
10
|
user: string;
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 收集系统信息
|
|
14
|
+
*/
|
|
15
|
+
export declare function collectSystemInfo(): SystemInfo;
|
|
12
16
|
/**
|
|
13
17
|
* 将系统信息格式化为字符串(供 AI 使用)
|
|
14
18
|
*/
|
|
15
|
-
export function formatSystemInfo(): string;
|
|
19
|
+
export declare function formatSystemInfo(): string;
|
package/dist/src/sysinfo.js
CHANGED
|
@@ -11,7 +11,7 @@ function detectPackageManager() {
|
|
|
11
11
|
{ name: 'yum', command: 'yum' },
|
|
12
12
|
{ name: 'pacman', command: 'pacman' },
|
|
13
13
|
{ name: 'zypper', command: 'zypper' },
|
|
14
|
-
{ name: 'apk', command: 'apk' }
|
|
14
|
+
{ name: 'apk', command: 'apk' },
|
|
15
15
|
];
|
|
16
16
|
for (const mgr of managers) {
|
|
17
17
|
try {
|
|
@@ -40,7 +40,7 @@ export function collectSystemInfo() {
|
|
|
40
40
|
shell: getCurrentShell(),
|
|
41
41
|
packageManager: detectPackageManager(),
|
|
42
42
|
cwd: process.cwd(),
|
|
43
|
-
user: os.userInfo().username
|
|
43
|
+
user: os.userInfo().username,
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
/**
|
package/dist/src/ui/theme.d.ts
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
export type ThemeName = 'dark' | 'light';
|
|
2
|
+
export interface Theme {
|
|
3
|
+
primary: string;
|
|
4
|
+
secondary: string;
|
|
5
|
+
accent: string;
|
|
6
|
+
success: string;
|
|
7
|
+
error: string;
|
|
8
|
+
warning: string;
|
|
9
|
+
info: string;
|
|
10
|
+
text: {
|
|
11
|
+
primary: string;
|
|
12
|
+
secondary: string;
|
|
13
|
+
muted: string;
|
|
14
|
+
dim: string;
|
|
14
15
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
border: string;
|
|
17
|
+
divider: string;
|
|
18
|
+
code: {
|
|
19
|
+
background: string;
|
|
20
|
+
text: string;
|
|
21
|
+
keyword: string;
|
|
22
|
+
string: string;
|
|
23
|
+
function: string;
|
|
24
|
+
comment: string;
|
|
24
25
|
};
|
|
25
|
-
}
|
|
26
|
-
export
|
|
26
|
+
}
|
|
27
|
+
export declare const themes: Record<ThemeName, Theme>;
|
|
28
|
+
export declare function getCurrentTheme(): Theme;
|
|
29
|
+
export declare const theme: Theme;
|