- Electron + React + TypeScript 桌面宠物外壳 - xterm.js + node-pty 直接对接 Claude CLI 进程 - 宠物 orb(80x80)可拖拽,点击展开终端面板 - CRT 复古终端主题(扫描线、文字发光) - 全局快捷键 Cmd+Shift+C 显隐 - 窗口位置自动保存 - 中华田园犬吉祥物 icon(AI 生成) - Makefile 新增 `make app` 打包命令
66 lines
1.5 KiB
TypeScript
66 lines
1.5 KiB
TypeScript
import * as pty from 'node-pty'
|
|
import { execSync } from 'child_process'
|
|
import { EventEmitter } from 'events'
|
|
|
|
export class PTYManager extends EventEmitter {
|
|
private ptyProcess: pty.IPty | null = null
|
|
|
|
start(shell: string = 'claude', args: string[] = []) {
|
|
const resolvedShell = this.resolveShell(shell)
|
|
const resolvedArgs = shell === 'claude' ? args : []
|
|
|
|
this.ptyProcess = pty.spawn(resolvedShell, resolvedArgs, {
|
|
name: 'xterm-color',
|
|
cols: 80,
|
|
rows: 24,
|
|
cwd: process.env.HOME,
|
|
env: process.env as { [key: string]: string },
|
|
})
|
|
|
|
this.ptyProcess.onData((data: string) => {
|
|
this.emit('data', data)
|
|
})
|
|
|
|
this.ptyProcess.onExit(({ exitCode }) => {
|
|
this.emit('exit', exitCode)
|
|
})
|
|
|
|
return this.ptyProcess
|
|
}
|
|
|
|
write(data: string) {
|
|
if (this.ptyProcess) {
|
|
this.ptyProcess.write(data)
|
|
}
|
|
}
|
|
|
|
resize(cols: number, rows: number) {
|
|
if (this.ptyProcess) {
|
|
this.ptyProcess.resize(cols, rows)
|
|
}
|
|
}
|
|
|
|
kill() {
|
|
if (this.ptyProcess) {
|
|
this.ptyProcess.kill()
|
|
this.ptyProcess = null
|
|
}
|
|
}
|
|
|
|
private resolveShell(shell: string): string {
|
|
if (shell === 'claude') {
|
|
try {
|
|
const path = execSync('which claude', { encoding: 'utf8' }).trim()
|
|
return path
|
|
} catch {
|
|
try {
|
|
return execSync('which zsh', { encoding: 'utf8' }).trim()
|
|
} catch {
|
|
return '/bin/zsh'
|
|
}
|
|
}
|
|
}
|
|
return shell
|
|
}
|
|
}
|