# API Reference ## Tauri IPC Commands These are the commands the Svelte frontend (or any Tauri WebView) can call via `invoke()`. **Source:** `src-tauri/src/commands.rs` ```typescript import { invoke } from '@tauri-apps/api/core'; ``` --- ### Status & Recording #### `get_status() → StatusPayload` Returns the current application state. ```typescript const status = await invoke('get_status'); ``` ```typescript interface StatusPayload { recording: boolean; processing: boolean; speaking: boolean; mcp_recording: boolean; audio_ready: boolean; word_count: number; active_target_id: string; active_target_label: string; } ``` --- #### `start_recording() → void` Sets the recording flag to true. The audio pipeline will start capturing. ```typescript await invoke('start_recording'); ``` --- #### `stop_recording() → void` Sets the recording flag to false, signaling the audio pipeline to stop. ```typescript await invoke('stop_recording'); ``` --- #### `toggle_recording() → boolean` Toggles recording state. Returns the **new** recording state. ```typescript const nowRecording = await invoke('toggle_recording'); ``` --- ### Configuration #### `get_config() → AppConfig` Returns the full application configuration. ```typescript const config = await invoke('get_config'); ``` --- #### `save_config(newConfig: AppConfig) → void` Persists configuration to `~/.config/voxctrl/config.json` and emits a `config-changed` event to all windows. ```typescript await invoke('save_config', { newConfig: myConfig }); ``` Note the parameter name is `newConfig` (camelCase), not `config`. --- ### Routing #### `get_targets() → OutputTarget[]` Returns all output commands from `targets.toml`. Named `targets` throughout the API; "Output Commands" is the UI label for the same thing. ```typescript const targets = await invoke('get_targets'); ``` --- #### `save_targets(targets: OutputTarget[]) → void` Writes updated targets to `targets.toml`, updates the in-memory cache, hot-reloads the router, and spawns any new FIFO response pipe listeners. ```typescript await invoke('save_targets', { targets: myTargets }); ``` --- #### `get_bindings() → HotkeyBinding[]` Returns all hotkey bindings from `bindings.toml`. ```typescript const bindings = await invoke('get_bindings'); ``` --- #### `save_bindings(bindings: HotkeyBinding[]) → void` Writes updated bindings to `bindings.toml` and sends a hot-reload signal to the hotkey listener thread. ```typescript await invoke('save_bindings', { bindings: myBindings }); ``` --- #### `reset_chat_conversation(targetId: string) → number` Forgets a `chat` target's stored conversation so the next dictation starts a new thread. Returns how many messages were discarded. Unknown target ids return `0`. ```typescript const dropped = await invoke('reset_chat_conversation', { targetId: 'hermes' }); ``` --- #### `test_chat_target(target: OutputTarget) → string` Probes a `chat` target's `GET /v1/models` endpoint. Resolves with a description of the reachable endpoint, or rejects with the failure reason. Accepts an unsaved target so the settings UI can test edits before they are persisted. ```typescript try { const detail = await invoke('test_chat_target', { target: editingTarget }); } catch (e) { console.error('Chat endpoint unreachable:', e); } ``` --- ### Setup & First Run #### `open_setup_wizard() → void` Opens the first-run wizard, building its window if it has been closed. A re-opened wizard starts at step one. ```typescript await invoke('open_setup_wizard'); ``` --- #### `finish_setup_wizard(openSettings: boolean) → void` Marks setup complete (`ui.setup_completed = true`), persists the config, and closes the wizard window. Pass `true` to open Settings afterwards. ```typescript await invoke('finish_setup_wizard', { openSettings: false }); ``` --- #### `get_setup_status() → SetupStatusPayload` Everything first-run setup depends on, in one call: how global shortcuts are being delivered, whether text can be typed into other windows, and whether a speech model is on disk. The wizard's final screen uses it to report problems it did not itself cause. ```typescript interface SetupStatusPayload { hotkeys: HotkeyStatusPayload; hotkeys_active: boolean; model_ready: boolean; model_size: string; model_auto_downloads: boolean; // small models fetch themselves in the background missing_injection_tool: string | null; pkexec_available: boolean; manual_package_commands: string; is_complete: boolean; } ``` --- ### Updates VoxCtrl never checks on its own; every command below only runs because the user triggered it from Settings → General or the update window. #### `check_for_update() → UpdateCheckPayload` Asks GitHub for the latest published release and compares it with the running version. Also resolves which release file matches this installation, so `install_update` does not have to fetch anything twice. ```typescript interface UpdateInfo { version: string; // "0.4.0" tag: string; // "v0.4.0" current_version: string; // the version running now notes: string; // release notes, trimmed for a dialog release_url: string; asset_name: string | null; // the file that would be installed download_size: number; // bytes can_self_update: boolean; // false for .deb / source / unwritable installs unsupported_reason: string | null; } interface UpdateCheckPayload { current_version: string; update: UpdateInfo | null; // null when this is the latest release } const result = await invoke('check_for_update'); ``` --- #### `get_pending_update() → UpdateCheckPayload` What the last check found, without contacting GitHub. Returns `update: null` if no check has run yet. --- #### `install_update() → void` Downloads the pending update, verifies it against the SHA-256 digest GitHub published, replaces the running application file and restarts into it. Emits `update-progress` while downloading and `update-installed` just before the app exits; on any failure it emits `update-failed` and leaves the running version untouched. Rejects if no update is pending, if this installation cannot update itself, or if an install is already running. ```typescript await invoke('install_update'); ``` --- #### `open_update_window() → void` / `dismiss_update() → void` Opens the update window (building it if needed), and closes it. --- ### Text-to-Speech #### `speak_text(text: string, voice?: string) → void` Queues text for TTS playback. ```typescript await invoke('speak_text', { text: 'Hello world', voice: 'en-us-lessac-medium' }); ``` `voice` is optional — omit to use the configured default. --- #### `check_voice_downloaded(voiceName: string) → boolean` Returns whether a Piper voice pack is available locally. ```typescript const downloaded = await invoke('check_voice_downloaded', { voiceName: 'en-us-lessac-medium' }); ``` --- #### `download_voice(voiceName: string) → void` Downloads a Piper voice pack from GitHub. ```typescript await invoke('download_voice', { voiceName: 'en-us-ryan-high' }); ``` --- #### `list_pocket_tts_voices(voiceDir: string) → { id: string; label: string }[]` Returns the built-in Pocket-TTS voice catalogue merged with any custom `.wav` clips found in `voiceDir` (`""` = default directory). A custom clip named after a built-in voice id overrides that entry's label/source instead of adding a duplicate. ```typescript const voices = await invoke<{ id: string; label: string }[]>('list_pocket_tts_voices', { voiceDir: '', }); ``` --- #### `check_pocket_tts_ready(voice: string, voiceDir: string) → boolean` Returns whether the model weights, tokenizer, and the selected voice's reference clip are all present locally (no network access). ```typescript const ready = await invoke('check_pocket_tts_ready', { voice: 'alba', voiceDir: '', }); ``` --- #### `download_pocket_tts(voice: string, voiceDir: string, hfToken: string | null) → void` Downloads the gated model weights, tokenizer, and the selected voice's reference clip. For a custom voice resolved from `voiceDir`, the clip is already on disk so only the model weights/tokenizer are fetched. ```typescript await invoke('download_pocket_tts', { voice: 'alba', voiceDir: '', hfToken: '', }); ``` --- #### `inflect_micro_available() → boolean` Whether this build was compiled with the `inflect-micro` cargo feature. When `false` the engine can be selected and its model downloaded, but synthesis is unavailable — the Settings panel uses this to explain why Test TTS is disabled. ```typescript const available = await invoke('inflect_micro_available'); ``` --- #### `check_inflect_micro_downloaded(modelDir: string) → boolean` Whether both ONNX graphs and a usable phoneme table are present in `modelDir` (`""` = default directory). The table is detected by parsing rather than by filename, so this agrees with what synthesis will actually accept. ```typescript const ready = await invoke('check_inflect_micro_downloaded', { modelDir: '' }); ``` --- #### `download_inflect_micro(modelDir: string) → void` Downloads `duration.onnx`, `decode.onnx`, their accompanying files, and the ordered symbol list. The export's layout is discovered by listing the Hugging Face API, and the symbol list is fetched separately because it is published in a different repository from the graphs. Independent of the `inflect-micro` feature — the model downloads in any build. ```typescript await invoke('download_inflect_micro', { modelDir: '' }); ``` --- #### `inflect_micro_inspect(modelDir: string) → object` Reports what the downloaded graphs actually declare: every input and output with its element type and shape, plus the phoneme table's filename and size. Skips the contract check, so it still answers for a model whose signature does *not* match. Requires the `inflect-micro` feature. ```typescript const signature = await invoke('inflect_micro_inspect', { modelDir: '' }); ``` --- #### `check_breeze_tts_2_ready(modelDir: string) → boolean` Returns whether Breeze-TTS-2 model weights and tokenizer exist locally. #### `download_breeze_tts_2(modelDir: string, hfToken: string | null) → void` Downloads gated Breeze-TTS-2 model weights from HuggingFace using the provided token. --- #### `check_vox_cpm_2_ready(modelDir: string) → boolean` Returns whether VoxCPM2 model assets (`config.json`, `generation_config.json`, weights) exist locally. #### `download_vox_cpm_2(modelDir: string, hfToken: string | null) → void` Downloads VoxCPM2 model assets from HuggingFace. --- #### `hf_token_env() → string | null` Returns any `HF_TOKEN` exported in the application environment (takes precedence over UI config). --- ### Speech Recognition Models #### `check_model_downloaded(modelSize: string) → boolean` Returns whether a Whisper model GGUF file is present locally. ```typescript const downloaded = await invoke('check_model_downloaded', { modelSize: 'base' }); ``` --- #### `download_model(modelSize: string) → void` Downloads a Whisper GGUF model. ```typescript await invoke('download_model', { modelSize: 'small' }); ``` Valid sizes: `"tiny"`, `"tiny.en"`, `"base"`, `"base.en"`, `"small"`, `"small.en"`, `"medium"`, `"medium.en"`, `"large-v2"`, `"large-v3"`, `"large-v3-turbo"` --- #### `download_parakeet_model(modelSize: string) → void` Downloads the Parakeet TDT ONNX model files. --- #### `check_s1_mini_downloaded(modelDir?: string) → boolean` Checks whether the S1-mini GGUF model and tokenizer are downloaded locally. #### `download_s1_mini_model(modelDir?: string) → void` Downloads the S1-mini GGUF model (~480 MB) and tokenizer assets for on-device dictation cleanup. --- #### `test_remote_stt(endpoint: string, apiKey: string | null, model: string, timeoutSecs: number) → RemoteSttTestResult` Tests connection to a Remote Speech Engine (OpenAI-compatible `/v1/audio/transcriptions` API) with sample audio, and queries available models. ```typescript interface RemoteSttTestResult { success: boolean; message: string; latency_ms: number; models: string[]; } ``` --- ### Audio Monitoring #### `start_monitoring_audio() → void` Enables the monitoring flag so `audio-level` events are emitted for the VU meter. ```typescript await invoke('start_monitoring_audio'); ``` --- #### `stop_monitoring_audio() → void` Disables monitoring and stops `audio-level` event streaming. ```typescript await invoke('stop_monitoring_audio'); ``` --- #### `list_audio_devices() → AudioDeviceInfo[]` Returns all available input devices. ```typescript const devices = await invoke('list_audio_devices'); ``` ```typescript interface AudioDeviceInfo { index: number; name: string; } ``` --- ### OpenAI API (LLM post-processing) #### `test_openai(endpoint: string, apiKey: string | null, timeoutSecs: number) → OpenAiTestResult` Pings an OpenAI-compatible API server (`GET {endpoint}/v1/models`) and lists available models. `apiKey` is sent as a `Bearer` token when present; pass `null` for servers that don't require authentication (e.g. a local server). > This command was previously named `test_ollama`; the client speaks the OpenAI > API and works with any compatible server. ```typescript const result = await invoke('test_openai', { endpoint: 'http://localhost:11434', apiKey: null, timeoutSecs: 5 }); ``` ```typescript interface OpenAiTestResult { success: boolean; message: string; models: string[]; } ``` --- ### Bug Reports The commands behind Settings → Bug Report. What a report may contain, and why, is in [Bug Reports](./bug_reports.md); the enforcement is in the `voxctrl-bugreport` crate. None of these run on their own — every one is a button press. ```typescript interface UserStatement { summary: string; // one line; becomes the issue title description: string; // what happened, what was expected area: string; // from a fixed list in the UI frequency: "always" | "sometimes" | "once"; } ``` #### `bug_report_context() → BugReportContext` What the page needs to describe itself: whether this build can submit a report itself, where the log it quotes lives, the limits, and how many reports have gone out. ```typescript interface BugReportContext { relay_configured: boolean; // false → the page hides Send and offers the rest issues_new_url: string; support_email: string; log_path: string; install_id: string; limits: { cooldown_seconds: number; per_day: number; per_month: number; min_description_chars: number; max_description_chars: number; }; submissions_last_day: number; submissions_last_month: number; } ``` #### `preview_bug_report(statement: UserStatement) → BugReportPreview` Builds the report and returns it. `markdown` is the literal issue body, not a summary of it — the page shows exactly this and nothing fuller is ever sent. Sends nothing. ```typescript interface BugReportPreview { markdown: string; title: string; fingerprint: string; blocked_reason: string | null; // why the limits will not allow a send can_submit: boolean; github_url: string; // GitHub's new-issue form, prefilled mailto_url: string; } ``` The machine facts are gathered once per run and cached: collecting them shells out to a system probe (PowerShell on Windows), and the page previews on every pause in typing. #### `submit_bug_report(statement: UserStatement) → BugReportOutcome` Posts the report to the relay compiled into this build. Re-checks the limits first — the page may have been open since before the last submission — and records a submission only when one actually goes out, so a failed send does not spend the reporter's allowance. ```typescript interface BugReportOutcome { ok: boolean; issue_url: string | null; message: string; // shown verbatim; the relay may write it } ``` #### `save_bug_report(statement: UserStatement, path: string) → string` Writes the report as Markdown to `path`. Never rate-limited, never needs a network. Returns the path written. #### `suggested_bug_report_filename() → string` A timestamped filename for the save dialog. #### `reset_bug_report_identity() → string` Throws away the installation identifier and the local submission history, and returns the new identifier. --- ### Overlay The overlay window has no explicit show/hide command — it is created and destroyed entirely from the Rust backend (`tray::spawn_status_ticker`, which polls the same recording/speaking/MCP-recording state and `ui.show_overlay` / `tts.response_overlay` / `mcp.visual_feedback` config that decides everything else that reacts to them), not requested by the frontend. Once the window exists, its *content* is driven by the `status-tick` / `audio-level` events the same way every other consumer of those already reacts to them. See the [Overlay UI Guide](./overlays.md). #### `get_custom_overlays() → CustomOverlayInfo[]` Lists every user-authored custom overlay folder under the local overlays directory (see `get_custom_overlays_dir`), alongside the built-in styles in the `ui.overlay_style` picker. ```typescript interface CustomOverlayInfo { name: string; html: string; css: string; } const overlays = await invoke('get_custom_overlays'); ``` #### `get_custom_overlay(name: string) → CustomOverlayInfo | null` Re-reads a single custom overlay folder by its display name — used to reload just the active style rather than the whole list. ```typescript const overlay = await invoke('get_custom_overlay', { name: 'Custom' }); ``` #### `get_custom_overlays_dir() → string` The resolved, absolute path to the custom-overlays folder, for display in Settings. ```typescript const dir = await invoke('get_custom_overlays_dir'); ``` --- ### Window & Setup Management #### `get_setup_status() → SetupStatusPayload` Returns comprehensive first-run readiness: shortcut health, active speech model status, missing injection tools (`wtype`/`xdotool`), and polkit privileges. #### `download_configured_model() → void` Downloads the speech model selected by the active configuration. #### `open_setup_wizard() → void` Spawns the first-run Setup Wizard window on demand. #### `finish_setup_wizard(openSettings: boolean) → void` Marks setup complete (`ui.setup_completed = true`), emits `config-changed`, closes the wizard, and optionally opens Settings. #### `open_settings_tab(tab: string) → void` Opens the Settings window and switches directly to the specified tab (`"general"`, `"engine"`, `"hotkeys"`, `"audio"`, `"tts"`, `"features"`, `"visual"`, `"openai"`, `"targets"`, `"bugreport"`). #### `get_available_monitors() → MonitorInfo[]` Queries connected display monitors (names, dimensions, primary flag) on the main thread. ```typescript interface MonitorInfo { name: string | null; width: number; height: number; is_primary: boolean; } ``` --- ## Tauri Events (Backend → Frontend) Subscribe with `listen()` from `@tauri-apps/api/event`. ```typescript import { listen } from '@tauri-apps/api/event'; ``` ### `status-tick` Emitted whenever the application state changes, and otherwise as a heartbeat under a second apart. The backend compares each payload against the last and skips sending an identical one, so an idle app is quiet without the frontend's staleness fallback ever tripping. ```typescript await listen('status-tick', (event) => { console.log(event.payload.recording); }); ``` ### `config-changed` Emitted when the config is saved (from any window or external change). ```typescript await listen('config-changed', (event) => { config.set(event.payload); }); ``` ### `audio-level` Emitted with the current RMS energy level (0.0–1.0+) while recording or monitoring — the overlay visualisers and the Audio Input tab's VU meter respectively. Nothing is emitted when neither is watching. The microphone produces a level for every buffer it delivers, which is far more often than anything can draw; they are coalesced to at most one event per frame (16 ms), newest value winning. ```typescript await listen('audio-level', (event) => { updateVuMeter(event.payload); }); ``` ### `update-progress` Emitted while an update downloads, at most once per megabyte. ```typescript await listen<{ downloaded: number; total: number }>('update-progress', (event) => { // total is 0 when the server sent no content length }); ``` ### `update-installed` Emitted with the new version once it is in place. The app exits shortly after and the new build starts itself. ### `update-failed` Emitted with a message when an update could not be installed. The running version is unchanged. --- ## TypeScript Types These types are defined in `src/stores/config.ts`: ```typescript interface AppConfig { engine: EngineConfig; audio: AudioConfig; ui: UiConfig; features: FeaturesConfig; openai: OpenAiConfig; tts: TtsConfig; mcp: McpConfig; } interface EngineConfig { backend: "whisper-cpp" | "moonshine" | "parakeet" | "remote-openai"; whisper_cpp: WhisperCppConfig; moonshine: MoonshineConfig; parakeet: ParakeetConfig; remote_openai: RemoteOpenAiConfig; s1_mini: S1MiniConfig; } interface WhisperCppConfig { model_dir: string; model_size: string; device: string; threads: number; } interface MoonshineConfig { model_size: string; language: string; } interface ParakeetConfig { model_size: string; language: string; } interface RemoteOpenAiConfig { endpoint: string; api_key: string | null; model: string; language: string; timeout_secs: number; } interface S1MiniConfig { enabled: boolean; styling: string; } interface AudioConfig { vad_threshold: number; input_device_index: number | null; evdev_device: string | null; noise_suppression: boolean; gain: number; dynamic_stream: boolean; } interface UiConfig { show_overlay: boolean; overlay_style: "voice_card" | "waveform" | "pulse" | "blue_wave" | "none"; overlay_position: string; overlay_monitor: string; auto_show_settings: boolean; setup_completed: boolean; show_notification: boolean; } interface FeaturesConfig { remove_fillers: boolean; custom_vocabulary: string[]; spoken_punctuation: boolean; auto_format_lists: boolean; snippets: Record; early_command_detection: boolean; } interface OpenAiConfig { enabled: boolean; model: string; mode: "clean" | "formal" | "casual" | "bullet" | "concise" | "custom"; // GUI preset that fills system_prompt custom_prompt: string | null; // legacy; migrated into user_prompt on load system_prompt: string; // system message (empty = none) user_prompt: string; // user message template; must contain "{text}" endpoint: string; // OpenAI-compatible API base URL (a `/v1` suffix is optional) api_key: string | null; // sent as a Bearer token when set timeout_secs: number; } interface PocketTtsConfig { voice: string; prewarm: boolean; voice_dir: string; // custom .wav voice clips; empty = default directory gpu: boolean; // Vulkan, via the audio.cpp subprocess } interface InflectMicroConfig { model_dir: string; // empty = default directory seed: number; // deterministic for a fixed seed noise_scale: number; // 0.0-1.0 variation, default 0.667 prewarm: boolean; } interface BreezeTts2Config { voice_mode: "prompt" | "clone"; cloned_voice: string; // voice id from the shared clip folder voice_dir: string; // shared with pocket_tts; empty = default directory speaker_prompt: string; // Voice Design description model_dir: string; // empty = default directory prewarm: boolean; gpu: boolean; // Vulkan, via the audio.cpp subprocess } interface VoxCpm2Config { voice_mode: "prompt" | "clone"; speaker_prompt: string; cloned_voice: string; voice_dir: string; ultimate_cloning: boolean; model_dir: string; prewarm: boolean; gpu: boolean; } interface TtsConfig { enabled: boolean; engine: "piper" | "espeak" | "pocket_tts" | "inflect_micro" | "breeze_tts_2" | "vox_cpm_2"; voice: string; voice_dir: string; stop_key: string[]; // singular field name, plural value response_overlay: boolean; speed: number; // not used by pocket_tts gpu: boolean; // applies to piper; Breeze and VoxCPM2 have their own flag hf_token: string | null; // one token for every gated model download pocket_tts: PocketTtsConfig; inflect_micro: InflectMicroConfig; breeze_tts_2: BreezeTts2Config; vox_cpm_2: VoxCpm2Config; memory_mode: "always_loaded" | "on_demand"; // "on_demand" unloads model after 15 min idle idle_unload_secs: number; // idle seconds before unloading (default 900) snippets: Record; // pronunciation guide, speech only } interface McpConfig { server_enabled: boolean; // not "enabled" record_timeout: number; // default for transcribe_voice, read per call visual_feedback: boolean; } interface OutputTarget { id: string; label: string; delivery: "inject" | "clipboard" | "exec" | "pipe" | "socket" | "file" | "dbus" | "http" | "webhook" | "mcp" | "speak" | "chat" | "command"; // exec command?: string; // pipe pipe_path?: string; // socket (unix or TCP) socket_unix?: string; socket_host?: string; socket_port?: number; // file file_path?: string; file_prefix: string; file_timestamp: boolean; file_timestamp_format: string; // strftime, UTC; default "%Y-%m-%dT%H:%M:%SZ" file_mode: string; // "append" or "write" // dbus dbus_signal?: string; // http / webhook http_url?: string; http_method?: string; webhook_url?: string; webhook_secret?: string; // mcp mcp_path?: string; mcp_tool?: string; // chat (conversational LLM) chat_url?: string; chat_model?: string; chat_system_prompt?: string; chat_reply_mode?: "speak" | "inject" | "clipboard" | "none"; chat_max_history?: number; chat_timeout_secs?: number; chat_reset_phrase?: string; chat_api_key?: string; strip_newlines?: boolean; // default: false; inject and command targets processing?: TargetProcessingConfig; response_pipe?: string; } interface TargetProcessingConfig { remove_fillers?: boolean; spoken_punctuation?: boolean; auto_format_lists?: boolean; code_mode?: boolean; } interface HotkeyBinding { id: string; label: string; keys: string[]; gesture: "hold" | "toggle" | "double_tap" | "double_tap_hold"; target_id: string; target_ids: string[]; tap_ms: number; // default: 250 hold_threshold_ms: number;// default: 200 disabled: boolean; openai_enabled?: boolean; // legacy alias: ollama_enabled openai_model?: string; // legacy alias: ollama_model openai_mode?: string; // legacy alias: ollama_mode openai_prompt?: string; // user prompt template override (must contain "{text}"); legacy alias: ollama_prompt openai_system_prompt?: string; // system prompt override (empty = inherit global default); legacy alias: ollama_system_prompt } interface AppStatus { recording: boolean; processing: boolean; speaking: boolean; mcp_recording: boolean; audio_ready?: boolean; word_count: number; active_target_id?: string; active_target_label?: string; } ```