FFI Boundary
The entire Evern API is exposed through a single CoreApi struct with thread-safe internal state (Arc<Mutex<CoreState>>). UniFFI auto-generates idiomatic Kotlin and Swift bindings — platform code calls native methods, not raw FFI.
All public structs use Serialize/Deserialize for JSON marshaling across the FFI boundary. The overhead is negligible — measured and documented during Phase 0.
Session Lifecycle
A typical Evern session follows a fixed call sequence: initialize the profile store, load saved profiles, connect to a server, exchange terminal I/O, and disconnect. The following Kotlin example shows this end-to-end flow.
Steps 5 and 6 repeat for the duration of the session. The drain functions are non-blocking — they return whatever events have accumulated since the last call. See Common Patterns below for polling loop and error handling examples.
Session Management
| Function | Returns | Description |
|---|---|---|
new() |
CoreApi |
Create a new API instance with default state |
connect() |
bool |
Initiate SSH/Mosh connection to a server |
disconnect() |
bool |
Close the active session |
pause_session() |
bool |
Pause session I/O (backgrounding) |
resume_session() |
bool |
Resume a paused session |
connection_state() |
String |
Returns CONNECTED, PAUSED, RECONNECTING, or DISCONNECTED |
connection_type() |
String |
Returns active transport type (SSH, MOSH) |
is_reconnecting() |
bool |
Whether reconnection is in progress |
reconnect_attempt_count() |
u32 |
Current reconnection attempt number |
notify_network_reachability() |
void | Platform callback when network state changes |
connect() — auth_type values
The auth_type parameter accepts three values:
"password"— authenticate usingpassword. Theprivate_key_base64field is ignored."private_key"— authenticate using the PEM key inprivate_key_base64. If the key is passphrase-protected, provide the passphrase inpassword."keyboard_interactive"— use challenge-response authentication. Thepasswordfield is sent as the response to the first challenge prompt.
When both password and private_key_base64 are provided with auth_type set to "private_key", key-based authentication takes precedence. Password is only used as the key passphrase in that case.
The connection timeout is 20 seconds (CONNECT_TIMEOUT). If the server does not respond within that window, the call returns false and a Timeout error is available via last_connection_error_kind().
Terminal I/O
| Function | Description |
|---|---|
send_input() |
Write text to terminal (chat mode, appends newline) |
send_raw_input() |
Write raw bytes to terminal (raw mode, no newline) |
drain_terminal_events() |
Poll raw terminal events (sequence, kind, payload) |
drain_output_events() |
Poll parsed AI output events with accessibility labels |
get_terminal_grid_text() |
Read the current terminal character grid as plain text |
get_terminal_grid_spans_json() |
Read the grid with styled runs (color, bold, italic, underline) |
set_scrollback_limit() |
Configure scrollback buffer size (100 - 100,000 lines) |
session_activity_summary() |
Get files changed, prompts sent, commands run |
export_session_markdown() |
Export session as markdown (max 200,000 chars) |
Polling guidance for drain functions
Both drain_terminal_events() and drain_output_events() are non-blocking. They return all events that have accumulated since the previous call, then clear the internal buffer. If no events are pending, they return an empty list.
Recommended polling intervals: Call drain_terminal_events() every ~100ms for responsive terminal rendering. Call drain_output_events() every ~200ms, since parsed output events arrive less frequently than raw terminal data.
Threading: Both drain functions must be called from a background thread, not the main/UI thread. The underlying mutex lock is fast (sub-millisecond) but should not block UI rendering. On Android, use a coroutine on Dispatchers.IO; on iOS, use a DispatchQueue or Swift concurrency task.
Profiles
| Function | Description |
|---|---|
initialize_profile_store() |
Initialize SQLite database at the given app data directory |
list_server_profiles() |
Return all saved server profiles |
upsert_server_profile() |
Create or update a server profile (id=0 creates new) |
delete_server_profile() |
Delete a server profile by ID |
Voice
Intelligence
| Function | Returns | Description |
|---|---|---|
drain_output_events() |
Vec<OutputEventRecord> |
Poll parsed AI output events (code blocks, diffs, tool use, thinking blocks) |
session_activity_summary() |
SessionActivitySummary |
Aggregate stats: files changed, prompts sent, commands run |
export_session_markdown() |
String |
Export full session as markdown |
list_developer_dictionary_terms() |
Vec<String> |
List all ~500 developer dictionary entries |
Snippets
Security & Errors
The API uses a stateful error model with three independent error channels. Errors persist until cleared — clients can query the current error state at any time.
Connection Errors
Connection and authentication failures. Kinds: InvalidInput, MissingCredentials, HostKeyMismatch, AuthFailed, Timeout, TransportError.
Session I/O Errors
Runtime transport failures. Kinds: InvalidInput, NotConnected, Paused, TransportError.
Data Errors
Database and persistence failures. Kinds: InvalidInput, StoreUnavailable, PersistenceError.
Host Key & Multiplexer
| Function | Description |
|---|---|
override_host_key(host, port) |
Accept a changed host key after user confirmation |
list_multiplexer_sessions() |
List tmux/screen sessions on the remote host |
attach_multiplexer_session(entry) |
Reattach to a tmux/screen session |
Handling HostKeyMismatch
When connect() returns false and last_connection_error_kind() returns "HostKeyMismatch", the server's host key has changed since the last connection. This can indicate a legitimate server reinstallation or a man-in-the-middle attack.
The expected handling flow is:
- Read the error message from
last_connection_error_message()— it contains the old and new key fingerprints. - Present a confirmation dialog to the user explaining the key change and showing both fingerprints.
- If the user accepts, call
override_host_key(host, port)to persist the new key. - Retry the
connect()call with the same parameters. The connection will now succeed (assuming credentials are valid).
Do not call override_host_key() automatically without user confirmation. The whole point of host key verification is to prevent silent MITM attacks.
Data Types
| Type | Fields |
|---|---|
ServerProfile |
id: i64, label, host, port: u16, username, auth_type, group_name |
TerminalEvent |
sequence: u64, kind, payload |
OutputEventRecord |
sequence: u64, kind, payload, accessibility_label |
SessionActivitySummary |
files_changed: Vec<SessionFileChange>, prompts_sent: u32, commands_run: u32 |
SessionFileChange |
path, operation, occurrences: u32 |
TerminalGridStyleRun |
text, fg_hex, bg_hex, bold, italic, underline |
TerminalGridStyleLine |
runs: Vec<TerminalGridStyleRun> |
SnippetEntry |
id: i64, title, body, variables, tags, folder |
VoiceCorrectionPair |
raw_phrase, corrected_phrase |
VoiceCorrectionEntry |
raw_phrase, corrected_phrase, occurrences: u32, status |
ConnectionError |
kind, message |
SessionIoError |
kind, message |
DataError |
kind, message |
Constants
| Constant | Value |
|---|---|
DEFAULT_SCROLLBACK_LINES |
10,000 |
MAX_SCROLLBACK_LINES |
100,000 |
MIN_SCROLLBACK_LINES |
100 |
MAX_OUTPUT_EVENT_HISTORY |
4,000 |
MAX_MARKDOWN_EXPORT_CHARS |
200,000 |
MAX_SNIPPET_BODY_CHARS |
12,000 |
MAX_SNIPPET_VARIABLES |
30 |
CONNECT_TIMEOUT |
20 seconds |
DEFAULT_TERMINAL_COLUMNS |
160 |
DEFAULT_TERMINAL_ROWS |
48 |
Common Patterns
The following Kotlin examples show integration patterns that recur in host applications. Each example is self-contained and can be adapted for Swift with minor syntax changes.
Connection error handling with HostKeyMismatch recovery
This pattern covers the full connection attempt, including the HostKeyMismatch recovery flow, authentication failures, and timeouts.
Background polling loop for terminal events
Terminal and output events must be drained on a background thread. This coroutine-based loop polls at the recommended intervals and dispatches results to the UI layer.
Voice input preprocessing with context
Voice-to-command preprocessing uses the personal dictionary and recent command history to correct STT output before sending it to the terminal. The context-aware variant produces significantly better results for technical terms.