@yivan-lab/pretty-please 1.1.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 (52) hide show
  1. package/README.md +283 -1
  2. package/bin/pls.tsx +1022 -104
  3. package/dist/bin/pls.js +894 -84
  4. package/dist/package.json +4 -4
  5. package/dist/src/alias.d.ts +41 -0
  6. package/dist/src/alias.js +240 -0
  7. package/dist/src/chat-history.js +10 -1
  8. package/dist/src/components/Chat.js +2 -1
  9. package/dist/src/components/CodeColorizer.js +26 -20
  10. package/dist/src/components/CommandBox.js +2 -1
  11. package/dist/src/components/ConfirmationPrompt.js +2 -1
  12. package/dist/src/components/Duration.js +2 -1
  13. package/dist/src/components/InlineRenderer.js +2 -1
  14. package/dist/src/components/MarkdownDisplay.js +2 -1
  15. package/dist/src/components/MultiStepCommandGenerator.d.ts +3 -1
  16. package/dist/src/components/MultiStepCommandGenerator.js +20 -10
  17. package/dist/src/components/TableRenderer.js +2 -1
  18. package/dist/src/config.d.ts +34 -3
  19. package/dist/src/config.js +71 -31
  20. package/dist/src/multi-step.d.ts +22 -6
  21. package/dist/src/multi-step.js +27 -4
  22. package/dist/src/remote-history.d.ts +63 -0
  23. package/dist/src/remote-history.js +315 -0
  24. package/dist/src/remote.d.ts +113 -0
  25. package/dist/src/remote.js +634 -0
  26. package/dist/src/shell-hook.d.ts +53 -0
  27. package/dist/src/shell-hook.js +242 -19
  28. package/dist/src/ui/theme.d.ts +27 -24
  29. package/dist/src/ui/theme.js +71 -21
  30. package/dist/src/upgrade.d.ts +41 -0
  31. package/dist/src/upgrade.js +348 -0
  32. package/dist/src/utils/console.js +22 -11
  33. package/package.json +4 -4
  34. package/src/alias.ts +301 -0
  35. package/src/chat-history.ts +11 -1
  36. package/src/components/Chat.tsx +2 -1
  37. package/src/components/CodeColorizer.tsx +27 -19
  38. package/src/components/CommandBox.tsx +2 -1
  39. package/src/components/ConfirmationPrompt.tsx +2 -1
  40. package/src/components/Duration.tsx +2 -1
  41. package/src/components/InlineRenderer.tsx +2 -1
  42. package/src/components/MarkdownDisplay.tsx +2 -1
  43. package/src/components/MultiStepCommandGenerator.tsx +25 -11
  44. package/src/components/TableRenderer.tsx +2 -1
  45. package/src/config.ts +117 -32
  46. package/src/multi-step.ts +43 -6
  47. package/src/remote-history.ts +390 -0
  48. package/src/remote.ts +800 -0
  49. package/src/shell-hook.ts +271 -19
  50. package/src/ui/theme.ts +101 -24
  51. package/src/upgrade.ts +397 -0
  52. package/src/utils/console.ts +22 -11
@@ -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
  /**
@@ -361,10 +375,11 @@ export function getHookStatus() {
361
375
  export function displayShellHistory() {
362
376
  const config = getConfig();
363
377
  const history = getShellHistory();
378
+ const colors = getColors();
364
379
  if (!config.shellHook) {
365
380
  console.log('');
366
- console.log(chalk.yellow('⚠️ Shell Hook 未启用'));
367
- console.log(chalk.gray('运行 ') + chalk.cyan('pls hook install') + chalk.gray(' 启用 Shell Hook'));
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'));
368
383
  console.log('');
369
384
  return;
370
385
  }
@@ -379,7 +394,7 @@ export function displayShellHistory() {
379
394
  console.log(chalk.gray('━'.repeat(50)));
380
395
  history.forEach((item, index) => {
381
396
  const num = index + 1;
382
- const status = item.exit === 0 ? chalk.green('✓') : chalk.red(`✗ (${item.exit})`);
397
+ const status = item.exit === 0 ? chalk.hex(colors.success)('✓') : chalk.hex(colors.error)(`✗ (${item.exit})`);
383
398
  // 检查是否是 pls 命令
384
399
  const isPls = item.cmd.startsWith('pls ') || item.cmd.startsWith('please ');
385
400
  if (isPls) {
@@ -389,20 +404,20 @@ export function displayShellHistory() {
389
404
  if (plsRecord && plsRecord.executed) {
390
405
  // 检查用户是否修改了命令
391
406
  if (plsRecord.userModified && plsRecord.aiGeneratedCommand) {
392
- console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} "${args}"`);
407
+ console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} "${args}"`);
393
408
  console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(plsRecord.aiGeneratedCommand)}`);
394
- console.log(` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.yellow('(已修改)')}`);
409
+ console.log(` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.hex(colors.warning)('(已修改)')}`);
395
410
  }
396
411
  else {
397
- console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} "${args}" → ${plsRecord.command} ${status}`);
412
+ console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} "${args}" → ${plsRecord.command} ${status}`);
398
413
  }
399
414
  }
400
415
  else {
401
- console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} ${args} ${status}`);
416
+ console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} ${args} ${status}`);
402
417
  }
403
418
  }
404
419
  else {
405
- console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${item.cmd} ${status}`);
420
+ console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${item.cmd} ${status}`);
406
421
  }
407
422
  });
408
423
  console.log(chalk.gray('━'.repeat(50)));
@@ -417,7 +432,215 @@ export function clearShellHistory() {
417
432
  if (fs.existsSync(SHELL_HISTORY_FILE)) {
418
433
  fs.unlinkSync(SHELL_HISTORY_FILE);
419
434
  }
435
+ const colors = getColors();
420
436
  console.log('');
421
- console.log(chalk.green('✓ Shell 历史已清空'));
437
+ console.log(chalk.hex(colors.success)('✓ Shell 历史已清空'));
422
438
  console.log('');
423
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,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;
@@ -1,25 +1,23 @@
1
- // 主题配置 - 优雅的配色方案
2
- export const theme = {
3
- // 主色调
4
- primary: '#00D9FF', // 青色 - 主要交互元素
5
- secondary: '#A78BFA', // 紫色 - 次要元素
6
- accent: '#F472B6', // 粉色 - 强调元素
7
- // 状态色
8
- success: '#10B981', // 绿色 - 成功
9
- error: '#EF4444', // 红色 - 错误
10
- warning: '#F59E0B', // 橙色 - 警告
11
- info: '#3B82F6', // 蓝色 - 信息
12
- // 文本色
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ // 深色主题(原默认主题)
5
+ const darkTheme = {
6
+ primary: '#00D9FF',
7
+ secondary: '#A78BFA',
8
+ accent: '#F472B6',
9
+ success: '#10B981',
10
+ error: '#EF4444',
11
+ warning: '#F59E0B',
12
+ info: '#3B82F6',
13
13
  text: {
14
- primary: '#E5E7EB', // 主文本
15
- secondary: '#9CA3AF', // 次要文本
16
- muted: '#6B7280', // 弱化文本
17
- dim: '#4B5563', // 暗淡文本
14
+ primary: '#E5E7EB',
15
+ secondary: '#9CA3AF',
16
+ muted: '#6B7280',
17
+ dim: '#4B5563',
18
18
  },
19
- // 边框和分隔
20
- border: '#374151', // 边框色
21
- divider: '#1F2937', // 分隔线
22
- // 代码相关
19
+ border: '#374151',
20
+ divider: '#1F2937',
23
21
  code: {
24
22
  background: '#1F2937',
25
23
  text: '#E5E7EB',
@@ -27,5 +25,57 @@ export const theme = {
27
25
  string: '#98C379',
28
26
  function: '#61AFEF',
29
27
  comment: '#5C6370',
30
- }
28
+ },
29
+ };
30
+ // 浅色主题(白色/浅色终端背景)
31
+ // 所有颜色都要在白色背景上清晰可见
32
+ const lightTheme = {
33
+ primary: '#0369A1', // 深天蓝,在白底上醒目
34
+ secondary: '#6D28D9', // 深紫色
35
+ accent: '#BE185D', // 深粉色
36
+ success: '#047857', // 深绿色
37
+ error: '#B91C1C', // 深红色
38
+ warning: '#B45309', // 深橙色
39
+ info: '#1D4ED8', // 深蓝色
40
+ text: {
41
+ primary: '#111827', // 近黑色,主要文字
42
+ secondary: '#374151', // 深灰色
43
+ muted: '#4B5563', // 中灰色
44
+ dim: '#6B7280', // 浅灰色
45
+ },
46
+ border: '#6B7280', // 边框要明显
47
+ divider: '#9CA3AF',
48
+ code: {
49
+ background: '#F3F4F6',
50
+ text: '#111827',
51
+ keyword: '#6D28D9',
52
+ string: '#047857',
53
+ function: '#0369A1',
54
+ comment: '#4B5563',
55
+ },
56
+ };
57
+ // 所有主题
58
+ export const themes = {
59
+ dark: darkTheme,
60
+ light: lightTheme,
31
61
  };
62
+ // 获取当前主题
63
+ export function getCurrentTheme() {
64
+ // 直接读取配置文件,避免循环依赖
65
+ try {
66
+ const configPath = path.join(os.homedir(), '.please', 'config.json');
67
+ if (fs.existsSync(configPath)) {
68
+ const content = fs.readFileSync(configPath, 'utf-8');
69
+ const config = JSON.parse(content);
70
+ if (config.theme && themes[config.theme]) {
71
+ return themes[config.theme];
72
+ }
73
+ }
74
+ }
75
+ catch {
76
+ // 忽略错误,返回默认主题
77
+ }
78
+ return themes.dark;
79
+ }
80
+ // 向后兼容:导出默认主题
81
+ export const theme = darkTheme;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * 获取最新版本(通过重定向,避免 API 限制)
3
+ * 优先使用 curl(支持代理),fallback 到 https 模块
4
+ */
5
+ export declare function getLatestVersion(): Promise<string | null>;
6
+ /**
7
+ * 比较版本号
8
+ * @returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal
9
+ */
10
+ export declare function compareVersions(v1: string, v2: string): number;
11
+ /**
12
+ * 检测当前平台
13
+ */
14
+ export declare function detectPlatform(): {
15
+ os: string;
16
+ arch: string;
17
+ artifact: string;
18
+ } | null;
19
+ /**
20
+ * 获取当前可执行文件路径
21
+ */
22
+ export declare function getCurrentExecutablePath(): string;
23
+ /**
24
+ * 检查是否有新版本(带缓存)
25
+ */
26
+ export declare function checkForUpdates(currentVersion: string, force?: boolean): Promise<{
27
+ hasUpdate: boolean;
28
+ latestVersion: string | null;
29
+ }>;
30
+ /**
31
+ * 显示更新提示
32
+ */
33
+ export declare function showUpdateNotice(currentVersion: string, latestVersion: string): void;
34
+ /**
35
+ * 检测是否是 Bun 编译的二进制
36
+ */
37
+ export declare function isBunBinary(): boolean;
38
+ /**
39
+ * 执行升级
40
+ */
41
+ export declare function performUpgrade(currentVersion: string): Promise<boolean>;