@yivan-lab/pretty-please 1.1.0 → 1.3.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 +390 -1
- package/bin/pls.tsx +1255 -123
- package/dist/bin/pls.js +1098 -103
- package/dist/package.json +4 -4
- package/dist/src/alias.d.ts +41 -0
- package/dist/src/alias.js +240 -0
- package/dist/src/chat-history.js +10 -1
- package/dist/src/components/Chat.js +54 -26
- package/dist/src/components/CodeColorizer.js +26 -20
- package/dist/src/components/CommandBox.js +19 -8
- package/dist/src/components/ConfirmationPrompt.js +2 -1
- 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 +3 -1
- package/dist/src/components/MultiStepCommandGenerator.js +20 -10
- package/dist/src/components/TableRenderer.js +2 -1
- package/dist/src/config.d.ts +33 -3
- package/dist/src/config.js +83 -34
- package/dist/src/mastra-agent.d.ts +1 -0
- package/dist/src/mastra-agent.js +3 -11
- package/dist/src/mastra-chat.d.ts +13 -6
- package/dist/src/mastra-chat.js +31 -31
- package/dist/src/multi-step.d.ts +23 -7
- package/dist/src/multi-step.js +45 -26
- package/dist/src/prompts.d.ts +30 -4
- package/dist/src/prompts.js +218 -70
- 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 +58 -0
- package/dist/src/shell-hook.js +295 -26
- package/dist/src/ui/theme.d.ts +60 -23
- package/dist/src/ui/theme.js +544 -22
- package/dist/src/upgrade.d.ts +41 -0
- package/dist/src/upgrade.js +348 -0
- package/dist/src/utils/console.d.ts +4 -0
- package/dist/src/utils/console.js +89 -17
- package/package.json +4 -4
- package/src/alias.ts +301 -0
- package/src/chat-history.ts +11 -1
- package/src/components/Chat.tsx +71 -26
- package/src/components/CodeColorizer.tsx +27 -19
- package/src/components/CommandBox.tsx +26 -8
- package/src/components/ConfirmationPrompt.tsx +2 -1
- 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 +25 -11
- package/src/components/TableRenderer.tsx +2 -1
- package/src/config.ts +126 -35
- package/src/mastra-agent.ts +3 -12
- package/src/mastra-chat.ts +40 -34
- package/src/multi-step.ts +62 -30
- package/src/prompts.ts +236 -78
- package/src/remote-history.ts +390 -0
- package/src/remote.ts +800 -0
- package/src/shell-hook.ts +339 -26
- package/src/ui/theme.ts +632 -23
- package/src/upgrade.ts +397 -0
- package/src/utils/console.ts +99 -17
package/dist/src/shell-hook.js
CHANGED
|
@@ -4,8 +4,19 @@ 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
|
-
const MAX_SHELL_HISTORY = 20;
|
|
9
20
|
// Hook 标记,用于识别我们添加的内容
|
|
10
21
|
const HOOK_START_MARKER = '# >>> pretty-please shell hook >>>';
|
|
11
22
|
const HOOK_END_MARKER = '# <<< pretty-please shell hook <<<';
|
|
@@ -48,6 +59,8 @@ export function getShellConfigPath(shellType) {
|
|
|
48
59
|
* 生成 zsh hook 脚本
|
|
49
60
|
*/
|
|
50
61
|
function generateZshHook() {
|
|
62
|
+
const config = getConfig();
|
|
63
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
51
64
|
return `
|
|
52
65
|
${HOOK_START_MARKER}
|
|
53
66
|
# 记录命令到 pretty-please 历史
|
|
@@ -64,8 +77,8 @@ __pls_precmd() {
|
|
|
64
77
|
# 转义命令中的特殊字符
|
|
65
78
|
local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
66
79
|
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
|
|
67
|
-
# 保持文件不超过 ${
|
|
68
|
-
tail -n ${
|
|
80
|
+
# 保持文件不超过 ${limit} 行(从配置读取)
|
|
81
|
+
tail -n ${limit} "${CONFIG_DIR}/shell_history.jsonl" > "${CONFIG_DIR}/shell_history.jsonl.tmp" && mv "${CONFIG_DIR}/shell_history.jsonl.tmp" "${CONFIG_DIR}/shell_history.jsonl"
|
|
69
82
|
unset __PLS_LAST_CMD
|
|
70
83
|
fi
|
|
71
84
|
}
|
|
@@ -80,6 +93,8 @@ ${HOOK_END_MARKER}
|
|
|
80
93
|
* 生成 bash hook 脚本
|
|
81
94
|
*/
|
|
82
95
|
function generateBashHook() {
|
|
96
|
+
const config = getConfig();
|
|
97
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
83
98
|
return `
|
|
84
99
|
${HOOK_START_MARKER}
|
|
85
100
|
# 记录命令到 pretty-please 历史
|
|
@@ -91,7 +106,7 @@ __pls_prompt_command() {
|
|
|
91
106
|
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
92
107
|
local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
93
108
|
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
|
|
94
|
-
tail -n ${
|
|
109
|
+
tail -n ${limit} "${CONFIG_DIR}/shell_history.jsonl" > "${CONFIG_DIR}/shell_history.jsonl.tmp" && mv "${CONFIG_DIR}/shell_history.jsonl.tmp" "${CONFIG_DIR}/shell_history.jsonl"
|
|
95
110
|
fi
|
|
96
111
|
}
|
|
97
112
|
|
|
@@ -105,6 +120,8 @@ ${HOOK_END_MARKER}
|
|
|
105
120
|
* 生成 PowerShell hook 脚本
|
|
106
121
|
*/
|
|
107
122
|
function generatePowerShellHook() {
|
|
123
|
+
const config = getConfig();
|
|
124
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
108
125
|
return `
|
|
109
126
|
${HOOK_START_MARKER}
|
|
110
127
|
# 记录命令到 pretty-please 历史
|
|
@@ -120,8 +137,8 @@ function __Pls_RecordCommand {
|
|
|
120
137
|
$escapedCmd = $lastCmd -replace '\\\\', '\\\\\\\\' -replace '"', '\\\\"'
|
|
121
138
|
$json = "{\`"cmd\`":\`"$escapedCmd\`",\`"exit\`":$exitCode,\`"time\`":\`"$timestamp\`"}"
|
|
122
139
|
Add-Content -Path "${CONFIG_DIR}/shell_history.jsonl" -Value $json
|
|
123
|
-
# 保持文件不超过 ${
|
|
124
|
-
$content = Get-Content "${CONFIG_DIR}/shell_history.jsonl" -Tail ${
|
|
140
|
+
# 保持文件不超过 ${limit} 行(从配置读取)
|
|
141
|
+
$content = Get-Content "${CONFIG_DIR}/shell_history.jsonl" -Tail ${limit}
|
|
125
142
|
$content | Set-Content "${CONFIG_DIR}/shell_history.jsonl"
|
|
126
143
|
}
|
|
127
144
|
}
|
|
@@ -157,20 +174,21 @@ function generateHookScript(shellType) {
|
|
|
157
174
|
export async function installShellHook() {
|
|
158
175
|
const shellType = detectShell();
|
|
159
176
|
const configPath = getShellConfigPath(shellType);
|
|
177
|
+
const colors = getColors();
|
|
160
178
|
if (!configPath) {
|
|
161
|
-
console.log(chalk.
|
|
179
|
+
console.log(chalk.hex(colors.error)(`❌ 不支持的 shell 类型: ${shellType}`));
|
|
162
180
|
return false;
|
|
163
181
|
}
|
|
164
182
|
const hookScript = generateHookScript(shellType);
|
|
165
183
|
if (!hookScript) {
|
|
166
|
-
console.log(chalk.
|
|
184
|
+
console.log(chalk.hex(colors.error)(`❌ 无法为 ${shellType} 生成 hook 脚本`));
|
|
167
185
|
return false;
|
|
168
186
|
}
|
|
169
187
|
// 检查是否已安装
|
|
170
188
|
if (fs.existsSync(configPath)) {
|
|
171
189
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
172
190
|
if (content.includes(HOOK_START_MARKER)) {
|
|
173
|
-
console.log(chalk.
|
|
191
|
+
console.log(chalk.hex(colors.warning)('⚠️ Shell hook 已安装,跳过'));
|
|
174
192
|
setConfigValue('shellHook', true);
|
|
175
193
|
return true;
|
|
176
194
|
}
|
|
@@ -189,9 +207,9 @@ export async function installShellHook() {
|
|
|
189
207
|
fs.appendFileSync(configPath, hookScript);
|
|
190
208
|
// 更新配置
|
|
191
209
|
setConfigValue('shellHook', true);
|
|
192
|
-
console.log(chalk.
|
|
193
|
-
console.log(chalk.
|
|
194
|
-
console.log(chalk.
|
|
210
|
+
console.log(chalk.hex(colors.success)(`✅ Shell hook 已安装到: ${configPath}`));
|
|
211
|
+
console.log(chalk.hex(colors.warning)('⚠️ 请重启终端或执行以下命令使其生效:'));
|
|
212
|
+
console.log(chalk.hex(colors.primary)(` source ${configPath}`));
|
|
195
213
|
return true;
|
|
196
214
|
}
|
|
197
215
|
/**
|
|
@@ -200,8 +218,9 @@ export async function installShellHook() {
|
|
|
200
218
|
export function uninstallShellHook() {
|
|
201
219
|
const shellType = detectShell();
|
|
202
220
|
const configPath = getShellConfigPath(shellType);
|
|
221
|
+
const colors = getColors();
|
|
203
222
|
if (!configPath || !fs.existsSync(configPath)) {
|
|
204
|
-
console.log(chalk.
|
|
223
|
+
console.log(chalk.hex(colors.warning)('⚠️ 未找到 shell 配置文件'));
|
|
205
224
|
setConfigValue('shellHook', false);
|
|
206
225
|
return true;
|
|
207
226
|
}
|
|
@@ -210,7 +229,7 @@ export function uninstallShellHook() {
|
|
|
210
229
|
const startIndex = content.indexOf(HOOK_START_MARKER);
|
|
211
230
|
const endIndex = content.indexOf(HOOK_END_MARKER);
|
|
212
231
|
if (startIndex === -1 || endIndex === -1) {
|
|
213
|
-
console.log(chalk.
|
|
232
|
+
console.log(chalk.hex(colors.warning)('⚠️ 未找到已安装的 hook'));
|
|
214
233
|
setConfigValue('shellHook', false);
|
|
215
234
|
return true;
|
|
216
235
|
}
|
|
@@ -224,8 +243,8 @@ export function uninstallShellHook() {
|
|
|
224
243
|
if (fs.existsSync(SHELL_HISTORY_FILE)) {
|
|
225
244
|
fs.unlinkSync(SHELL_HISTORY_FILE);
|
|
226
245
|
}
|
|
227
|
-
console.log(chalk.
|
|
228
|
-
console.log(chalk.
|
|
246
|
+
console.log(chalk.hex(colors.success)('✅ Shell hook 已卸载'));
|
|
247
|
+
console.log(chalk.hex(colors.warning)('⚠️ 请重启终端使其生效'));
|
|
229
248
|
return true;
|
|
230
249
|
}
|
|
231
250
|
/**
|
|
@@ -246,7 +265,7 @@ export function getShellHistory() {
|
|
|
246
265
|
.trim()
|
|
247
266
|
.split('\n')
|
|
248
267
|
.filter((line) => line.trim());
|
|
249
|
-
|
|
268
|
+
const allHistory = lines
|
|
250
269
|
.map((line) => {
|
|
251
270
|
try {
|
|
252
271
|
return JSON.parse(line);
|
|
@@ -256,6 +275,9 @@ export function getShellHistory() {
|
|
|
256
275
|
}
|
|
257
276
|
})
|
|
258
277
|
.filter((item) => item !== null);
|
|
278
|
+
// 应用 shellHistoryLimit 限制:只返回最近的 N 条
|
|
279
|
+
const limit = config.shellHistoryLimit || 15;
|
|
280
|
+
return allHistory.slice(-limit);
|
|
259
281
|
}
|
|
260
282
|
catch {
|
|
261
283
|
return [];
|
|
@@ -361,10 +383,11 @@ export function getHookStatus() {
|
|
|
361
383
|
export function displayShellHistory() {
|
|
362
384
|
const config = getConfig();
|
|
363
385
|
const history = getShellHistory();
|
|
386
|
+
const colors = getColors();
|
|
364
387
|
if (!config.shellHook) {
|
|
365
388
|
console.log('');
|
|
366
|
-
console.log(chalk.
|
|
367
|
-
console.log(chalk.gray('运行 ') + chalk.
|
|
389
|
+
console.log(chalk.hex(colors.warning)('⚠️ Shell Hook 未启用'));
|
|
390
|
+
console.log(chalk.gray('运行 ') + chalk.hex(colors.primary)('pls hook install') + chalk.gray(' 启用 Shell Hook'));
|
|
368
391
|
console.log('');
|
|
369
392
|
return;
|
|
370
393
|
}
|
|
@@ -379,7 +402,7 @@ export function displayShellHistory() {
|
|
|
379
402
|
console.log(chalk.gray('━'.repeat(50)));
|
|
380
403
|
history.forEach((item, index) => {
|
|
381
404
|
const num = index + 1;
|
|
382
|
-
const status = item.exit === 0 ? chalk.
|
|
405
|
+
const status = item.exit === 0 ? chalk.hex(colors.success)('✓') : chalk.hex(colors.error)(`✗ (${item.exit})`);
|
|
383
406
|
// 检查是否是 pls 命令
|
|
384
407
|
const isPls = item.cmd.startsWith('pls ') || item.cmd.startsWith('please ');
|
|
385
408
|
if (isPls) {
|
|
@@ -389,20 +412,20 @@ export function displayShellHistory() {
|
|
|
389
412
|
if (plsRecord && plsRecord.executed) {
|
|
390
413
|
// 检查用户是否修改了命令
|
|
391
414
|
if (plsRecord.userModified && plsRecord.aiGeneratedCommand) {
|
|
392
|
-
console.log(` ${chalk.
|
|
415
|
+
console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} "${args}"`);
|
|
393
416
|
console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(plsRecord.aiGeneratedCommand)}`);
|
|
394
|
-
console.log(` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.
|
|
417
|
+
console.log(` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.hex(colors.warning)('(已修改)')}`);
|
|
395
418
|
}
|
|
396
419
|
else {
|
|
397
|
-
console.log(` ${chalk.
|
|
420
|
+
console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} "${args}" → ${plsRecord.command} ${status}`);
|
|
398
421
|
}
|
|
399
422
|
}
|
|
400
423
|
else {
|
|
401
|
-
console.log(` ${chalk.
|
|
424
|
+
console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} ${args} ${status}`);
|
|
402
425
|
}
|
|
403
426
|
}
|
|
404
427
|
else {
|
|
405
|
-
console.log(` ${chalk.
|
|
428
|
+
console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${item.cmd} ${status}`);
|
|
406
429
|
}
|
|
407
430
|
});
|
|
408
431
|
console.log(chalk.gray('━'.repeat(50)));
|
|
@@ -410,6 +433,40 @@ export function displayShellHistory() {
|
|
|
410
433
|
console.log(chalk.gray(`文件: ${SHELL_HISTORY_FILE}`));
|
|
411
434
|
console.log('');
|
|
412
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* 当 shellHistoryLimit 变化时,自动重装 Hook
|
|
438
|
+
* 返回是否成功重装
|
|
439
|
+
*/
|
|
440
|
+
export async function reinstallHookForLimitChange(oldLimit, newLimit) {
|
|
441
|
+
const config = getConfig();
|
|
442
|
+
// 只有在 hook 已启用时才重装
|
|
443
|
+
if (!config.shellHook) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
// 值没有变化,不需要重装
|
|
447
|
+
if (oldLimit === newLimit) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
const colors = getColors();
|
|
451
|
+
console.log('');
|
|
452
|
+
console.log(chalk.hex(colors.primary)(`检测到 shellHistoryLimit 变化 (${oldLimit} → ${newLimit})`));
|
|
453
|
+
console.log(chalk.hex(colors.primary)('正在更新 Shell Hook...'));
|
|
454
|
+
uninstallShellHook();
|
|
455
|
+
await installShellHook();
|
|
456
|
+
console.log('');
|
|
457
|
+
console.log(chalk.hex(colors.warning)('⚠️ 请重启终端或运行以下命令使新配置生效:'));
|
|
458
|
+
const shellType = detectShell();
|
|
459
|
+
let configFile = '~/.zshrc';
|
|
460
|
+
if (shellType === 'bash') {
|
|
461
|
+
configFile = process.platform === 'darwin' ? '~/.bash_profile' : '~/.bashrc';
|
|
462
|
+
}
|
|
463
|
+
else if (shellType === 'powershell') {
|
|
464
|
+
configFile = '~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1';
|
|
465
|
+
}
|
|
466
|
+
console.log(chalk.gray(` source ${configFile}`));
|
|
467
|
+
console.log('');
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
413
470
|
/**
|
|
414
471
|
* 清空 shell 历史
|
|
415
472
|
*/
|
|
@@ -417,7 +474,219 @@ export function clearShellHistory() {
|
|
|
417
474
|
if (fs.existsSync(SHELL_HISTORY_FILE)) {
|
|
418
475
|
fs.unlinkSync(SHELL_HISTORY_FILE);
|
|
419
476
|
}
|
|
477
|
+
const colors = getColors();
|
|
420
478
|
console.log('');
|
|
421
|
-
console.log(chalk.
|
|
479
|
+
console.log(chalk.hex(colors.success)('✓ Shell 历史已清空'));
|
|
422
480
|
console.log('');
|
|
423
481
|
}
|
|
482
|
+
// ================== 远程 Shell Hook ==================
|
|
483
|
+
/**
|
|
484
|
+
* 生成远程 zsh hook 脚本
|
|
485
|
+
*/
|
|
486
|
+
function generateRemoteZshHook() {
|
|
487
|
+
const config = getConfig();
|
|
488
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
489
|
+
return `
|
|
490
|
+
${HOOK_START_MARKER}
|
|
491
|
+
# 记录命令到 pretty-please 历史
|
|
492
|
+
__pls_preexec() {
|
|
493
|
+
__PLS_LAST_CMD="$1"
|
|
494
|
+
__PLS_CMD_START=$(date +%s)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
__pls_precmd() {
|
|
498
|
+
local exit_code=$?
|
|
499
|
+
if [[ -n "$__PLS_LAST_CMD" ]]; then
|
|
500
|
+
local end_time=$(date +%s)
|
|
501
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
502
|
+
# 确保目录存在
|
|
503
|
+
mkdir -p ~/.please
|
|
504
|
+
# 转义命令中的特殊字符
|
|
505
|
+
local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
506
|
+
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
|
|
507
|
+
# 保持文件不超过 ${limit} 行(从配置读取)
|
|
508
|
+
tail -n ${limit} ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
|
|
509
|
+
unset __PLS_LAST_CMD
|
|
510
|
+
fi
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
autoload -Uz add-zsh-hook
|
|
514
|
+
add-zsh-hook preexec __pls_preexec
|
|
515
|
+
add-zsh-hook precmd __pls_precmd
|
|
516
|
+
${HOOK_END_MARKER}
|
|
517
|
+
`;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* 生成远程 bash hook 脚本
|
|
521
|
+
*/
|
|
522
|
+
function generateRemoteBashHook() {
|
|
523
|
+
const config = getConfig();
|
|
524
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
525
|
+
return `
|
|
526
|
+
${HOOK_START_MARKER}
|
|
527
|
+
# 记录命令到 pretty-please 历史
|
|
528
|
+
__pls_prompt_command() {
|
|
529
|
+
local exit_code=$?
|
|
530
|
+
local last_cmd=$(history 1 | sed 's/^ *[0-9]* *//')
|
|
531
|
+
if [[ -n "$last_cmd" && "$last_cmd" != "$__PLS_LAST_CMD" ]]; then
|
|
532
|
+
__PLS_LAST_CMD="$last_cmd"
|
|
533
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
534
|
+
# 确保目录存在
|
|
535
|
+
mkdir -p ~/.please
|
|
536
|
+
local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
537
|
+
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
|
|
538
|
+
tail -n ${limit} ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
|
|
539
|
+
fi
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if [[ ! "$PROMPT_COMMAND" =~ __pls_prompt_command ]]; then
|
|
543
|
+
PROMPT_COMMAND="__pls_prompt_command;\${PROMPT_COMMAND}"
|
|
544
|
+
fi
|
|
545
|
+
${HOOK_END_MARKER}
|
|
546
|
+
`;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* 检测远程服务器的 shell 类型
|
|
550
|
+
*/
|
|
551
|
+
export async function detectRemoteShell(sshExecFn) {
|
|
552
|
+
try {
|
|
553
|
+
const result = await sshExecFn('basename "$SHELL"');
|
|
554
|
+
if (result.exitCode === 0) {
|
|
555
|
+
const shell = result.stdout.trim();
|
|
556
|
+
if (shell === 'zsh')
|
|
557
|
+
return 'zsh';
|
|
558
|
+
if (shell === 'bash')
|
|
559
|
+
return 'bash';
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
// 忽略错误
|
|
564
|
+
}
|
|
565
|
+
return 'bash'; // 默认 bash
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* 获取远程 shell 配置文件路径
|
|
569
|
+
*/
|
|
570
|
+
export function getRemoteShellConfigPath(shellType) {
|
|
571
|
+
switch (shellType) {
|
|
572
|
+
case 'zsh':
|
|
573
|
+
return '~/.zshrc';
|
|
574
|
+
case 'bash':
|
|
575
|
+
return '~/.bashrc';
|
|
576
|
+
default:
|
|
577
|
+
return '~/.bashrc';
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* 生成远程 hook 脚本
|
|
582
|
+
*/
|
|
583
|
+
export function generateRemoteHookScript(shellType) {
|
|
584
|
+
switch (shellType) {
|
|
585
|
+
case 'zsh':
|
|
586
|
+
return generateRemoteZshHook();
|
|
587
|
+
case 'bash':
|
|
588
|
+
return generateRemoteBashHook();
|
|
589
|
+
default:
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* 检查远程 hook 是否已安装
|
|
595
|
+
*/
|
|
596
|
+
export async function checkRemoteHookInstalled(sshExecFn, configPath) {
|
|
597
|
+
try {
|
|
598
|
+
const result = await sshExecFn(`grep -q "${HOOK_START_MARKER}" ${configPath} 2>/dev/null && echo "installed" || echo "not_installed"`);
|
|
599
|
+
return result.stdout.trim() === 'installed';
|
|
600
|
+
}
|
|
601
|
+
catch {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* 在远程服务器安装 shell hook
|
|
607
|
+
*/
|
|
608
|
+
export async function installRemoteShellHook(sshExecFn, shellType) {
|
|
609
|
+
const colors = getColors();
|
|
610
|
+
const configPath = getRemoteShellConfigPath(shellType);
|
|
611
|
+
const hookScript = generateRemoteHookScript(shellType);
|
|
612
|
+
if (!hookScript) {
|
|
613
|
+
return { success: false, message: chalk.hex(colors.error)(`不支持的 shell 类型: ${shellType}`) };
|
|
614
|
+
}
|
|
615
|
+
// 检查是否已安装
|
|
616
|
+
const installed = await checkRemoteHookInstalled(sshExecFn, configPath);
|
|
617
|
+
if (installed) {
|
|
618
|
+
return { success: true, message: chalk.hex(colors.warning)('Shell hook 已安装,跳过') };
|
|
619
|
+
}
|
|
620
|
+
// 备份原配置文件
|
|
621
|
+
try {
|
|
622
|
+
await sshExecFn(`cp ${configPath} ${configPath}.pls-backup 2>/dev/null || true`);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
// 忽略备份错误
|
|
626
|
+
}
|
|
627
|
+
// 安装 hook
|
|
628
|
+
// 使用 cat 和 heredoc 来追加内容
|
|
629
|
+
const escapedScript = hookScript.replace(/'/g, "'\"'\"'");
|
|
630
|
+
const installCmd = `echo '${escapedScript}' >> ${configPath}`;
|
|
631
|
+
try {
|
|
632
|
+
const result = await sshExecFn(installCmd);
|
|
633
|
+
if (result.exitCode !== 0) {
|
|
634
|
+
return { success: false, message: chalk.hex(colors.error)(`安装失败: ${result.stdout}`) };
|
|
635
|
+
}
|
|
636
|
+
// 确保 ~/.please 目录存在
|
|
637
|
+
await sshExecFn('mkdir -p ~/.please');
|
|
638
|
+
return {
|
|
639
|
+
success: true,
|
|
640
|
+
message: chalk.hex(colors.success)(`Shell hook 已安装到 ${configPath}`),
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
645
|
+
return { success: false, message: chalk.hex(colors.error)(`安装失败: ${message}`) };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* 从远程服务器卸载 shell hook
|
|
650
|
+
*/
|
|
651
|
+
export async function uninstallRemoteShellHook(sshExecFn, shellType) {
|
|
652
|
+
const colors = getColors();
|
|
653
|
+
const configPath = getRemoteShellConfigPath(shellType);
|
|
654
|
+
// 检查是否已安装
|
|
655
|
+
const installed = await checkRemoteHookInstalled(sshExecFn, configPath);
|
|
656
|
+
if (!installed) {
|
|
657
|
+
return { success: true, message: chalk.hex(colors.warning)('Shell hook 未安装,跳过') };
|
|
658
|
+
}
|
|
659
|
+
// 使用 sed 删除 hook 代码块
|
|
660
|
+
// 注意:需要处理特殊字符
|
|
661
|
+
const startMarkerEscaped = HOOK_START_MARKER.replace(/[[\]]/g, '\\$&');
|
|
662
|
+
const endMarkerEscaped = HOOK_END_MARKER.replace(/[[\]]/g, '\\$&');
|
|
663
|
+
// 在 macOS 和 Linux 上 sed -i 行为不同,使用 sed + 临时文件
|
|
664
|
+
const uninstallCmd = `
|
|
665
|
+
sed '/${startMarkerEscaped}/,/${endMarkerEscaped}/d' ${configPath} > ${configPath}.tmp && mv ${configPath}.tmp ${configPath}
|
|
666
|
+
`;
|
|
667
|
+
try {
|
|
668
|
+
const result = await sshExecFn(uninstallCmd);
|
|
669
|
+
if (result.exitCode !== 0) {
|
|
670
|
+
return { success: false, message: chalk.hex(colors.error)(`卸载失败: ${result.stdout}`) };
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
success: true,
|
|
674
|
+
message: chalk.hex(colors.success)('Shell hook 已卸载'),
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
679
|
+
return { success: false, message: chalk.hex(colors.error)(`卸载失败: ${message}`) };
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* 获取远程 hook 状态
|
|
684
|
+
*/
|
|
685
|
+
export async function getRemoteHookStatus(sshExecFn) {
|
|
686
|
+
// 检测 shell 类型
|
|
687
|
+
const shellType = await detectRemoteShell(sshExecFn);
|
|
688
|
+
const configPath = getRemoteShellConfigPath(shellType);
|
|
689
|
+
// 检查是否已安装
|
|
690
|
+
const installed = await checkRemoteHookInstalled(sshExecFn, configPath);
|
|
691
|
+
return { installed, shellType, configPath };
|
|
692
|
+
}
|
package/dist/src/ui/theme.d.ts
CHANGED
|
@@ -1,26 +1,63 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
export type ThemeName = string;
|
|
2
|
+
export type BuiltinThemeName = 'dark' | 'light' | 'nord' | 'dracula' | 'retro' | 'contrast' | 'monokai';
|
|
3
|
+
export interface Theme {
|
|
4
|
+
primary: string;
|
|
5
|
+
secondary: string;
|
|
6
|
+
accent: string;
|
|
7
|
+
success: string;
|
|
8
|
+
error: string;
|
|
9
|
+
warning: string;
|
|
10
|
+
info: string;
|
|
11
|
+
text: {
|
|
12
|
+
primary: string;
|
|
13
|
+
secondary: string;
|
|
14
|
+
muted: string;
|
|
15
|
+
dim: string;
|
|
14
16
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
border: string;
|
|
18
|
+
divider: string;
|
|
19
|
+
code: {
|
|
20
|
+
background: string;
|
|
21
|
+
text: string;
|
|
22
|
+
keyword: string;
|
|
23
|
+
string: string;
|
|
24
|
+
function: string;
|
|
25
|
+
comment: string;
|
|
24
26
|
};
|
|
27
|
+
}
|
|
28
|
+
export interface ThemeMetadata {
|
|
29
|
+
name: ThemeName;
|
|
30
|
+
displayName: string;
|
|
31
|
+
description: string;
|
|
32
|
+
category: 'dark' | 'light';
|
|
33
|
+
previewColor: string;
|
|
34
|
+
author?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ThemeDefinition {
|
|
37
|
+
metadata: ThemeMetadata;
|
|
38
|
+
colors: Theme;
|
|
39
|
+
}
|
|
40
|
+
export declare const themeDefinitions: Record<ThemeName, ThemeDefinition>;
|
|
41
|
+
export declare const themes: Record<ThemeName, Theme>;
|
|
42
|
+
export declare function getCurrentTheme(): Theme;
|
|
43
|
+
export declare function getThemeMetadata(name: ThemeName): ThemeMetadata | undefined;
|
|
44
|
+
export declare function getAllThemeMetadata(): ThemeMetadata[];
|
|
45
|
+
export declare function getThemeDefinition(name: ThemeName): ThemeDefinition | undefined;
|
|
46
|
+
export declare function isValidTheme(name: string): name is ThemeName;
|
|
47
|
+
export declare function isBuiltinTheme(name: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* 验证主题格式是否正确
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateTheme(theme: any): theme is ThemeDefinition;
|
|
52
|
+
/**
|
|
53
|
+
* 获取主题验证的详细错误信息
|
|
54
|
+
*/
|
|
55
|
+
export declare function validateThemeWithDetails(theme: any): {
|
|
56
|
+
valid: boolean;
|
|
57
|
+
errors: string[];
|
|
25
58
|
};
|
|
26
|
-
|
|
59
|
+
/**
|
|
60
|
+
* 创建主题模板
|
|
61
|
+
*/
|
|
62
|
+
export declare function createThemeTemplate(name: string, displayName: string, category: 'dark' | 'light'): ThemeDefinition;
|
|
63
|
+
export declare const theme: Theme;
|