@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.
- package/README.md +283 -1
- package/bin/pls.tsx +1022 -104
- package/dist/bin/pls.js +894 -84
- 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 +2 -1
- package/dist/src/components/CodeColorizer.js +26 -20
- package/dist/src/components/CommandBox.js +2 -1
- 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 +34 -3
- package/dist/src/config.js +71 -31
- package/dist/src/multi-step.d.ts +22 -6
- package/dist/src/multi-step.js +27 -4
- 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 +53 -0
- package/dist/src/shell-hook.js +242 -19
- package/dist/src/ui/theme.d.ts +27 -24
- package/dist/src/ui/theme.js +71 -21
- package/dist/src/upgrade.d.ts +41 -0
- package/dist/src/upgrade.js +348 -0
- package/dist/src/utils/console.js +22 -11
- package/package.json +4 -4
- package/src/alias.ts +301 -0
- package/src/chat-history.ts +11 -1
- package/src/components/Chat.tsx +2 -1
- package/src/components/CodeColorizer.tsx +27 -19
- package/src/components/CommandBox.tsx +2 -1
- 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 +117 -32
- package/src/multi-step.ts +43 -6
- package/src/remote-history.ts +390 -0
- package/src/remote.ts +800 -0
- package/src/shell-hook.ts +271 -19
- package/src/ui/theme.ts +101 -24
- package/src/upgrade.ts +397 -0
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
230
|
-
console.log(chalk.
|
|
231
|
-
console.log(chalk.
|
|
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.
|
|
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.
|
|
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.
|
|
275
|
-
console.log(chalk.
|
|
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.
|
|
437
|
-
console.log(chalk.gray('运行 ') + chalk.
|
|
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.
|
|
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.
|
|
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.
|
|
487
|
+
` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.hex(colors.warning)('(已修改)')}`
|
|
472
488
|
)
|
|
473
489
|
} else {
|
|
474
490
|
console.log(
|
|
475
|
-
` ${chalk.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
17
|
-
secondary:
|
|
18
|
-
muted:
|
|
19
|
-
dim:
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
110
|
+
return themes.dark
|
|
111
|
+
}
|
|
36
112
|
|
|
37
|
-
|
|
113
|
+
// 向后兼容:导出默认主题
|
|
114
|
+
export const theme = darkTheme
|