hermes-ide-terminal

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hermes IDE Terminal Skill

Hermes IDE终端技能

Skill by ara.so — Hermes Skills collection.
ara.so提供的技能 — Hermes技能合集。

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
undefined

Choose your platform: macOS (Intel/Apple Silicon), Windows, Linux (.deb/.AppImage/.rpm)

Choose your platform: macOS (Intel/Apple Silicon), Windows, Linux (.deb/.AppImage/.rpm)

undefined
undefined

Build from Source

从源码构建

Prerequisites:
bash
undefined
前置依赖:
bash
undefined

Check versions

Check versions

node --version # 18+ rustc --version # 1.70+

**Platform-specific dependencies:**

```bash
node --version # 18+ rustc --version # 1.70+

**平台专属依赖:**

```bash

Linux (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 build

Architecture

架构

Hermes IDE uses a layered architecture:
React Frontend (TypeScript) ← Tauri IPC Bridge → Rust Backend
     UI & State                                   PTY, DB, Scanner
Key directories:
  • src/
    - React/TypeScript frontend (components, hooks, state, terminal)
  • src-tauri/
    - Rust backend (PTY management, SQLite, project scanning)
  • src/api/
    - Tauri IPC command wrappers
  • src/terminal/
    - Terminal pool & AI intelligence engine
Hermes IDE采用分层架构:
React Frontend (TypeScript) ← Tauri IPC Bridge → Rust Backend
     UI & State                                   PTY, DB, Scanner
核心目录:
  • src/
    - React/TypeScript前端(组件、Hook、状态、终端)
  • src-tauri/
    - Rust后端(PTY管理、SQLite、项目扫描)
  • src/api/
    - Tauri IPC命令封装
  • src/terminal/
    - 终端池与AI智能引擎

Configuration

配置

Claude Agent Setup

Claude代理设置

Hermes uses your existing
claude
CLI authentication:
bash
undefined
Hermes使用您已有的
claude
CLI认证:
bash
undefined

Install 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 restarts
claude 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 info
Hermes会自动扫描项目以构建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 info

Terminal 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+W
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+W

Git Integration

Git集成

Access via sidebar panel:
bash
undefined
通过侧边栏面板访问:
bash
undefined

All 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
undefined
git add . git commit -m "feat: add new feature" git push origin main
undefined

AI Ghost-Text Suggestions

AI幽灵文本建议

Hermes provides real-time command suggestions:
bash
undefined
Hermes提供实时命令建议:
bash
undefined

Start 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

undefined
undefined

Prompt Composer

提示编辑器

Use natural language for autonomous execution:
bash
undefined
使用自然语言执行自动化操作:
bash
undefined

Open 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

undefined
undefined

Command Palette

命令面板

Access all features quickly:
bash
undefined
快速访问所有功能:
bash
undefined

Open: 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

undefined
undefined

Development 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
undefined
bash
undefined

Run 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 output
rust
#[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 output
rust
#[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 found
bash
undefined
错误:
webkit2gtk not found
bash
undefined

Linux

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/env
Error:
node-gyp
failures on Windows
bash
npm install --global windows-build-tools
npm config set msvs_version 2019
pkg-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-gyp
执行失败
bash
npm install --global windows-build-tools
npm config set msvs_version 2019

Runtime 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
undefined

Check 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"

undefined
undefined

Performance Issues

性能问题

High CPU usage
bash
undefined
CPU占用过高
bash
undefined

Check 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
undefined
bash
undefined

Claude 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
undefined
export HERMES_CONFIG_DIR=$HOME/.config/hermes
undefined

Contributing

贡献指南

bash
undefined
bash
undefined

Fork and clone

Fork and clone

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

资源