Build a cross-platform desktop terminal application inspired by the interaction model of Fig, but implemented as a fully local desktop application.
The application must:
- Run without a backend server owned by the application.
- Execute shell commands directly on the user's machine.
- Provide a real persistent terminal using PTY.
- Support multiple terminal tabs.
- Provide a modern terminal UI.
- Provide command autocomplete/suggestions.
- Provide Git-aware and filesystem-aware suggestions.
- Be architected so AI assistance can be added later.
- Work on macOS, Linux, and Windows where technically supported.
The application should feel like a modern developer terminal rather than a traditional terminal emulator.
- React
- TypeScript
- Vite
- Tailwind CSS
- xterm.js
- xterm-addon-fit
- xterm-addon-web-links
- Tauri 2.x
- Rust
Rust is responsible for:
- PTY creation
- Shell process management
- Terminal input/output
- Filesystem operations
- Git operations
- Process management
- OS integration
- Application configuration
Use Tauri commands/events.
Do not create an HTTP server between the frontend and Rust.
Architecture:
React
│
│ Tauri IPC
▼
Rust
│
├── PTY
├── Filesystem
├── Git
└── Process management
The application must NOT depend on a remote backend for core terminal functionality.
This must work while completely offline:
Create terminal
↓
Start shell
↓
Run commands
↓
Read filesystem
↓
Run git
↓
Autocomplete commands
No API server should be required.
AI integration must be an optional layer.
figy-term/
├── src/
│
│ │ ├── Sidebar/
│ │ │ ├── Sidebar.tsx
│ │ │ └── WorkspaceTree.tsx
│ │ │
│ │ ├── CommandPalette/
│ │ │ └── CommandPalette.tsx
│ │ │
│ │ └── Settings/
│ │ └── Settings.tsx
│ │
│ ├── hooks/
│ │ ├── useTerminal.ts
│ │ ├── useTerminalTabs.ts
│ │ └── useAutocomplete.ts
│ │
│ ├── services/
│ │ ├── terminal.ts
│ │ ├── filesystem.ts
│ │ ├── git.ts
│ │ ├── autocomplete.ts
│ │ └── settings.ts
│ │
│ ├── stores/
│ │ ├── terminalStore.ts
│ │ ├── settingsStore.ts
│ │ └── workspaceStore.ts
│ │
│ ├── types/
│ │ ├── terminal.ts
│ │ ├── autocomplete.ts
│ │ └── git.ts
│ │
│ ├── App.tsx
│ └── main.tsx
│
├── src-tauri/
│
│ ├── src/
│ │
│ │ ├── main.rs
│ │ │
│ │ ├── terminal/
│ │ │ ├── mod.rs
│ │ │ ├── pty.rs
│ │ │ ├── session.rs
│ │ │ └── manager.rs
│ │ │
│ │ ├── filesystem/
│ │ │ ├── mod.rs
│ │ │ └── operations.rs
│ │ │
│ │ ├── git/
│ │ │ ├── mod.rs
│ │ │ └── operations.rs
│ │ │
│ │ ├── commands/
│ │ │ ├── mod.rs
│ │ │ └── terminal.rs
│ │ │
│ │ └── state/
│ │ └── app_state.rs
│ │
│ ├── Cargo.toml
│ └── tauri.conf.json
│
├── package.json
└── README.md
The terminal must use a real PTY.
Do NOT implement terminal execution using independent commands such as:
Command::new("ls")for each command.
A terminal tab must maintain a persistent shell process.
Example:
Terminal Tab
│
▼
PTY Session
│
▼
Shell Process
│
├── stdin
├── stdout
└── stderr
Supported shells:
Detect in this order:
$SHELL
/bin/zsh
/bin/bash
/bin/fish
Detect:
PowerShell
cmd.exe
The shell must remain alive for the lifetime of the terminal tab.
Each terminal tab has a unique session ID.
Example:
interface TerminalSession {
id: string;
shell: string;
cwd: string;
title: string;
createdAt: number;
status: "running" | "exited";
}Rust should maintain:
HashMap<SessionId, PtySession>
Each PTY session must support:
create_session
write_to_session
resize_session
close_session
get_session_info
Terminal output should be emitted to the frontend through Tauri events.
Example event:
terminal-output
Payload:
{
"sessionId": "abc123",
"data": "hello world\n"
}The UI should resemble a modern developer terminal.
Layout:
┌──────────────────────────────────────────────────────────┐
│ Terminal + ⌘K ⚙ │
├──────────────────────────────────────────────────────────┤
│ Tab 1 Tab 2 Tab 3 + │
├──────────────────────────────────────────────────────────┤
│ │
│ ~/projects/my-app │
│ $ git status │
│ │
│ On branch main │
│ Your branch is up to date. │
│ │
│ $ npm run dev │
│ │
│ > my-app@1.0.0 dev │
│ > vite │
│ │
│ Local: http://localhost:5173 │
│ │
│ $ _ │
│ │
└──────────────────────────────────────────────────────────┘
The terminal rendering itself must use xterm.js.
Do not implement terminal ANSI rendering manually.
Users must be able to:
- Create tab
- Close tab
- Switch tab
- Rename tab
- Duplicate tab
- Restart terminal
- Split terminal in the future
Each tab owns exactly one PTY session.
Example:
Tab 1 → PTY 001 → zsh
Tab 2 → PTY 002 → zsh
Tab 3 → PTY 003 → zsh
Closing a tab must terminate its PTY process.
Each terminal has its own current working directory.
When the shell executes:
cd ~/projectsthe shell itself controls the working directory.
The frontend should not attempt to emulate shell state.
The application may separately detect the current directory for UI purposes.
Display:
~/projects/my-app
in the terminal header when possible.
When the application window changes size:
React
↓
xterm.js dimensions
↓
Tauri IPC
↓
Rust PTY resize
The PTY must receive updated rows/columns.
Do not restart the shell when resizing.
Required shortcuts:
Cmd/Ctrl + T
New terminal
Cmd/Ctrl + W
Close terminal
Cmd/Ctrl + Shift + T
Reopen terminal
Cmd/Ctrl + K
Command palette
Cmd/Ctrl + Shift + P
Command palette
Cmd/Ctrl + Tab
Next terminal
Cmd/Ctrl + Shift + Tab
Previous terminal
Terminal-native shortcuts such as:
Ctrl+C
Ctrl+D
Ctrl+Z
Ctrl+L
Ctrl+R
must continue to work normally.
Do not intercept them unnecessarily.
Autocomplete is a separate subsystem.
It must not be tightly coupled to the PTY.
Architecture:
User input
↓
Autocomplete Engine
↓
Context Analyzer
↓
Suggestion Providers
↓
Ranker
↓
Suggestions
Providers:
CommandProvider
FilesystemProvider
GitProvider
EnvironmentProvider
HistoryProvider
Future:
DockerProvider
KubernetesProvider
AWSProvider
TerraformProvider
AIProvider
Create a local command specification format.
Example:
{
"name": "git",
"description": "Distributed version control system",
"subcommands": [
{
"name": "checkout",
"description": "Switch branches or restore files"
},
{
"name": "commit",
"description": "Record changes to the repository"
},
{
"name": "branch",
"description": "List, create, or delete branches"
}
]
}The system must support:
command
subcommand
arguments
options
descriptions
examples
Example:
git checkout
Options:
-b
Create a new branch
-B
Create/reset branch
--detach
Detach HEAD
When the user types:
git che
display:
┌─────────────────────────────────────────┐
│ git checkout │
│ Switch branches or restore files │
├─────────────────────────────────────────┤
│ git cherry-pick │
│ Apply changes from existing commits │
├─────────────────────────────────────────┤
│ git check-attr │
└─────────────────────────────────────────┘
Suggestions should appear near the cursor.
Keyboard:
↑ / ↓
Navigate
Tab
Accept
Enter
Accept
Esc
Close
Autocomplete must never prevent normal shell input.
When the user types:
cd ~/prosuggest:
~/projects/
~/production/
~/programming/
When typing:
cat ./src/suggest files/directories from the current path.
Filesystem suggestions must be asynchronous.
Do not block the UI while scanning directories.
Respect filesystem permissions.
Detect whether the current directory is a Git repository.
For example:
git status --short --branchUse Git information for suggestions.
Example:
git checkout
suggest:
main
develop
feature/auth
feature/payment
For:
git switch
suggest local branches.
For:
git add
suggest modified/untracked files.
Git integration should be implemented as a separate Rust module.
Maintain local command history.
History should include:
interface CommandHistoryEntry {
command: string;
cwd: string;
timestamp: number;
}History should be searchable.
Example:
Cmd + R
Search commands...
docker compose up
docker compose up -d
kubectl get pods
git checkout develop
Do not store passwords or obvious secret values.
History storage should be local.
Implement a global command palette.
Shortcut:
Cmd/Ctrl + K
Example:
┌────────────────────────────────────────────┐
│ Search commands... │
├────────────────────────────────────────────┤
│ New Terminal │
│ Close Terminal │
│ Split Terminal │
│ Search History │
│ Open Settings │
│ Change Theme │
│ Clear Terminal │
└────────────────────────────────────────────┘
Commands should be searchable.
Settings should be local.
Initial settings:
interface Settings {
theme: string;
fontFamily: string;
fontSize: number;
lineHeight: number;
cursorStyle: "block" | "underline" | "bar";
cursorBlink: boolean;
scrollback: number;
shell: string | null;
}Store settings using Tauri's local application storage.
Implement theme support.
At minimum:
Dark
Light
System
The architecture should allow custom terminal themes later.
Security is critical because this application executes arbitrary shell commands.
Rules:
- Never execute shell commands from the frontend directly.
- All process execution must happen in Rust.
- Validate Tauri IPC inputs.
- Do not expose arbitrary filesystem APIs unnecessarily.
- Use Tauri capabilities/permissions correctly.
- Do not create an HTTP server.
- Do not expose a localhost API unless explicitly required later.
- Do not send terminal output anywhere by default.
- AI integrations must be opt-in.
- API keys must never be committed to source control.
AI must be implemented as an optional provider layer.
AIProvider
│
├── OpenAIProvider
├── AnthropicProvider
├── GeminiProvider
└── OllamaProvider
Interface:
interface AIProvider {
generateCommand(context: CommandContext): Promise<string>;
explainCommand(command: string): Promise<string>;
explainError(context: TerminalErrorContext): Promise<string>;
}AI should receive only the minimum required context.
Example:
User:
"Why did my Docker build fail?"
Context:
command:
docker build .
exitCode:
1
output:
...
The AI provider should be called directly from the desktop application or through a user-configured provider.
No application-owned backend is required.
Future feature:
User opens AI command palette:
┌────────────────────────────────────────────┐
│ What do you want to do? │
│ │
│ deploy my current app to kubernetes │
└────────────────────────────────────────────┘
AI returns:
kubectl apply -f deployment.yamlBefore execution:
┌────────────────────────────────────────────┐
│ Proposed command │
│ │
│ kubectl apply -f deployment.yaml │
│ │
│ [Cancel] [Run Command] │
└────────────────────────────────────────────┘
Never execute AI-generated commands automatically without explicit user confirmation.
The application must remain useful with zero network access.
Offline functionality:
Terminal ✓
PTY ✓
Shell ✓
Filesystem ✓
Git ✓
Autocomplete ✓
History ✓
Command palette ✓
Themes ✓
Settings ✓
Optional network functionality:
Cloud AI optional
Updates optional
Telemetry optional
No telemetry should be enabled by default.
The terminal must remain responsive while commands produce large amounts of output.
Requirements:
- Do not render every output chunk through React state.
- xterm.js should handle terminal rendering directly.
- Rust should stream PTY output efficiently.
- Avoid unnecessary serialization.
- Autocomplete must not block terminal input.
- Filesystem scanning must be asynchronous.
- Git operations must not block the UI.
- Large terminal output must not freeze the application.
Target:
Typing latency:
< 16 ms perceived UI latency
Autocomplete:
< 100 ms for normal local suggestions
Terminal:
Smooth while handling large output
The first implementation should ONLY include:
Tauri
React
TypeScript
Tailwind
xterm.js
Rust
PTY
Features:
- Application window
- Terminal UI
- Create terminal
- Persistent shell
- Input/output
- Resize
- Multiple tabs
- Close tabs
- Basic keyboard shortcuts
- macOS/Linux shell detection
- Windows shell detection
- Clean application shutdown
Do NOT implement AI in Phase 1.
Do NOT implement autocomplete in Phase 1.
The goal is to first prove that the PTY architecture is correct.
Add:
Autocomplete
Filesystem suggestions
Command history
Git integration
Command specifications
Command palette
Add:
AI provider abstraction
OpenAI
Anthropic
Gemini
Ollama
Command generation
Command explanation
Error explanation
Add advanced developer features:
Split panes
Workspaces
Project detection
Docker integration
Kubernetes integration
AWS integration
Terraform integration
SSH sessions
Remote terminals
AI agent
SSH/remote terminal support should remain separate from the local PTY implementation.
The MVP is complete when:
- Application launches successfully.
- A terminal automatically opens.
- User can type shell commands.
- Commands execute in the real local shell.
cdpersists.- Environment variables persist.
- Ctrl+C works.
- Ctrl+D works.
- Ctrl+Z works.
- Terminal resize works.
- ANSI colors work.
- Interactive applications work where supported.
Examples:
top
vim
python
ssh
git
npm- Multiple terminal sessions can run simultaneously.
- Each tab has an independent shell.
- Closing a tab terminates its process.
- Switching tabs preserves terminal state.
- Terminal is responsive.
- No unnecessary React rerendering occurs for terminal output.
- Dark theme works.
- Keyboard navigation works.
- No backend server exists.
- No HTTP API is required.
- Shell execution happens in Rust.
- PTY management happens in Rust.
- Frontend communicates with Rust using Tauri IPC.
- Core functionality works offline.
When implementing this project:
- Start with Phase 1 only.
- Do not implement future features prematurely.
- Do not create a backend server.
- Do not use Node.js for shell execution.
- Use Rust for process/PTY management.
- Use xterm.js for terminal rendering.
- Keep terminal state separate from React rendering.
- Keep PTY management isolated from UI code.
- Use strongly typed TypeScript interfaces.
- Use strongly typed Rust structures.
- Keep modules small and focused.
- Add error handling for all IPC operations.
- Do not silently swallow Rust errors.
- Add logging for PTY lifecycle events.
- Ensure child processes are cleaned up when the application exits.
- Do not add AI until Phase 1 is stable.
- Do not add unnecessary dependencies.
- Prefer native Tauri functionality over introducing a server.
- Keep the architecture extensible for autocomplete providers.
- Write a README explaining how the architecture works.
Before implementing a feature, inspect the existing project structure and avoid duplicating functionality.
After each major implementation step, run:
npm run buildand the appropriate Tauri development/build command.
Fix compilation/type errors before moving to the next feature.
A developer should be able to clone the repository, install dependencies, and run:
npm install
npm run tauri devand receive a desktop application containing a working terminal.
The resulting application should be a real local terminal, not a web-based terminal simulation.
The architecture must be ready for the next phase:
PTY
↓
Terminal
↓
Autocomplete
↓
Command Intelligence
↓
AI
The project should prioritize correctness of the PTY/terminal foundation over visual polish during Phase 1.