@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
package/src/shell-hook.ts CHANGED
@@ -4,6 +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
+ // 获取主题颜色
10
+ function getColors() {
11
+ const theme = getCurrentTheme()
12
+ return {
13
+ primary: theme.primary,
14
+ success: theme.success,
15
+ error: theme.error,
16
+ warning: theme.warning,
17
+ secondary: theme.secondary,
18
+ }
19
+ }
7
20
 
8
21
  const SHELL_HISTORY_FILE = path.join(CONFIG_DIR, 'shell_history.jsonl')
9
22
  const MAX_SHELL_HISTORY = 20
@@ -186,15 +199,16 @@ function generateHookScript(shellType: ShellType): string | null {
186
199
  export async function installShellHook(): Promise<boolean> {
187
200
  const shellType = detectShell()
188
201
  const configPath = getShellConfigPath(shellType)
202
+ const colors = getColors()
189
203
 
190
204
  if (!configPath) {
191
- console.log(chalk.red(`❌ 不支持的 shell 类型: ${shellType}`))
205
+ console.log(chalk.hex(colors.error)(`❌ 不支持的 shell 类型: ${shellType}`))
192
206
  return false
193
207
  }
194
208
 
195
209
  const hookScript = generateHookScript(shellType)
196
210
  if (!hookScript) {
197
- console.log(chalk.red(`❌ 无法为 ${shellType} 生成 hook 脚本`))
211
+ console.log(chalk.hex(colors.error)(`❌ 无法为 ${shellType} 生成 hook 脚本`))
198
212
  return false
199
213
  }
200
214
 
@@ -202,7 +216,7 @@ export async function installShellHook(): Promise<boolean> {
202
216
  if (fs.existsSync(configPath)) {
203
217
  const content = fs.readFileSync(configPath, 'utf-8')
204
218
  if (content.includes(HOOK_START_MARKER)) {
205
- console.log(chalk.yellow('⚠️ Shell hook 已安装,跳过'))
219
+ console.log(chalk.hex(colors.warning)('⚠️ Shell hook 已安装,跳过'))
206
220
  setConfigValue('shellHook', true)
207
221
  return true
208
222
  }
@@ -226,9 +240,9 @@ export async function installShellHook(): Promise<boolean> {
226
240
  // 更新配置
227
241
  setConfigValue('shellHook', true)
228
242
 
229
- console.log(chalk.green(`✅ Shell hook 已安装到: ${configPath}`))
230
- console.log(chalk.yellow('⚠️ 请重启终端或执行以下命令使其生效:'))
231
- console.log(chalk.cyan(` source ${configPath}`))
243
+ console.log(chalk.hex(colors.success)(`✅ Shell hook 已安装到: ${configPath}`))
244
+ console.log(chalk.hex(colors.warning)('⚠️ 请重启终端或执行以下命令使其生效:'))
245
+ console.log(chalk.hex(colors.primary)(` source ${configPath}`))
232
246
 
233
247
  return true
234
248
  }
@@ -239,9 +253,10 @@ export async function installShellHook(): Promise<boolean> {
239
253
  export function uninstallShellHook(): boolean {
240
254
  const shellType = detectShell()
241
255
  const configPath = getShellConfigPath(shellType)
256
+ const colors = getColors()
242
257
 
243
258
  if (!configPath || !fs.existsSync(configPath)) {
244
- console.log(chalk.yellow('⚠️ 未找到 shell 配置文件'))
259
+ console.log(chalk.hex(colors.warning)('⚠️ 未找到 shell 配置文件'))
245
260
  setConfigValue('shellHook', false)
246
261
  return true
247
262
  }
@@ -253,7 +268,7 @@ export function uninstallShellHook(): boolean {
253
268
  const endIndex = content.indexOf(HOOK_END_MARKER)
254
269
 
255
270
  if (startIndex === -1 || endIndex === -1) {
256
- console.log(chalk.yellow('⚠️ 未找到已安装的 hook'))
271
+ console.log(chalk.hex(colors.warning)('⚠️ 未找到已安装的 hook'))
257
272
  setConfigValue('shellHook', false)
258
273
  return true
259
274
  }
@@ -271,8 +286,8 @@ export function uninstallShellHook(): boolean {
271
286
  fs.unlinkSync(SHELL_HISTORY_FILE)
272
287
  }
273
288
 
274
- console.log(chalk.green('✅ Shell hook 已卸载'))
275
- console.log(chalk.yellow('⚠️ 请重启终端使其生效'))
289
+ console.log(chalk.hex(colors.success)('✅ Shell hook 已卸载'))
290
+ console.log(chalk.hex(colors.warning)('⚠️ 请重启终端使其生效'))
276
291
 
277
292
  return true
278
293
  }
@@ -430,11 +445,12 @@ export function getHookStatus(): HookStatus {
430
445
  export function displayShellHistory(): void {
431
446
  const config = getConfig()
432
447
  const history = getShellHistory()
448
+ const colors = getColors()
433
449
 
434
450
  if (!config.shellHook) {
435
451
  console.log('')
436
- console.log(chalk.yellow('⚠️ Shell Hook 未启用'))
437
- console.log(chalk.gray('运行 ') + chalk.cyan('pls hook install') + chalk.gray(' 启用 Shell Hook'))
452
+ console.log(chalk.hex(colors.warning)('⚠️ Shell Hook 未启用'))
453
+ console.log(chalk.gray('运行 ') + chalk.hex(colors.primary)('pls hook install') + chalk.gray(' 启用 Shell Hook'))
438
454
  console.log('')
439
455
  return
440
456
  }
@@ -452,7 +468,7 @@ export function displayShellHistory(): void {
452
468
 
453
469
  history.forEach((item, index) => {
454
470
  const num = index + 1
455
- const status = item.exit === 0 ? chalk.green('✓') : chalk.red(`✗ (${item.exit})`)
471
+ const status = item.exit === 0 ? chalk.hex(colors.success)('✓') : chalk.hex(colors.error)(`✗ (${item.exit})`)
456
472
 
457
473
  // 检查是否是 pls 命令
458
474
  const isPls = item.cmd.startsWith('pls ') || item.cmd.startsWith('please ')
@@ -465,21 +481,21 @@ export function displayShellHistory(): void {
465
481
  if (plsRecord && plsRecord.executed) {
466
482
  // 检查用户是否修改了命令
467
483
  if (plsRecord.userModified && plsRecord.aiGeneratedCommand) {
468
- console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} "${args}"`)
484
+ console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} "${args}"`)
469
485
  console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(plsRecord.aiGeneratedCommand)}`)
470
486
  console.log(
471
- ` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.yellow('(已修改)')}`
487
+ ` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.hex(colors.warning)('(已修改)')}`
472
488
  )
473
489
  } else {
474
490
  console.log(
475
- ` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} "${args}" → ${plsRecord.command} ${status}`
491
+ ` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} "${args}" → ${plsRecord.command} ${status}`
476
492
  )
477
493
  }
478
494
  } else {
479
- console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} ${args} ${status}`)
495
+ console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${chalk.hex(colors.secondary)('[pls]')} ${args} ${status}`)
480
496
  }
481
497
  } else {
482
- console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${item.cmd} ${status}`)
498
+ console.log(` ${chalk.hex(colors.primary)(num.toString().padStart(2, ' '))}. ${item.cmd} ${status}`)
483
499
  }
484
500
  })
485
501
 
@@ -496,7 +512,243 @@ export function clearShellHistory(): void {
496
512
  if (fs.existsSync(SHELL_HISTORY_FILE)) {
497
513
  fs.unlinkSync(SHELL_HISTORY_FILE)
498
514
  }
515
+ const colors = getColors()
499
516
  console.log('')
500
- console.log(chalk.green('✓ Shell 历史已清空'))
517
+ console.log(chalk.hex(colors.success)('✓ Shell 历史已清空'))
501
518
  console.log('')
502
519
  }
520
+
521
+ // ================== 远程 Shell Hook ==================
522
+
523
+ /**
524
+ * 生成远程 zsh hook 脚本
525
+ */
526
+ function generateRemoteZshHook(): string {
527
+ return `
528
+ ${HOOK_START_MARKER}
529
+ # 记录命令到 pretty-please 历史
530
+ __pls_preexec() {
531
+ __PLS_LAST_CMD="$1"
532
+ __PLS_CMD_START=$(date +%s)
533
+ }
534
+
535
+ __pls_precmd() {
536
+ local exit_code=$?
537
+ if [[ -n "$__PLS_LAST_CMD" ]]; then
538
+ local end_time=$(date +%s)
539
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
540
+ # 确保目录存在
541
+ mkdir -p ~/.please
542
+ # 转义命令中的特殊字符
543
+ local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
544
+ echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
545
+ # 保持文件不超过 50 行
546
+ tail -n 50 ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
547
+ unset __PLS_LAST_CMD
548
+ fi
549
+ }
550
+
551
+ autoload -Uz add-zsh-hook
552
+ add-zsh-hook preexec __pls_preexec
553
+ add-zsh-hook precmd __pls_precmd
554
+ ${HOOK_END_MARKER}
555
+ `
556
+ }
557
+
558
+ /**
559
+ * 生成远程 bash hook 脚本
560
+ */
561
+ function generateRemoteBashHook(): string {
562
+ return `
563
+ ${HOOK_START_MARKER}
564
+ # 记录命令到 pretty-please 历史
565
+ __pls_prompt_command() {
566
+ local exit_code=$?
567
+ local last_cmd=$(history 1 | sed 's/^ *[0-9]* *//')
568
+ if [[ -n "$last_cmd" && "$last_cmd" != "$__PLS_LAST_CMD" ]]; then
569
+ __PLS_LAST_CMD="$last_cmd"
570
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
571
+ # 确保目录存在
572
+ mkdir -p ~/.please
573
+ local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
574
+ echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
575
+ tail -n 50 ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
576
+ fi
577
+ }
578
+
579
+ if [[ ! "$PROMPT_COMMAND" =~ __pls_prompt_command ]]; then
580
+ PROMPT_COMMAND="__pls_prompt_command;\${PROMPT_COMMAND}"
581
+ fi
582
+ ${HOOK_END_MARKER}
583
+ `
584
+ }
585
+
586
+ /**
587
+ * 检测远程服务器的 shell 类型
588
+ */
589
+ export async function detectRemoteShell(sshExecFn: (cmd: string) => Promise<{ stdout: string; exitCode: number }>): Promise<ShellType> {
590
+ try {
591
+ const result = await sshExecFn('basename "$SHELL"')
592
+ if (result.exitCode === 0) {
593
+ const shell = result.stdout.trim()
594
+ if (shell === 'zsh') return 'zsh'
595
+ if (shell === 'bash') return 'bash'
596
+ }
597
+ } catch {
598
+ // 忽略错误
599
+ }
600
+ return 'bash' // 默认 bash
601
+ }
602
+
603
+ /**
604
+ * 获取远程 shell 配置文件路径
605
+ */
606
+ export function getRemoteShellConfigPath(shellType: ShellType): string {
607
+ switch (shellType) {
608
+ case 'zsh':
609
+ return '~/.zshrc'
610
+ case 'bash':
611
+ return '~/.bashrc'
612
+ default:
613
+ return '~/.bashrc'
614
+ }
615
+ }
616
+
617
+ /**
618
+ * 生成远程 hook 脚本
619
+ */
620
+ export function generateRemoteHookScript(shellType: ShellType): string | null {
621
+ switch (shellType) {
622
+ case 'zsh':
623
+ return generateRemoteZshHook()
624
+ case 'bash':
625
+ return generateRemoteBashHook()
626
+ default:
627
+ return null
628
+ }
629
+ }
630
+
631
+ /**
632
+ * 检查远程 hook 是否已安装
633
+ */
634
+ export async function checkRemoteHookInstalled(
635
+ sshExecFn: (cmd: string) => Promise<{ stdout: string; exitCode: number }>,
636
+ configPath: string
637
+ ): Promise<boolean> {
638
+ try {
639
+ const result = await sshExecFn(`grep -q "${HOOK_START_MARKER}" ${configPath} 2>/dev/null && echo "installed" || echo "not_installed"`)
640
+ return result.stdout.trim() === 'installed'
641
+ } catch {
642
+ return false
643
+ }
644
+ }
645
+
646
+ /**
647
+ * 在远程服务器安装 shell hook
648
+ */
649
+ export async function installRemoteShellHook(
650
+ sshExecFn: (cmd: string) => Promise<{ stdout: string; exitCode: number }>,
651
+ shellType: ShellType
652
+ ): Promise<{ success: boolean; message: string }> {
653
+ const colors = getColors()
654
+ const configPath = getRemoteShellConfigPath(shellType)
655
+ const hookScript = generateRemoteHookScript(shellType)
656
+
657
+ if (!hookScript) {
658
+ return { success: false, message: chalk.hex(colors.error)(`不支持的 shell 类型: ${shellType}`) }
659
+ }
660
+
661
+ // 检查是否已安装
662
+ const installed = await checkRemoteHookInstalled(sshExecFn, configPath)
663
+ if (installed) {
664
+ return { success: true, message: chalk.hex(colors.warning)('Shell hook 已安装,跳过') }
665
+ }
666
+
667
+ // 备份原配置文件
668
+ try {
669
+ await sshExecFn(`cp ${configPath} ${configPath}.pls-backup 2>/dev/null || true`)
670
+ } catch {
671
+ // 忽略备份错误
672
+ }
673
+
674
+ // 安装 hook
675
+ // 使用 cat 和 heredoc 来追加内容
676
+ const escapedScript = hookScript.replace(/'/g, "'\"'\"'")
677
+ const installCmd = `echo '${escapedScript}' >> ${configPath}`
678
+
679
+ try {
680
+ const result = await sshExecFn(installCmd)
681
+ if (result.exitCode !== 0) {
682
+ return { success: false, message: chalk.hex(colors.error)(`安装失败: ${result.stdout}`) }
683
+ }
684
+
685
+ // 确保 ~/.please 目录存在
686
+ await sshExecFn('mkdir -p ~/.please')
687
+
688
+ return {
689
+ success: true,
690
+ message: chalk.hex(colors.success)(`Shell hook 已安装到 ${configPath}`),
691
+ }
692
+ } catch (error) {
693
+ const message = error instanceof Error ? error.message : String(error)
694
+ return { success: false, message: chalk.hex(colors.error)(`安装失败: ${message}`) }
695
+ }
696
+ }
697
+
698
+ /**
699
+ * 从远程服务器卸载 shell hook
700
+ */
701
+ export async function uninstallRemoteShellHook(
702
+ sshExecFn: (cmd: string) => Promise<{ stdout: string; exitCode: number }>,
703
+ shellType: ShellType
704
+ ): Promise<{ success: boolean; message: string }> {
705
+ const colors = getColors()
706
+ const configPath = getRemoteShellConfigPath(shellType)
707
+
708
+ // 检查是否已安装
709
+ const installed = await checkRemoteHookInstalled(sshExecFn, configPath)
710
+ if (!installed) {
711
+ return { success: true, message: chalk.hex(colors.warning)('Shell hook 未安装,跳过') }
712
+ }
713
+
714
+ // 使用 sed 删除 hook 代码块
715
+ // 注意:需要处理特殊字符
716
+ const startMarkerEscaped = HOOK_START_MARKER.replace(/[[\]]/g, '\\$&')
717
+ const endMarkerEscaped = HOOK_END_MARKER.replace(/[[\]]/g, '\\$&')
718
+
719
+ // 在 macOS 和 Linux 上 sed -i 行为不同,使用 sed + 临时文件
720
+ const uninstallCmd = `
721
+ sed '/${startMarkerEscaped}/,/${endMarkerEscaped}/d' ${configPath} > ${configPath}.tmp && mv ${configPath}.tmp ${configPath}
722
+ `
723
+
724
+ try {
725
+ const result = await sshExecFn(uninstallCmd)
726
+ if (result.exitCode !== 0) {
727
+ return { success: false, message: chalk.hex(colors.error)(`卸载失败: ${result.stdout}`) }
728
+ }
729
+
730
+ return {
731
+ success: true,
732
+ message: chalk.hex(colors.success)('Shell hook 已卸载'),
733
+ }
734
+ } catch (error) {
735
+ const message = error instanceof Error ? error.message : String(error)
736
+ return { success: false, message: chalk.hex(colors.error)(`卸载失败: ${message}`) }
737
+ }
738
+ }
739
+
740
+ /**
741
+ * 获取远程 hook 状态
742
+ */
743
+ export async function getRemoteHookStatus(
744
+ sshExecFn: (cmd: string) => Promise<{ stdout: string; exitCode: number }>
745
+ ): Promise<{ installed: boolean; shellType: ShellType; configPath: string }> {
746
+ // 检测 shell 类型
747
+ const shellType = await detectRemoteShell(sshExecFn)
748
+ const configPath = getRemoteShellConfigPath(shellType)
749
+
750
+ // 检查是否已安装
751
+ const installed = await checkRemoteHookInstalled(sshExecFn, configPath)
752
+
753
+ return { installed, shellType, configPath }
754
+ }
package/src/ui/theme.ts CHANGED
@@ -1,29 +1,53 @@
1
- // 主题配置 - 优雅的配色方案
2
- export const theme = {
3
- // 主色调
4
- primary: '#00D9FF', // 青色 - 主要交互元素
5
- secondary: '#A78BFA', // 紫色 - 次要元素
6
- accent: '#F472B6', // 粉色 - 强调元素
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import os from 'os'
7
4
 
8
- // 状态色
9
- success: '#10B981', // 绿色 - 成功
10
- error: '#EF4444', // 红色 - 错误
11
- warning: '#F59E0B', // 橙色 - 警告
12
- info: '#3B82F6', // 蓝色 - 信息
5
+ // 主题类型定义
6
+ export type ThemeName = 'dark' | 'light'
13
7
 
14
- // 文本色
8
+ export interface Theme {
9
+ primary: string
10
+ secondary: string
11
+ accent: string
12
+ success: string
13
+ error: string
14
+ warning: string
15
+ info: string
15
16
  text: {
16
- primary: '#E5E7EB', // 主文本
17
- secondary: '#9CA3AF', // 次要文本
18
- muted: '#6B7280', // 弱化文本
19
- dim: '#4B5563', // 暗淡文本
20
- },
21
-
22
- // 边框和分隔
23
- border: '#374151', // 边框色
24
- divider: '#1F2937', // 分隔线
17
+ primary: string
18
+ secondary: string
19
+ muted: string
20
+ dim: string
21
+ }
22
+ border: string
23
+ divider: string
24
+ code: {
25
+ background: string
26
+ text: string
27
+ keyword: string
28
+ string: string
29
+ function: string
30
+ comment: string
31
+ }
32
+ }
25
33
 
26
- // 代码相关
34
+ // 深色主题(原默认主题)
35
+ const darkTheme: Theme = {
36
+ primary: '#00D9FF',
37
+ secondary: '#A78BFA',
38
+ accent: '#F472B6',
39
+ success: '#10B981',
40
+ error: '#EF4444',
41
+ warning: '#F59E0B',
42
+ info: '#3B82F6',
43
+ text: {
44
+ primary: '#E5E7EB',
45
+ secondary: '#9CA3AF',
46
+ muted: '#6B7280',
47
+ dim: '#4B5563',
48
+ },
49
+ border: '#374151',
50
+ divider: '#1F2937',
27
51
  code: {
28
52
  background: '#1F2937',
29
53
  text: '#E5E7EB',
@@ -31,7 +55,60 @@ export const theme = {
31
55
  string: '#98C379',
32
56
  function: '#61AFEF',
33
57
  comment: '#5C6370',
58
+ },
59
+ }
60
+
61
+ // 浅色主题(白色/浅色终端背景)
62
+ // 所有颜色都要在白色背景上清晰可见
63
+ const lightTheme: Theme = {
64
+ primary: '#0369A1', // 深天蓝,在白底上醒目
65
+ secondary: '#6D28D9', // 深紫色
66
+ accent: '#BE185D', // 深粉色
67
+ success: '#047857', // 深绿色
68
+ error: '#B91C1C', // 深红色
69
+ warning: '#B45309', // 深橙色
70
+ info: '#1D4ED8', // 深蓝色
71
+ text: {
72
+ primary: '#111827', // 近黑色,主要文字
73
+ secondary: '#374151', // 深灰色
74
+ muted: '#4B5563', // 中灰色
75
+ dim: '#6B7280', // 浅灰色
76
+ },
77
+ border: '#6B7280', // 边框要明显
78
+ divider: '#9CA3AF',
79
+ code: {
80
+ background: '#F3F4F6',
81
+ text: '#111827',
82
+ keyword: '#6D28D9',
83
+ string: '#047857',
84
+ function: '#0369A1',
85
+ comment: '#4B5563',
86
+ },
87
+ }
88
+
89
+ // 所有主题
90
+ export const themes: Record<ThemeName, Theme> = {
91
+ dark: darkTheme,
92
+ light: lightTheme,
93
+ }
94
+
95
+ // 获取当前主题
96
+ export function getCurrentTheme(): Theme {
97
+ // 直接读取配置文件,避免循环依赖
98
+ try {
99
+ const configPath = path.join(os.homedir(), '.please', 'config.json')
100
+ if (fs.existsSync(configPath)) {
101
+ const content = fs.readFileSync(configPath, 'utf-8')
102
+ const config = JSON.parse(content)
103
+ if (config.theme && themes[config.theme as ThemeName]) {
104
+ return themes[config.theme as ThemeName]
105
+ }
106
+ }
107
+ } catch {
108
+ // 忽略错误,返回默认主题
34
109
  }
35
- } as const
110
+ return themes.dark
111
+ }
36
112
 
37
- export type Theme = typeof theme
113
+ // 向后兼容:导出默认主题
114
+ export const theme = darkTheme