hermes-ide-terminal
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHermes IDE Terminal Skill
Hermes IDE终端技能
Overview
概述
Hermes IDE is an AI-native terminal emulator built with Tauri 2, React 18, and Rust. It provides:
- Agent mode for Claude with rich chat interface, image support, and persistent conversations
- Multi-session terminal management with split panes and WebGL rendering
- Git integration with built-in panel for staging, commits, and inline diffs
- AI intelligence including ghost-text suggestions, prompt composer, and error pattern matching
- Project awareness that scans your codebase to build context for AI agents
- Cross-platform support for macOS, Windows, and Linux
Hermes IDE是一款基于Tauri 2、React 18和Rust构建的原生AI终端模拟器。它提供以下功能:
- Claude代理模式:带有丰富聊天界面、图片支持和持久化对话
- 多会话终端管理:支持分屏和WebGL渲染
- Git集成:内置面板用于暂存、提交和内联差异对比
- AI智能功能:包括幽灵文本建议、提示编辑器和错误模式匹配
- 项目感知:扫描代码库为AI代理构建上下文
- 跨平台支持:兼容macOS、Windows和Linux
Installation
安装
Pre-built Binaries
预构建二进制文件
Download from the official website:
bash
undefined从官方网站下载:
bash
undefinedChoose your platform: macOS (Intel/Apple Silicon), Windows, Linux (.deb/.AppImage/.rpm)
Choose your platform: macOS (Intel/Apple Silicon), Windows, Linux (.deb/.AppImage/.rpm)
undefinedundefinedBuild from Source
从源码构建
Prerequisites:
bash
undefined前置依赖:
bash
undefinedCheck versions
Check versions
node --version # 18+
rustc --version # 1.70+
**Platform-specific dependencies:**
```bashnode --version # 18+
rustc --version # 1.70+
**平台专属依赖:**
```bashLinux (Debian/Ubuntu)
Linux (Debian/Ubuntu)
sudo apt install libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
sudo apt install libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
macOS
macOS
xcode-select --install
xcode-select --install
Windows
Windows
Install Visual Studio Build Tools with "Desktop development with C++"
Install Visual Studio Build Tools with "Desktop development with C++"
Install WebView2 Runtime from Microsoft
Install WebView2 Runtime from Microsoft
**Clone and build:**
```bash
git clone https://github.com/hermes-hq/hermes-ide.git
cd hermes-ide
npm install
npm run tauri dev # Development mode with hot-reload
npm run tauri build # Production build
**克隆并构建:**
```bash
git clone https://github.com/hermes-hq/hermes-ide.git
cd hermes-ide
npm install
npm run tauri dev # Development mode with hot-reload
npm run tauri build # Production buildArchitecture
架构
Hermes IDE uses a layered architecture:
React Frontend (TypeScript) ← Tauri IPC Bridge → Rust Backend
UI & State PTY, DB, ScannerKey directories:
- - React/TypeScript frontend (components, hooks, state, terminal)
src/ - - Rust backend (PTY management, SQLite, project scanning)
src-tauri/ - - Tauri IPC command wrappers
src/api/ - - Terminal pool & AI intelligence engine
src/terminal/
Hermes IDE采用分层架构:
React Frontend (TypeScript) ← Tauri IPC Bridge → Rust Backend
UI & State PTY, DB, Scanner核心目录:
- - React/TypeScript前端(组件、Hook、状态、终端)
src/ - - Rust后端(PTY管理、SQLite、项目扫描)
src-tauri/ - - Tauri IPC命令封装
src/api/ - - 终端池与AI智能引擎
src/terminal/
Configuration
配置
Claude Agent Setup
Claude代理设置
Hermes uses your existing CLI authentication:
claudebash
undefinedHermes使用您已有的 CLI认证:
claudebash
undefinedInstall Claude CLI first
Install Claude CLI first
npm install -g @anthropic-ai/claude-cli
npm install -g @anthropic-ai/claude-cli
Authenticate (choose one method)
Authenticate (choose one method)
claude auth login # Pro/Max account
claude auth api-key $ANTHROPIC_API_KEY # API key
**In Hermes IDE:**
1. Open Command Palette (Cmd/Ctrl+K)
2. Type "Claude"
3. Select "Start Claude Agent Session"
4. Conversations persist across restartsclaude auth login # Pro/Max account
claude auth api-key $ANTHROPIC_API_KEY # API key
**在Hermes IDE中:**
1. 打开命令面板(Cmd/Ctrl+K)
2. 输入“Claude”
3. 选择“Start Claude Agent Session”
4. 对话会在重启后保留Project Scanning
项目扫描
Hermes automatically scans projects to build AI context:
typescript
// Project scanning happens automatically when you:
// 1. Open a terminal in a directory
// 2. Attach a project via sidebar
// 3. Use Cmd/Ctrl+Shift+P → "Scan Project"
// Detected information includes:
// - Languages and frameworks
// - Project structure
// - Dependencies
// - Git repository infoHermes会自动扫描项目以构建AI上下文:
typescript
// Project scanning happens automatically when you:
// 1. Open a terminal in a directory
// 2. Attach a project via sidebar
// 3. Use Cmd/Ctrl+Shift+P → "Scan Project"
// Detected information includes:
// - Languages and frameworks
// - Project structure
// - Dependencies
// - Git repository infoTerminal Configuration
终端配置
Configure shell and environment in settings:
json
{
"shell": {
"program": "/bin/zsh",
"args": ["-l"]
},
"environment": {
"TERM": "xterm-256color",
"COLORTERM": "truecolor"
},
"fontSize": 14,
"fontFamily": "JetBrains Mono, monospace"
}在设置中配置Shell和环境:
json
{
"shell": {
"program": "/bin/zsh",
"args": ["-l"]
},
"environment": {
"TERM": "xterm-256color",
"COLORTERM": "truecolor"
},
"fontSize": 14,
"fontFamily": "JetBrains Mono, monospace"
}Key Features & Usage
关键功能与用法
Terminal Sessions
终端会话
typescript
// Create new session
// Cmd/Ctrl+T
// Switch sessions
// Cmd/Ctrl+1-9 (first 9 sessions)
// Or use sidebar
// Split panes
// Cmd/Ctrl+D (horizontal)
// Cmd/Ctrl+Shift+D (vertical)
// Close session
// Cmd/Ctrl+Wtypescript
// Create new session
// Cmd/Ctrl+T
// Switch sessions
// Cmd/Ctrl+1-9 (first 9 sessions)
// Or use sidebar
// Split panes
// Cmd/Ctrl+D (horizontal)
// Cmd/Ctrl+Shift+D (vertical)
// Close session
// Cmd/Ctrl+WGit Integration
Git集成
Access via sidebar panel:
bash
undefined通过侧边栏面板访问:
bash
undefinedAll git operations available via UI
All git operations available via UI
- View staged/unstaged/untracked files
- View staged/unstaged/untracked files
- Click files to see inline diffs
- Click files to see inline diffs
- Stage/unstage with checkboxes
- Stage/unstage with checkboxes
- Commit with message input
- Commit with message input
- Push/Pull buttons
- Push/Pull buttons
Or use git commands directly in terminal
Or use git commands directly in terminal
git add .
git commit -m "feat: add new feature"
git push origin main
undefinedgit add .
git commit -m "feat: add new feature"
git push origin main
undefinedAI Ghost-Text Suggestions
AI幽灵文本建议
Hermes provides real-time command suggestions:
bash
undefinedHermes提供实时命令建议:
bash
undefinedStart typing and ghost text appears
Start typing and ghost text appears
git com█
git com█
Suggestion: git commit -m "message"
Suggestion: git commit -m "message"
Accept with Tab or Right Arrow
Accept with Tab or Right Arrow
Reject with Esc or continue typing
Reject with Esc or continue typing
undefinedundefinedPrompt Composer
提示编辑器
Use natural language for autonomous execution:
bash
undefined使用自然语言执行自动化操作:
bash
undefinedOpen Prompt Composer: Cmd/Ctrl+Shift+P
Open Prompt Composer: Cmd/Ctrl+Shift+P
Type natural language instructions:
Type natural language instructions:
"Create a new React component called UserProfile with TypeScript"
"Fix all eslint errors in src/"
"Run tests and commit if they pass"
"Create a new React component called UserProfile with TypeScript"
"Fix all eslint errors in src/"
"Run tests and commit if they pass"
Hermes executes commands and tracks progress
Hermes executes commands and tracks progress
undefinedundefinedCommand Palette
命令面板
Access all features quickly:
bash
undefined快速访问所有功能:
bash
undefinedOpen: Cmd/Ctrl+K
Open: Cmd/Ctrl+K
Fuzzy search for:
Fuzzy search for:
- Commands
- Commands
- Settings
- Settings
- Projects
- Projects
- Sessions
- Sessions
- Git operations
- Git operations
undefinedundefinedDevelopment Workflow
开发工作流
Frontend Development
前端开发
typescript
// src/components/Terminal.tsx
import React, { useEffect, useRef } from 'react';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
export const TerminalComponent: React.FC = () => {
const terminalRef = useRef<HTMLDivElement>(null);
const xtermRef = useRef<Terminal | null>(null);
useEffect(() => {
if (!terminalRef.current) return;
const term = new Terminal({
fontFamily: 'JetBrains Mono, monospace',
fontSize: 14,
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
},
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(terminalRef.current);
fitAddon.fit();
xtermRef.current = term;
return () => {
term.dispose();
};
}, []);
return <div ref={terminalRef} className="terminal-container" />;
};typescript
// src/components/Terminal.tsx
import React, { useEffect, useRef } from 'react';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
export const TerminalComponent: React.FC = () => {
const terminalRef = useRef<HTMLDivElement>(null);
const xtermRef = useRef<Terminal | null>(null);
useEffect(() => {
if (!terminalRef.current) return;
const term = new Terminal({
fontFamily: 'JetBrains Mono, monospace',
fontSize: 14,
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
},
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(terminalRef.current);
fitAddon.fit();
xtermRef.current = term;
return () => {
term.dispose();
};
}, []);
return <div ref={terminalRef} className="terminal-container" />;
};Backend Development
后端开发
rust
// src-tauri/src/pty/mod.rs
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
use std::sync::Arc;
use tokio::sync::Mutex;
pub struct PtySession {
pub id: String,
pty: Arc<Mutex<Box<dyn portable_pty::MasterPty + Send>>>,
writer: Arc<Mutex<Box<dyn std::io::Write + Send>>>,
}
impl PtySession {
pub fn new(shell: &str, cwd: &str) -> Result<Self, Box<dyn std::error::Error>> {
let pty_system = native_pty_system();
let pair = pty_system.openpty(PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
})?;
let mut cmd = CommandBuilder::new(shell);
cmd.cwd(cwd);
let _child = pair.slave.spawn_command(cmd)?;
let writer = pair.master.take_writer()?;
Ok(Self {
id: uuid::Uuid::new_v4().to_string(),
pty: Arc::new(Mutex::new(pair.master)),
writer: Arc::new(Mutex::new(writer)),
})
}
pub async fn write(&self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
let mut writer = self.writer.lock().await;
writer.write_all(data)?;
writer.flush()?;
Ok(())
}
}rust
// src-tauri/src/pty/mod.rs
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
use std::sync::Arc;
use tokio::sync::Mutex;
pub struct PtySession {
pub id: String,
pty: Arc<Mutex<Box<dyn portable_pty::MasterPty + Send>>>,
writer: Arc<Mutex<Box<dyn std::io::Write + Send>>>,
}
impl PtySession {
pub fn new(shell: &str, cwd: &str) -> Result<Self, Box<dyn std::error::Error>> {
let pty_system = native_pty_system();
let pair = pty_system.openpty(PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
})?;
let mut cmd = CommandBuilder::new(shell);
cmd.cwd(cwd);
let _child = pair.slave.spawn_command(cmd)?;
let writer = pair.master.take_writer()?;
Ok(Self {
id: uuid::Uuid::new_v4().to_string(),
pty: Arc::new(Mutex::new(pair.master)),
writer: Arc::new(Mutex::new(writer)),
})
}
pub async fn write(&self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
let mut writer = self.writer.lock().await;
writer.write_all(data)?;
writer.flush()?;
Ok(())
}
}Tauri IPC Commands
Tauri IPC命令
rust
// src-tauri/src/commands.rs
use tauri::command;
#[command]
pub async fn create_terminal_session(
shell: String,
cwd: String,
) -> Result<String, String> {
let session = PtySession::new(&shell, &cwd)
.map_err(|e| e.to_string())?;
let session_id = session.id.clone();
// Store session in global state
// ...
Ok(session_id)
}
#[command]
pub async fn write_to_terminal(
session_id: String,
data: String,
) -> Result<(), String> {
// Get session from global state
// ...
session.write(data.as_bytes())
.await
.map_err(|e| e.to_string())?;
Ok(())
}rust
// src-tauri/src/commands.rs
use tauri::command;
#[command]
pub async fn create_terminal_session(
shell: String,
cwd: String,
) -> Result<String, String> {
let session = PtySession::new(&shell, &cwd)
.map_err(|e| e.to_string())?;
let session_id = session.id.clone();
// Store session in global state
// ...
Ok(session_id)
}
#[command]
pub async fn write_to_terminal(
session_id: String,
data: String,
) -> Result<(), String> {
// Get session from global state
// ...
session.write(data.as_bytes())
.await
.map_err(|e| e.to_string())?;
Ok(())
}Frontend API Wrappers
前端API封装
typescript
// src/api/terminal.ts
import { invoke } from '@tauri-apps/api/tauri';
export interface TerminalSession {
id: string;
cwd: string;
shell: string;
}
export async function createTerminalSession(
shell: string,
cwd: string
): Promise<string> {
return await invoke<string>('create_terminal_session', {
shell,
cwd,
});
}
export async function writeToTerminal(
sessionId: string,
data: string
): Promise<void> {
await invoke('write_to_terminal', {
sessionId,
data,
});
}
export async function resizeTerminal(
sessionId: string,
rows: number,
cols: number
): Promise<void> {
await invoke('resize_terminal', {
sessionId,
rows,
cols,
});
}typescript
// src/api/terminal.ts
import { invoke } from '@tauri-apps/api/tauri';
export interface TerminalSession {
id: string;
cwd: string;
shell: string;
}
export async function createTerminalSession(
shell: string,
cwd: string
): Promise<string> {
return await invoke<string>('create_terminal_session', {
shell,
cwd,
});
}
export async function writeToTerminal(
sessionId: string,
data: string
): Promise<void> {
await invoke('write_to_terminal', {
sessionId,
data,
});
}
export async function resizeTerminal(
sessionId: string,
rows: number,
cols: number
): Promise<void> {
await invoke('resize_terminal', {
sessionId,
rows,
cols,
});
}Common Patterns
常见模式
State Management
状态管理
Hermes uses React Context + useReducer:
typescript
// src/state/AppContext.tsx
import React, { createContext, useReducer } from 'react';
interface AppState {
sessions: TerminalSession[];
activeSessionId: string | null;
projects: Project[];
}
type AppAction =
| { type: 'ADD_SESSION'; session: TerminalSession }
| { type: 'REMOVE_SESSION'; sessionId: string }
| { type: 'SET_ACTIVE_SESSION'; sessionId: string };
const appReducer = (state: AppState, action: AppAction): AppState => {
switch (action.type) {
case 'ADD_SESSION':
return {
...state,
sessions: [...state.sessions, action.session],
};
case 'REMOVE_SESSION':
return {
...state,
sessions: state.sessions.filter(s => s.id !== action.sessionId),
};
case 'SET_ACTIVE_SESSION':
return {
...state,
activeSessionId: action.sessionId,
};
default:
return state;
}
};
export const AppContext = createContext<{
state: AppState;
dispatch: React.Dispatch<AppAction>;
} | null>(null);
export const AppProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, {
sessions: [],
activeSessionId: null,
projects: [],
});
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};Hermes使用React Context + useReducer:
typescript
// src/state/AppContext.tsx
import React, { createContext, useReducer } from 'react';
interface AppState {
sessions: TerminalSession[];
activeSessionId: string | null;
projects: Project[];
}
type AppAction =
| { type: 'ADD_SESSION'; session: TerminalSession }
| { type: 'REMOVE_SESSION'; sessionId: string }
| { type: 'SET_ACTIVE_SESSION'; sessionId: string };
const appReducer = (state: AppState, action: AppAction): AppState => {
switch (action.type) {
case 'ADD_SESSION':
return {
...state,
sessions: [...state.sessions, action.session],
};
case 'REMOVE_SESSION':
return {
...state,
sessions: state.sessions.filter(s => s.id !== action.sessionId),
};
case 'SET_ACTIVE_SESSION':
return {
...state,
activeSessionId: action.sessionId,
};
default:
return state;
}
};
export const AppContext = createContext<{
state: AppState;
dispatch: React.Dispatch<AppAction>;
} | null>(null);
export const AppProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, {
sessions: [],
activeSessionId: null,
projects: [],
});
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};Custom Hooks
自定义Hook
typescript
// src/hooks/useTerminal.ts
import { useContext, useCallback } from 'react';
import { AppContext } from '../state/AppContext';
import { createTerminalSession, writeToTerminal } from '../api/terminal';
export const useTerminal = () => {
const context = useContext(AppContext);
if (!context) throw new Error('useTerminal must be used within AppProvider');
const { state, dispatch } = context;
const createSession = useCallback(async (shell: string, cwd: string) => {
const sessionId = await createTerminalSession(shell, cwd);
dispatch({
type: 'ADD_SESSION',
session: { id: sessionId, shell, cwd },
});
dispatch({ type: 'SET_ACTIVE_SESSION', sessionId });
return sessionId;
}, [dispatch]);
const write = useCallback(async (data: string) => {
if (!state.activeSessionId) return;
await writeToTerminal(state.activeSessionId, data);
}, [state.activeSessionId]);
return {
sessions: state.sessions,
activeSessionId: state.activeSessionId,
createSession,
write,
};
};typescript
// src/hooks/useTerminal.ts
import { useContext, useCallback } from 'react';
import { AppContext } from '../state/AppContext';
import { createTerminalSession, writeToTerminal } from '../api/terminal';
export const useTerminal = () => {
const context = useContext(AppContext);
if (!context) throw new Error('useTerminal must be used within AppProvider');
const { state, dispatch } = context;
const createSession = useCallback(async (shell: string, cwd: string) => {
const sessionId = await createTerminalSession(shell, cwd);
dispatch({
type: 'ADD_SESSION',
session: { id: sessionId, shell, cwd },
});
dispatch({ type: 'SET_ACTIVE_SESSION', sessionId });
return sessionId;
}, [dispatch]);
const write = useCallback(async (data: string) => {
if (!state.activeSessionId) return;
await writeToTerminal(state.activeSessionId, data);
}, [state.activeSessionId]);
return {
sessions: state.sessions,
activeSessionId: state.activeSessionId,
createSession,
write,
};
};Project Scanning
项目扫描
rust
// src-tauri/src/project/scanner.rs
use std::path::Path;
use walkdir::WalkDir;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct ProjectContext {
pub languages: Vec<String>,
pub frameworks: Vec<String>,
pub dependencies: Vec<String>,
pub structure: String,
}
pub fn scan_project(path: &Path) -> Result<ProjectContext, Box<dyn std::error::Error>> {
let mut languages = Vec::new();
let mut frameworks = Vec::new();
let mut dependencies = Vec::new();
// Detect package.json (Node.js)
if path.join("package.json").exists() {
languages.push("JavaScript".to_string());
let package_json = std::fs::read_to_string(path.join("package.json"))?;
let package: serde_json::Value = serde_json::from_str(&package_json)?;
if let Some(deps) = package.get("dependencies") {
if deps.get("react").is_some() {
frameworks.push("React".to_string());
}
if deps.get("vue").is_some() {
frameworks.push("Vue".to_string());
}
}
}
// Detect Cargo.toml (Rust)
if path.join("Cargo.toml").exists() {
languages.push("Rust".to_string());
}
// Count files by type for structure summary
let mut file_counts = std::collections::HashMap::new();
for entry in WalkDir::new(path).max_depth(3) {
if let Ok(entry) = entry {
if let Some(ext) = entry.path().extension() {
*file_counts.entry(ext.to_string_lossy().to_string()).or_insert(0) += 1;
}
}
}
Ok(ProjectContext {
languages,
frameworks,
dependencies,
structure: format!("{:?}", file_counts),
})
}rust
// src-tauri/src/project/scanner.rs
use std::path::Path;
use walkdir::WalkDir;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct ProjectContext {
pub languages: Vec<String>,
pub frameworks: Vec<String>,
pub dependencies: Vec<String>,
pub structure: String,
}
pub fn scan_project(path: &Path) -> Result<ProjectContext, Box<dyn std::error::Error>> {
let mut languages = Vec::new();
let mut frameworks = Vec::new();
let mut dependencies = Vec::new();
// Detect package.json (Node.js)
if path.join("package.json").exists() {
languages.push("JavaScript".to_string());
let package_json = std::fs::read_to_string(path.join("package.json"))?;
let package: serde_json::Value = serde_json::from_str(&package_json)?;
if let Some(deps) = package.get("dependencies") {
if deps.get("react").is_some() {
frameworks.push("React".to_string());
}
if deps.get("vue").is_some() {
frameworks.push("Vue".to_string());
}
}
}
// Detect Cargo.toml (Rust)
if path.join("Cargo.toml").exists() {
languages.push("Rust".to_string());
}
// Count files by type for structure summary
let mut file_counts = std::collections::HashMap::new();
for entry in WalkDir::new(path).max_depth(3) {
if let Ok(entry) = entry {
if let Some(ext) = entry.path().extension() {
*file_counts.entry(ext.to_string_lossy().to_string()).or_insert(0) += 1;
}
}
}
Ok(ProjectContext {
languages,
frameworks,
dependencies,
structure: format!("{:?}", file_counts),
})
}Testing
测试
Frontend Tests
前端测试
bash
undefinedbash
undefinedRun all tests
Run all tests
npm run test
npm run test
Run with coverage
Run with coverage
npm run test:coverage
npm run test:coverage
Watch mode
Watch mode
npm run test:watch
```typescript
// src/components/__tests__/Terminal.test.tsx
import { render, screen } from '@testing-library/react';
import { TerminalComponent } from '../Terminal';
describe('TerminalComponent', () => {
it('renders terminal container', () => {
render(<TerminalComponent />);
const container = screen.getByClassName('terminal-container');
expect(container).toBeInTheDocument();
});
});npm run test:watch
```typescript
// src/components/__tests__/Terminal.test.tsx
import { render, screen } from '@testing-library/react';
import { TerminalComponent } from '../Terminal';
describe('TerminalComponent', () => {
it('renders terminal container', () => {
render(<TerminalComponent />);
const container = screen.getByClassName('terminal-container');
expect(container).toBeInTheDocument();
});
});Backend Tests
后端测试
bash
cd src-tauri
cargo test
cargo test -- --nocapture # Show outputrust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pty_session_creation() {
let session = PtySession::new("/bin/bash", "/tmp");
assert!(session.is_ok());
let session = session.unwrap();
assert!(!session.id.is_empty());
}
#[tokio::test]
async fn test_write_to_pty() {
let session = PtySession::new("/bin/bash", "/tmp").unwrap();
let result = session.write(b"echo test\n").await;
assert!(result.is_ok());
}
}bash
cd src-tauri
cargo test
cargo test -- --nocapture # Show outputrust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pty_session_creation() {
let session = PtySession::new("/bin/bash", "/tmp");
assert!(session.is_ok());
let session = session.unwrap();
assert!(!session.id.is_empty());
}
#[tokio::test]
async fn test_write_to_pty() {
let session = PtySession::new("/bin/bash", "/tmp").unwrap();
let result = session.write(b"echo test\n").await;
assert!(result.is_ok());
}
}Troubleshooting
故障排除
Build Issues
构建问题
Error:
webkit2gtk not foundbash
undefined错误:
webkit2gtk not foundbash
undefinedLinux
Linux
sudo apt install libwebkit2gtk-4.1-dev
sudo apt install libwebkit2gtk-4.1-dev
If still failing, check pkg-config
If still failing, check pkg-config
pkg-config --modversion webkit2gtk-4.1
**Error: `Rust toolchain not found`**
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/envError: failures on Windows
node-gypbash
npm install --global windows-build-tools
npm config set msvs_version 2019pkg-config --modversion webkit2gtk-4.1
**错误:`Rust toolchain not found`**
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env错误:Windows上执行失败
node-gypbash
npm install --global windows-build-tools
npm config set msvs_version 2019Runtime Issues
运行时问题
Terminal not rendering
typescript
// Check terminal element is mounted
useEffect(() => {
console.log('Terminal ref:', terminalRef.current);
if (!terminalRef.current) {
console.error('Terminal container not found');
return;
}
// ...
}, []);PTY session hangs
rust
// Add timeout to PTY operations
use tokio::time::{timeout, Duration};
let result = timeout(
Duration::from_secs(5),
session.write(data)
).await;
if result.is_err() {
eprintln!("PTY write timed out");
}Git authentication fails
bash
undefined终端无法渲染
typescript
// Check terminal element is mounted
useEffect(() => {
console.log('Terminal ref:', terminalRef.current);
if (!terminalRef.current) {
console.error('Terminal container not found');
return;
}
// ...
}, []);PTY会话挂起
rust
// Add timeout to PTY operations
use tokio::time::{timeout, Duration};
let result = timeout(
Duration::from_secs(5),
session.write(data)
).await;
if result.is_err() {
eprintln!("PTY write timed out");
}Git认证失败
bash
undefinedCheck SSH agent
Check SSH agent
ssh-add -l
ssh-add -l
Or use Git Credential Manager
Or use Git Credential Manager
git config --global credential.helper manager
git config --global credential.helper manager
Set environment variable in Hermes settings
Set environment variable in Hermes settings
GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa"
GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa"
undefinedundefinedPerformance Issues
性能问题
High CPU usage
bash
undefinedCPU占用过高
bash
undefinedCheck terminal rendering settings
Check terminal rendering settings
Reduce font size or disable ligatures
Reduce font size or disable ligatures
Limit scrollback buffer size in settings
Limit scrollback buffer size in settings
**Memory leaks**
```typescript
// Ensure proper cleanup in useEffect
useEffect(() => {
const term = new Terminal();
// ...
return () => {
term.dispose(); // Critical!
};
}, []);
**内存泄漏**
```typescript
// Ensure proper cleanup in useEffect
useEffect(() => {
const term = new Terminal();
// ...
return () => {
term.dispose(); // Critical!
};
}, []);Environment Variables
环境变量
bash
undefinedbash
undefinedClaude API configuration
Claude API configuration
export ANTHROPIC_API_KEY=sk-ant-...
export ANTHROPIC_API_KEY=sk-ant-...
Custom shell
Custom shell
export HERMES_SHELL=/bin/zsh
export HERMES_SHELL=/bin/zsh
Debug logging
Debug logging
export RUST_LOG=debug
export HERMES_DEBUG=1
export RUST_LOG=debug
export HERMES_DEBUG=1
Custom config directory
Custom config directory
export HERMES_CONFIG_DIR=$HOME/.config/hermes
undefinedexport HERMES_CONFIG_DIR=$HOME/.config/hermes
undefinedContributing
贡献指南
bash
undefinedbash
undefinedFork and clone
Fork and clone
git clone https://github.com/YOUR_USERNAME/hermes-ide.git
cd hermes-ide
git clone https://github.com/YOUR_USERNAME/hermes-ide.git
cd hermes-ide
Create feature branch
Create feature branch
git checkout -b feature/my-feature
git checkout -b feature/my-feature
Make changes and test
Make changes and test
npm run tauri dev
npm run test
cd src-tauri && cargo test
npm run tauri dev
npm run test
cd src-tauri && cargo test
Type check
Type check
npx tsc --noEmit
npx tsc --noEmit
Commit (follows conventional commits)
Commit (follows conventional commits)
git commit -m "feat: add new terminal feature"
git commit -m "feat: add new terminal feature"
Push and open PR
Push and open PR
git push origin feature/my-feature
git push origin feature/my-feature
Sign CLA when prompted
Sign CLA when prompted
Read [CONTRIBUTING.md](https://github.com/hermes-hq/hermes-ide/blob/main/CONTRIBUTING.md) and [DESIGN_PRINCIPLES.md](https://github.com/hermes-hq/hermes-ide/blob/main/DESIGN_PRINCIPLES.md) before contributing.
在贡献前请阅读[CONTRIBUTING.md](https://github.com/hermes-hq/hermes-ide/blob/main/CONTRIBUTING.md)和[DESIGN_PRINCIPLES.md](https://github.com/hermes-hq/hermes-ide/blob/main/DESIGN_PRINCIPLES.md)。Resources
资源
- Documentation: https://github.com/hermes-hq/hermes-ide#readme
- Architecture: https://github.com/hermes-hq/hermes-ide/blob/main/ARCHITECTURE.md
- Discord: https://discord.gg/vMQXSTY6BM
- Discussions: https://github.com/hermes-hq/hermes-ide/discussions
- Changelog: https://hermes-ide.com/changelog