@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.
Files changed (89) hide show
  1. package/README.md +381 -28
  2. package/bin/pls.tsx +1138 -109
  3. package/dist/bin/pls.d.ts +1 -1
  4. package/dist/bin/pls.js +994 -91
  5. package/dist/package.json +80 -0
  6. package/dist/src/ai.d.ts +1 -41
  7. package/dist/src/ai.js +9 -190
  8. package/dist/src/alias.d.ts +41 -0
  9. package/dist/src/alias.js +240 -0
  10. package/dist/src/builtin-detector.d.ts +14 -8
  11. package/dist/src/builtin-detector.js +36 -16
  12. package/dist/src/chat-history.d.ts +16 -11
  13. package/dist/src/chat-history.js +35 -4
  14. package/dist/src/components/Chat.js +5 -4
  15. package/dist/src/components/CodeColorizer.js +26 -20
  16. package/dist/src/components/CommandBox.js +3 -17
  17. package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
  18. package/dist/src/components/ConfirmationPrompt.js +9 -4
  19. package/dist/src/components/Duration.js +2 -1
  20. package/dist/src/components/InlineRenderer.js +2 -1
  21. package/dist/src/components/MarkdownDisplay.js +2 -1
  22. package/dist/src/components/MultiStepCommandGenerator.d.ts +5 -1
  23. package/dist/src/components/MultiStepCommandGenerator.js +127 -14
  24. package/dist/src/components/TableRenderer.js +2 -1
  25. package/dist/src/config.d.ts +59 -9
  26. package/dist/src/config.js +147 -48
  27. package/dist/src/history.d.ts +19 -5
  28. package/dist/src/history.js +26 -11
  29. package/dist/src/mastra-agent.d.ts +0 -1
  30. package/dist/src/mastra-agent.js +3 -4
  31. package/dist/src/mastra-chat.d.ts +28 -0
  32. package/dist/src/mastra-chat.js +93 -0
  33. package/dist/src/multi-step.d.ts +23 -7
  34. package/dist/src/multi-step.js +29 -6
  35. package/dist/src/prompts.d.ts +11 -0
  36. package/dist/src/prompts.js +140 -0
  37. package/dist/src/remote-history.d.ts +63 -0
  38. package/dist/src/remote-history.js +315 -0
  39. package/dist/src/remote.d.ts +113 -0
  40. package/dist/src/remote.js +634 -0
  41. package/dist/src/shell-hook.d.ts +87 -12
  42. package/dist/src/shell-hook.js +315 -17
  43. package/dist/src/sysinfo.d.ts +9 -5
  44. package/dist/src/sysinfo.js +2 -2
  45. package/dist/src/ui/theme.d.ts +27 -24
  46. package/dist/src/ui/theme.js +71 -21
  47. package/dist/src/upgrade.d.ts +41 -0
  48. package/dist/src/upgrade.js +348 -0
  49. package/dist/src/utils/console.d.ts +11 -11
  50. package/dist/src/utils/console.js +26 -17
  51. package/package.json +11 -9
  52. package/src/alias.ts +301 -0
  53. package/src/builtin-detector.ts +126 -0
  54. package/src/chat-history.ts +140 -0
  55. package/src/components/Chat.tsx +6 -5
  56. package/src/components/CodeColorizer.tsx +27 -19
  57. package/src/components/CommandBox.tsx +3 -17
  58. package/src/components/ConfirmationPrompt.tsx +11 -3
  59. package/src/components/Duration.tsx +2 -1
  60. package/src/components/InlineRenderer.tsx +2 -1
  61. package/src/components/MarkdownDisplay.tsx +2 -1
  62. package/src/components/MultiStepCommandGenerator.tsx +167 -16
  63. package/src/components/TableRenderer.tsx +2 -1
  64. package/src/config.ts +394 -0
  65. package/src/history.ts +160 -0
  66. package/src/mastra-agent.ts +3 -4
  67. package/src/mastra-chat.ts +124 -0
  68. package/src/multi-step.ts +45 -8
  69. package/src/prompts.ts +154 -0
  70. package/src/remote-history.ts +390 -0
  71. package/src/remote.ts +800 -0
  72. package/src/shell-hook.ts +754 -0
  73. package/src/{sysinfo.js → sysinfo.ts} +28 -16
  74. package/src/ui/theme.ts +101 -24
  75. package/src/upgrade.ts +397 -0
  76. package/src/utils/{console.js → console.ts} +36 -27
  77. package/bin/pls.js +0 -681
  78. package/src/ai.js +0 -324
  79. package/src/builtin-detector.js +0 -98
  80. package/src/chat-history.js +0 -94
  81. package/src/components/ChatStatus.tsx +0 -53
  82. package/src/components/CommandGenerator.tsx +0 -184
  83. package/src/components/ConfigDisplay.tsx +0 -64
  84. package/src/components/ConfigWizard.tsx +0 -101
  85. package/src/components/HistoryDisplay.tsx +0 -69
  86. package/src/components/HookManager.tsx +0 -150
  87. package/src/config.js +0 -221
  88. package/src/history.js +0 -131
  89. package/src/shell-hook.js +0 -393
@@ -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(): "unknown" | "zsh" | "bash" | "powershell";
23
+ export declare function detectShell(): ShellType;
5
24
  /**
6
25
  * 获取 shell 配置文件路径
7
26
  */
8
- export function getShellConfigPath(shellType: any): string | null;
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(): any[];
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
- enabled: any;
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: string;
33
- configPath: string | null;
34
- historyFile: string;
35
- };
107
+ shellType: ShellType;
108
+ configPath: string;
109
+ }>;
110
+ export {};
@@ -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.red(`❌ 不支持的 shell 类型: ${shellType}`));
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.red(`❌ 无法为 ${shellType} 生成 hook 脚本`));
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.yellow('⚠️ Shell hook 已安装,跳过'));
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.green(`✅ Shell hook 已安装到: ${configPath}`));
193
- console.log(chalk.yellow('⚠️ 请重启终端或执行以下命令使其生效:'));
194
- console.log(chalk.cyan(` source ${configPath}`));
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.yellow('⚠️ 未找到 shell 配置文件'));
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.yellow('⚠️ 未找到已安装的 hook'));
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.green('✅ Shell hook 已卸载'));
228
- console.log(chalk.yellow('⚠️ 请重启终端使其生效'));
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.trim().split('\n').filter(line => line.trim());
246
- return lines.map(line => {
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
- }).filter(Boolean);
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
- return `${index + 1}. [pls] "${prompt}" → 实际执行: ${plsRecord.command} ${execStatus}`;
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
+ }
@@ -1,15 +1,19 @@
1
1
  /**
2
- * 收集系统信息
2
+ * 系统信息
3
3
  */
4
- export function collectSystemInfo(): {
4
+ export interface SystemInfo {
5
5
  os: NodeJS.Platform;
6
- arch: NodeJS.Architecture;
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;
@@ -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
  /**
@@ -1,26 +1,29 @@
1
- export declare const theme: {
2
- readonly primary: "#00D9FF";
3
- readonly secondary: "#A78BFA";
4
- readonly accent: "#F472B6";
5
- readonly success: "#10B981";
6
- readonly error: "#EF4444";
7
- readonly warning: "#F59E0B";
8
- readonly info: "#3B82F6";
9
- readonly text: {
10
- readonly primary: "#E5E7EB";
11
- readonly secondary: "#9CA3AF";
12
- readonly muted: "#6B7280";
13
- readonly dim: "#4B5563";
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
- readonly border: "#374151";
16
- readonly divider: "#1F2937";
17
- readonly code: {
18
- readonly background: "#1F2937";
19
- readonly text: "#E5E7EB";
20
- readonly keyword: "#C678DD";
21
- readonly string: "#98C379";
22
- readonly function: "#61AFEF";
23
- readonly comment: "#5C6370";
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 type Theme = typeof theme;
26
+ }
27
+ export declare const themes: Record<ThemeName, Theme>;
28
+ export declare function getCurrentTheme(): Theme;
29
+ export declare const theme: Theme;