A safe, simple, extensible, and fast agent harness
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rename `ein-tui` to `ein`. Move from `crates/` directory to top level project directory.

+54 -422
+8 -8
CLAUDE.md
··· 4 4 5 5 ## What is Ein 6 6 7 - Ein is a Rust-based AI agent framework with a client-server architecture. A gRPC server drives an LLM agent loop and executes tools implemented as pluggable WASM modules. Multiple model client plugins are supported (OpenRouter, Anthropic, OpenAI, Ollama). A terminal UI client (`ein-tui`) connects to the server and provides an interactive chat interface. Sessions are persisted to SQLite so conversations can be resumed across reconnects. 7 + Ein is a Rust-based AI agent framework with a client-server architecture. A gRPC server drives an LLM agent loop and executes tools implemented as pluggable WASM modules. Multiple model client plugins are supported (OpenRouter, Anthropic, OpenAI, Ollama). A terminal UI client (`ein`) connects to the server and provides an interactive chat interface. Sessions are persisted to SQLite so conversations can be resumed across reconnects. 8 8 9 9 ## Setup 10 10 11 11 ```bash 12 12 rustup target add wasm32-wasip2 13 13 cargo build # Build all crates 14 - cargo build -p ein-tui # Build just the TUI client 14 + cargo build -p ein # Build just the TUI client 15 15 cargo build -p eind # Build just the server 16 16 ``` 17 17 ··· 45 45 cargo run --bin eind 46 46 47 47 # Terminal 2 — start the TUI (connects to localhost:50051 by default) 48 - cargo run --bin ein-tui 48 + cargo run --bin ein 49 49 50 50 # Optional: connect to a non-default server address 51 - cargo run -p ein-tui -- http://my-server:50051 51 + cargo run -p ein -- http://my-server:50051 52 52 ``` 53 53 54 54 The server creates `~/.ein/sessions.db` on first run to persist session history. ··· 59 59 60 60 ``` 61 61 ┌─────────────────────────────┐ ┌──────────────────────────────┐ 62 - │ ein-tui │ gRPC │ eind │ 62 + │ ein │ gRPC │ eind │ 63 63 │ │ (proto) │ │ 64 64 │ Ratatui terminal UI │◄────────►│ Agent loop + tool executor │ 65 65 │ Keyboard / render loop │ │ WASM plugin host │ ··· 105 105 106 106 Database migrations live in `eind/migrations/`. 107 107 108 - ### Client config (`crates/ein-tui/src/config.rs`) 108 + ### Client config (`ein/src/config.rs`) 109 109 110 110 `ClientConfig` is loaded from (or created at) `~/.ein/config.json` on TUI startup. Structure mirrors `SessionConfig`. At startup the TUI shows a floating modal asking whether to add the current working directory to `allowed_paths` for that session; this is never persisted to `config.json`. 111 111 ··· 129 129 130 130 **Plugin loading** (`src/tools.rs`): scans the plugin directory for `.wasm` files and instantiates each as a Wasmtime component. The filename stem (e.g. `ein_bash`) is used as the plugin's config identity to look up its entry in `plugin_configs`; global `allowed_paths`/`allowed_hosts` are merged with any plugin-specific overrides before the WASI context is built. After instantiation, `name()`/`schema()` are called to get the display name (e.g. `"Bash"`) and tool schema exposed to the model. In debug mode both tool and model client plugins are loaded from `./target/wasm32-wasip2/debug/`; in release mode tool plugins come from `~/.ein/plugins/tools/` and model client plugins from `~/.ein/plugins/model_clients/`. 131 131 132 - ### TUI (`crates/ein-tui/`) 132 + ### TUI (`ein/`) 133 133 134 - Six source files under `crates/ein-tui/src/`: 134 + Six source files under `ein/src/`: 135 135 136 136 | File | Role | 137 137 |------|------|
+26 -26
Cargo.lock
··· 971 971 checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 972 972 973 973 [[package]] 974 + name = "ein" 975 + version = "0.1.8" 976 + dependencies = [ 977 + "anyhow", 978 + "chrono", 979 + "clap", 980 + "crossterm", 981 + "dirs", 982 + "ein-proto", 983 + "notify", 984 + "ratatui", 985 + "reqwest", 986 + "serde", 987 + "serde_json", 988 + "syntect", 989 + "tar", 990 + "tokio", 991 + "tokio-stream", 992 + "tonic", 993 + "tracing", 994 + "tracing-appender", 995 + "tracing-subscriber", 996 + "xz2", 997 + ] 998 + 999 + [[package]] 974 1000 name = "ein-agent" 975 1001 version = "0.1.8" 976 1002 dependencies = [ ··· 1000 1026 "prost", 1001 1027 "tonic", 1002 1028 "tonic-build", 1003 - ] 1004 - 1005 - [[package]] 1006 - name = "ein-tui" 1007 - version = "0.1.8" 1008 - dependencies = [ 1009 - "anyhow", 1010 - "chrono", 1011 - "clap", 1012 - "crossterm", 1013 - "dirs", 1014 - "ein-proto", 1015 - "notify", 1016 - "ratatui", 1017 - "reqwest", 1018 - "serde", 1019 - "serde_json", 1020 - "syntect", 1021 - "tar", 1022 - "tokio", 1023 - "tokio-stream", 1024 - "tonic", 1025 - "tracing", 1026 - "tracing-appender", 1027 - "tracing-subscriber", 1028 - "xz2", 1029 1029 ] 1030 1030 1031 1031 [[package]]
+3 -3
Cargo.toml
··· 1 1 [workspace] 2 2 members = [ 3 3 "eind", 4 - "crates/ein-tui", 4 + "ein", 5 5 "crates/ein-proto", 6 6 "crates/ein-agent", 7 7 "crates/ein-core", ··· 9 9 ] 10 10 default-members = [ 11 11 "eind", 12 - "crates/ein-tui", 12 + "ein", 13 13 "crates/ein-proto", 14 14 "crates/ein-agent", 15 15 "crates/ein-core", ··· 35 35 "x86_64-unknown-linux-gnu", 36 36 ] 37 37 pr-run-mode = "plan" 38 - members = ["crates/ein-tui"] 38 + members = ["ein"] 39 39 # Allow manual edits to the generated workflow (we add a protoc install step). 40 40 allow-dirty = ["ci"] 41 41
+10 -10
README.md
··· 4 4 5 5 ``` 6 6 ┌─────────────────────────────┐ ┌──────────────────────────────┐ 7 - │ ein-tui │ gRPC │ eind │ 7 + │ ein │ gRPC │ eind │ 8 8 │ │ (proto) │ │ 9 9 │ Ratatui terminal UI │◄────────►│ Agent loop + tool executor │ 10 10 │ Session picker on startup │ │ WASM plugin host │ ··· 23 23 cargo binstall --git https://github.com/mstallmo/ein ein 24 24 ``` 25 25 26 - This installs both `ein-tui` (terminal UI) and `eind` (gRPC agent server). You can also install them individually: 26 + This installs both `ein` (terminal UI) and `eind` (gRPC agent server). You can also install them individually: 27 27 28 28 ```bash 29 - cargo binstall --git https://github.com/mstallmo/ein ein-tui 29 + cargo binstall --git https://github.com/mstallmo/ein ein 30 30 cargo binstall --git https://github.com/mstallmo/ein eind 31 31 ``` 32 32 ··· 134 134 Start the TUI client in another: 135 135 136 136 ```bash 137 - cargo run --bin ein-tui 137 + cargo run --bin ein 138 138 ``` 139 139 140 140 The TUI connects to `localhost:50051` by default. To connect to a different address: 141 141 142 142 ```bash 143 - cargo run --bin ein-tui -- http://my-server:50051 143 + cargo run --bin ein -- http://my-server:50051 144 144 ``` 145 145 146 146 To enable debug logging to `~/.ein/tui.log`: 147 147 148 148 ```bash 149 - cargo run --bin ein-tui -- --debug 149 + cargo run --bin ein -- --debug 150 150 ``` 151 151 152 152 On first connection a **session picker** modal appears. Use `↑`/`↓` to navigate, `Enter` to select: ··· 270 270 ``` 271 271 crates/ 272 272 ein-proto/ Protocol Buffer definitions (gRPC service + message types) 273 - eind/ gRPC server — agent loop, WASM plugin host, session persistence 274 - ein-tui/ Terminal UI client 273 + ein/ Terminal UI client 274 + eind/ gRPC server — agent loop, WASM plugin host, session persistence 275 275 packages/ 276 276 ein_tool/ WASM tool plugin interface (ToolPlugin trait, ToolDef, syscalls) 277 277 ein_bash/ Bash tool plugin ··· 302 302 303 303 Sessions are persisted to `~/.ein/sessions.db`. Supplying a previously assigned `session_id` in `SessionConfig` causes the server to restore the full conversation history and resume as if the session never disconnected. 304 304 305 - ### TUI modules (`crates/ein-tui/src/`) 305 + ### TUI modules (`ein/src/`) 306 306 307 307 | File | Role | 308 308 |------|------| ··· 329 329 330 330 ## Releasing 331 331 332 - Releases are fully automated via CI using [cargo-dist](https://axodotdev.github.io/cargo-dist/). Only the `crates/ein` meta-package is distributed — it includes both the `ein-tui` and `eind` binaries. 332 + Releases are fully automated via CI using [cargo-dist](https://axodotdev.github.io/cargo-dist/). 333 333 334 334 **1. Bump the version** 335 335
+3 -3
crates/ein-tui/Cargo.toml ein/Cargo.toml
··· 1 1 [package] 2 - name = "ein-tui" 2 + name = "ein" 3 3 version.workspace = true 4 4 edition.workspace = true 5 5 authors.workspace = true ··· 8 8 homepage.workspace = true 9 9 10 10 [lib] 11 - name = "ein_tui" 11 + name = "ein" 12 12 path = "src/lib.rs" 13 13 14 14 [[bin]] ··· 23 23 crossterm = { version = "0.28", features = ["event-stream"] } 24 24 notify = "6" 25 25 dirs = "6.0.0" 26 - ein-proto = { path = "../ein-proto" } 26 + ein-proto = { path = "../crates/ein-proto" } 27 27 ratatui = { version = "0.29", features = ["unstable-rendered-line-info"] } 28 28 serde = { workspace = true } 29 29 serde_json = { workspace = true }
crates/ein-tui/src/app.rs ein/src/app.rs
-368
crates/ein-tui/src/bootstrap.rs
··· 1 - // SPDX-License-Identifier: Apache-2.0 2 - // Copyright 2026 Mason Stallmo 3 - 4 - //! Bootstrap logic: downloads `eind` on first run and registers it as a 5 - //! system service (macOS LaunchAgent or Linux systemd user service). 6 - 7 - // These items are only called from the #[cfg(not(debug_assertions))] block in 8 - // lib.rs, so they appear unused in debug builds. That's intentional. 9 - #![cfg_attr(debug_assertions, allow(dead_code))] 10 - 11 - use anyhow::{Context, Result}; 12 - use std::{ 13 - io, 14 - os::unix::fs::PermissionsExt, 15 - path::{Path, PathBuf}, 16 - }; 17 - use tar::Archive; 18 - use tokio::{fs, process::Command, task}; 19 - use xz2::read::XzDecoder; 20 - 21 - const GITHUB_REPO: &str = "mstallmo/ein"; 22 - 23 - /// Path where `ein` installs the server binary: `~/.ein/bin/eind`. 24 - pub fn server_bin_path() -> PathBuf { 25 - dirs::home_dir() 26 - .expect("home directory not found") 27 - .join(".ein") 28 - .join("bin") 29 - .join("eind") 30 - } 31 - 32 - /// Compile-time target triple used to select the right GitHub release asset. 33 - pub fn target_triple() -> &'static str { 34 - #[cfg(all(target_os = "macos", target_arch = "aarch64"))] 35 - return "aarch64-apple-darwin"; 36 - #[cfg(all(target_os = "macos", target_arch = "x86_64"))] 37 - return "x86_64-apple-darwin"; 38 - #[cfg(all(target_os = "linux", target_arch = "aarch64"))] 39 - return "aarch64-unknown-linux-gnu"; 40 - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 41 - return "x86_64-unknown-linux-gnu"; 42 - #[allow(unreachable_code)] 43 - "" 44 - } 45 - 46 - /// Downloads the `eind` binary for the current platform from GitHub 47 - /// releases and writes it to `~/.ein/bin/eind` with executable permissions. 48 - pub async fn download_server(version: &str) -> Result<()> { 49 - let ver = version.trim_start_matches('v'); 50 - let tag = format!("v{ver}"); 51 - let triple = target_triple(); 52 - // cargo-dist names archives as "{package}-{triple}.tar.xz" (no version in filename). 53 - let archive_name = format!("eind-{triple}.tar.xz"); 54 - let url = format!("https://github.com/{GITHUB_REPO}/releases/download/{tag}/{archive_name}"); 55 - 56 - let dest = server_bin_path(); 57 - fs::create_dir_all(dest.parent().unwrap()) 58 - .await 59 - .context("failed to create ~/.ein/bin")?; 60 - 61 - println!("Downloading {url}..."); 62 - 63 - let response = reqwest::get(&url) 64 - .await 65 - .with_context(|| format!("failed to fetch {url}"))?; 66 - 67 - if !response.status().is_success() { 68 - anyhow::bail!("download failed: HTTP {}", response.status()); 69 - } 70 - 71 - let bytes = response 72 - .bytes() 73 - .await 74 - .context("failed to read response body")?; 75 - 76 - let dest_clone = dest.clone(); 77 - task::spawn_blocking(move || extract_server(&bytes, &dest_clone)) 78 - .await 79 - .context("extraction task panicked")??; 80 - 81 - // Make the binary executable. 82 - let mut perms = fs::metadata(&dest).await?.permissions(); 83 - perms.set_mode(0o755); 84 - fs::set_permissions(&dest, perms).await?; 85 - 86 - println!("eind installed to {}", dest.display()); 87 - Ok(()) 88 - } 89 - 90 - /// Extracts the `eind` binary from a tar.xz archive into `dest`. 91 - fn extract_server(bytes: &[u8], dest: &Path) -> Result<()> { 92 - let xz = XzDecoder::new(io::Cursor::new(bytes)); 93 - let mut archive = Archive::new(xz); 94 - 95 - for entry in archive 96 - .entries() 97 - .context("failed to read archive entries")? 98 - { 99 - let mut entry = entry.context("corrupt archive entry")?; 100 - let entry_path = entry.path().context("entry has no path")?; 101 - 102 - // The archive contains exactly one file: the `eind` binary. 103 - // Accept it regardless of any leading directory component. 104 - let file_name = entry_path 105 - .file_name() 106 - .and_then(|n| n.to_str()) 107 - .unwrap_or(""); 108 - 109 - if file_name == "eind" { 110 - let mut file = std::fs::File::create(dest) 111 - .with_context(|| format!("failed to create {}", dest.display()))?; 112 - io::copy(&mut entry, &mut file).context("failed to write eind")?; 113 - return Ok(()); 114 - } 115 - } 116 - 117 - anyhow::bail!("eind binary not found in archive") 118 - } 119 - 120 - // --------------------------------------------------------------------------- 121 - // Service registration 122 - // --------------------------------------------------------------------------- 123 - 124 - /// Ensures `eind` is registered as a system service. 125 - /// 126 - /// On macOS, installs a LaunchAgent plist and loads it. 127 - /// On Linux, writes a systemd user unit and enables it. 128 - /// On other platforms, does nothing (the TUI's retry loop handles reconnects). 129 - pub async fn ensure_service_installed() -> Result<()> { 130 - #[cfg(target_os = "macos")] 131 - return ensure_launchagent_installed().await; 132 - 133 - #[cfg(target_os = "linux")] 134 - return ensure_systemd_installed().await; 135 - 136 - #[cfg(not(any(target_os = "macos", target_os = "linux")))] 137 - Ok(()) 138 - } 139 - 140 - // --------------------------------------------------------------------------- 141 - // Uninstall 142 - // --------------------------------------------------------------------------- 143 - 144 - /// Stops and removes the `eind` service and binary installed by 145 - /// [`ensure_service_installed`] and [`download_server`]. 146 - /// 147 - /// Returns a list of completed step descriptions for display in the TUI. 148 - /// User config and session data in `~/.ein/` are left intact. 149 - pub async fn uninstall() -> Result<Vec<String>> { 150 - let mut steps: Vec<String> = Vec::new(); 151 - #[cfg(target_os = "macos")] 152 - uninstall_launchagent(&mut steps).await?; 153 - #[cfg(target_os = "linux")] 154 - uninstall_systemd(&mut steps).await?; 155 - remove_server_binary(&mut steps).await?; 156 - Ok(steps) 157 - } 158 - 159 - async fn remove_server_binary(steps: &mut Vec<String>) -> Result<()> { 160 - let path = server_bin_path(); 161 - if path.exists() { 162 - fs::remove_file(&path) 163 - .await 164 - .with_context(|| format!("failed to remove {}", path.display()))?; 165 - steps.push(format!("Removed {}", path.display())); 166 - } 167 - Ok(()) 168 - } 169 - 170 - #[cfg(target_os = "macos")] 171 - async fn uninstall_launchagent(steps: &mut Vec<String>) -> Result<()> { 172 - let plist = launchagent_plist_path(); 173 - // Ignore errors — the service may already be stopped/unloaded. 174 - let _ = Command::new("launchctl") 175 - .args(["unload", plist.to_str().unwrap_or("")]) 176 - .output() 177 - .await; 178 - if plist.exists() { 179 - fs::remove_file(&plist) 180 - .await 181 - .with_context(|| format!("failed to remove {}", plist.display()))?; 182 - steps.push(format!("Removed LaunchAgent ({})", LAUNCH_AGENT_LABEL)); 183 - } 184 - Ok(()) 185 - } 186 - 187 - #[cfg(target_os = "linux")] 188 - async fn uninstall_systemd(steps: &mut Vec<String>) -> Result<()> { 189 - let unit = systemd_unit_path(); 190 - let _ = Command::new("systemctl") 191 - .args(["--user", "stop", SYSTEMD_SERVICE_NAME]) 192 - .output() 193 - .await; 194 - let _ = Command::new("systemctl") 195 - .args(["--user", "disable", SYSTEMD_SERVICE_NAME]) 196 - .output() 197 - .await; 198 - if unit.exists() { 199 - fs::remove_file(&unit) 200 - .await 201 - .with_context(|| format!("failed to remove {}", unit.display()))?; 202 - steps.push(format!( 203 - "Removed systemd user service ({})", 204 - SYSTEMD_SERVICE_NAME 205 - )); 206 - } 207 - let _ = Command::new("systemctl") 208 - .args(["--user", "daemon-reload"]) 209 - .output() 210 - .await; 211 - Ok(()) 212 - } 213 - 214 - // --------------------------------------------------------------------------- 215 - // macOS LaunchAgent 216 - // --------------------------------------------------------------------------- 217 - 218 - #[cfg(target_os = "macos")] 219 - const LAUNCH_AGENT_LABEL: &str = "com.ein.server"; 220 - 221 - #[cfg(target_os = "macos")] 222 - fn launchagent_plist_path() -> PathBuf { 223 - dirs::home_dir() 224 - .expect("home directory not found") 225 - .join("Library") 226 - .join("LaunchAgents") 227 - .join(format!("{LAUNCH_AGENT_LABEL}.plist")) 228 - } 229 - 230 - #[cfg(target_os = "macos")] 231 - async fn ensure_launchagent_installed() -> Result<()> { 232 - // Check if already loaded. 233 - let status = Command::new("launchctl") 234 - .args(["list", LAUNCH_AGENT_LABEL]) 235 - .output() 236 - .await 237 - .context("launchctl not found")?; 238 - 239 - if status.status.success() { 240 - return Ok(()); // Already running. 241 - } 242 - 243 - let plist_path = launchagent_plist_path(); 244 - let bin = server_bin_path(); 245 - let log = dirs::home_dir() 246 - .expect("home directory not found") 247 - .join(".ein") 248 - .join("server.log"); 249 - 250 - let plist = format!( 251 - r#"<?xml version="1.0" encoding="UTF-8"?> 252 - <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 253 - <plist version="1.0"> 254 - <dict> 255 - <key>Label</key> 256 - <string>{LAUNCH_AGENT_LABEL}</string> 257 - <key>ProgramArguments</key> 258 - <array> 259 - <string>{bin}</string> 260 - </array> 261 - <key>RunAtLoad</key> 262 - <true/> 263 - <key>KeepAlive</key> 264 - <true/> 265 - <key>StandardOutPath</key> 266 - <string>{log}</string> 267 - <key>StandardErrorPath</key> 268 - <string>{log}</string> 269 - </dict> 270 - </plist> 271 - "#, 272 - LAUNCH_AGENT_LABEL = LAUNCH_AGENT_LABEL, 273 - bin = bin.display(), 274 - log = log.display(), 275 - ); 276 - 277 - fs::create_dir_all(plist_path.parent().unwrap()) 278 - .await 279 - .context("failed to create LaunchAgents directory")?; 280 - fs::write(&plist_path, plist) 281 - .await 282 - .context("failed to write plist")?; 283 - 284 - let output = Command::new("launchctl") 285 - .args(["load", plist_path.to_str().unwrap()]) 286 - .output() 287 - .await 288 - .context("failed to run launchctl load")?; 289 - 290 - if !output.status.success() { 291 - let stderr = String::from_utf8_lossy(&output.stderr); 292 - anyhow::bail!("launchctl load failed: {stderr}"); 293 - } 294 - 295 - println!("eind registered as LaunchAgent ({LAUNCH_AGENT_LABEL})"); 296 - Ok(()) 297 - } 298 - 299 - // --------------------------------------------------------------------------- 300 - // Linux systemd user service 301 - // --------------------------------------------------------------------------- 302 - 303 - #[cfg(target_os = "linux")] 304 - const SYSTEMD_SERVICE_NAME: &str = "eind"; 305 - 306 - #[cfg(target_os = "linux")] 307 - fn systemd_unit_path() -> PathBuf { 308 - dirs::home_dir() 309 - .expect("home directory not found") 310 - .join(".config") 311 - .join("systemd") 312 - .join("user") 313 - .join(format!("{SYSTEMD_SERVICE_NAME}.service")) 314 - } 315 - 316 - #[cfg(target_os = "linux")] 317 - async fn ensure_systemd_installed() -> Result<()> { 318 - // Check if already enabled. 319 - let status = Command::new("systemctl") 320 - .args(["--user", "is-enabled", SYSTEMD_SERVICE_NAME]) 321 - .output() 322 - .await 323 - .context("systemctl not found")?; 324 - 325 - if status.status.success() { 326 - return Ok(()); // Already enabled. 327 - } 328 - 329 - let unit_path = systemd_unit_path(); 330 - let bin = server_bin_path(); 331 - 332 - let unit = format!( 333 - "[Unit]\nDescription=Ein server\n\n[Service]\nExecStart={bin}\nRestart=always\n\n[Install]\nWantedBy=default.target\n", 334 - bin = bin.display(), 335 - ); 336 - 337 - fs::create_dir_all(unit_path.parent().unwrap()) 338 - .await 339 - .context("failed to create systemd user directory")?; 340 - fs::write(&unit_path, unit) 341 - .await 342 - .context("failed to write systemd unit")?; 343 - 344 - let reload = Command::new("systemctl") 345 - .args(["--user", "daemon-reload"]) 346 - .output() 347 - .await 348 - .context("failed to run systemctl daemon-reload")?; 349 - 350 - if !reload.status.success() { 351 - let stderr = String::from_utf8_lossy(&reload.stderr); 352 - anyhow::bail!("systemctl daemon-reload failed: {stderr}"); 353 - } 354 - 355 - let enable = Command::new("systemctl") 356 - .args(["--user", "enable", "--now", SYSTEMD_SERVICE_NAME]) 357 - .output() 358 - .await 359 - .context("failed to run systemctl enable")?; 360 - 361 - if !enable.status.success() { 362 - let stderr = String::from_utf8_lossy(&enable.stderr); 363 - anyhow::bail!("systemctl enable failed: {stderr}"); 364 - } 365 - 366 - println!("eind registered as systemd user service ({SYSTEMD_SERVICE_NAME})"); 367 - Ok(()) 368 - }
crates/ein-tui/src/config.rs ein/src/config.rs
crates/ein-tui/src/connection.rs ein/src/connection.rs
crates/ein-tui/src/input.rs ein/src/input.rs
+2 -2
crates/ein-tui/src/lib.rs ein/src/lib.rs
··· 3 3 4 4 //! Ein TUI library. 5 5 //! 6 - //! Exposes [`run`] so both the standalone `ein-tui` binary and the `ein` 6 + //! Exposes [`run`] so both the standalone `ein` binary and the `ein` 7 7 //! meta-package binary can share the same entry-point without duplicating code. 8 8 9 9 mod app; ··· 96 96 None 97 97 }; 98 98 99 - info!(server_addr = %args.server_addr, "ein-tui starting"); 99 + info!(server_addr = %args.server_addr, "ein starting"); 100 100 101 101 // In release builds: download eind if absent, then register it as a 102 102 // system service. Runs before raw mode so stdout is visible for progress.
+2 -2
crates/ein-tui/src/main.rs ein/src/main.rs
··· 2 2 // Copyright 2026 Mason Stallmo 3 3 4 4 use clap::Parser; 5 - use ein_tui::Args; 5 + use ein::Args; 6 6 7 7 #[tokio::main] 8 8 async fn main() -> anyhow::Result<()> { 9 - ein_tui::run(Args::parse()).await 9 + ein::run(Args::parse()).await 10 10 }
crates/ein-tui/src/render.rs ein/src/render.rs