#![cfg_attr(all(target_os = "windows", not(test)), windows_subsystem = "windows")]
const VERSION: &str = env!("HEX_BUILD_VERSION");
#[cfg(target_os = "macos")]
mod accessibility;
mod app_paths;
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
mod app_settings;
#[cfg(target_os = "macos")]
mod apple_intelligence;
#[cfg(target_os = "macos")]
mod apple_speech;
mod application_catalog;
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
mod audio;
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
mod calendar;
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
mod caps_lock_dictation;
#[cfg(hex_capture)]
mod capture_host;
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
mod control;
mod desktop;
mod dev_build;
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
mod dictation;
mod durable;
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
mod feedback;
mod history;
#[cfg(target_os = "macos")]
mod keyboard;
#[cfg_attr(target_os = "macos", path = "keyboard_input_sources/macos.rs")]
#[cfg_attr(target_os = "windows", path = "keyboard_input_sources/windows.rs")]
#[cfg_attr(target_os = "linux", path = "keyboard_input_sources/x11.rs")]
mod keyboard_input_sources;
mod labs;
#[cfg(target_os = "linux")]
mod linux;
mod local_api;
#[cfg(target_os = "macos")]
mod macos_rune;
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
mod meeting;
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
mod microphone_activity;
mod net;
#[cfg(target_os = "macos")]
mod parakeet;
#[cfg(target_os = "macos")]
mod paste;
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
mod personal_commands;
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
mod policy;
#[cfg(target_os = "macos")]
mod recognition;
mod rune_hud;
mod rune_ui;
#[cfg(all(target_os = "macos", hex_capture))]
mod screenshots;
#[cfg(target_os = "macos")]
mod suppression;
mod transcription;
mod updater;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "macos")]
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
#[cfg(target_os = "macos")]
use clap::{Parser, Subcommand, ValueEnum};
use color_eyre::Result;
#[cfg(target_os = "macos")]
use color_eyre::eyre::eyre;
#[cfg(target_os = "macos")]
#[derive(Parser)]
#[command(
name = "hex",
version = crate::VERSION,
about = "Local, observable voice control",
long_about = "Local, observable voice control.\n\n\
Control commands (status, dictation, meetings record, settings, model, window, …) drive \
the one running Hex over its owner-only local API and never launch it, except `start` \
and `restart`. Every one takes --json for a versioned reply (docs/cli.md). Exit codes: \
0 done or nothing to change, 1 refused or failed, 2 usage, 3 Hex isn't running.",
after_help = "Run `hex <command> --help` for details, or `hex settings list` for every setting."
)]
struct Cli {
#[command(subcommand)]
command: Option<Command>,
}
#[cfg(all(debug_assertions, target_os = "macos"))]
#[global_allocator]
static ALLOC: procwatch::alloc::Counting = procwatch::alloc::Counting::system();
static SHUTDOWN: AtomicBool = AtomicBool::new(false);
pub(crate) const DEVELOPER_FEATURES_ENABLED: bool = cfg!(debug_assertions);
#[cfg(target_os = "macos")]
#[derive(Subcommand)]
enum Command {
#[command(flatten)]
Control(control::cli::ControlCli),
App {
#[arg(long)]
hidden: bool,
#[arg(long)]
device: Option<String>,
#[arg(last = true)]
rune_args: Vec<String>,
},
Listen {
#[arg(long)]
device: Option<String>,
},
#[command(hide = true)]
Service {
#[arg(long)]
embedded: bool,
},
Dev {
#[command(subcommand)]
command: DevCommand,
},
#[cfg(hex_capture)]
#[command(name = "capsule-playground", hide = true)]
CapsulePlayground {
#[arg(last = true)]
rune_args: Vec<String>,
},
#[cfg(debug_assertions)]
Meeting {
#[command(subcommand)]
command: MeetingCommand,
},
#[command(hide = true)]
BenchmarkTranscription {
manifest: PathBuf,
#[arg(long, value_enum, default_value = "transcribe-cpp")]
backend: TranscriptionBenchmarkBackend,
#[arg(long)]
model: Option<PathBuf>,
#[arg(long, default_value_t = 1)]
warmups: usize,
#[arg(long, default_value_t = 7)]
runs: usize,
},
}
#[cfg(target_os = "macos")]
#[derive(Clone, Copy, ValueEnum)]
enum TranscriptionBenchmarkBackend {
Onnx,
TranscribeCpp,
}
#[cfg(target_os = "macos")]
#[derive(Subcommand)]
enum DevCommand {
#[cfg(debug_assertions)]
Status,
#[cfg(debug_assertions)]
Dashboard,
#[cfg(debug_assertions)]
Hud {
#[arg(value_enum)]
state: control::developer_control::DeveloperHudState,
},
#[cfg(debug_assertions)]
Show {
#[arg(value_enum)]
pane: control::developer_control::DeveloperPane,
},
#[cfg(all(debug_assertions, hex_capture))]
CaptureReplay {
#[arg(required = true)]
wavs: Vec<PathBuf>,
},
VoiceFilter {
#[command(subcommand)]
command: VoiceFilterCommand,
},
}
#[cfg(target_os = "macos")]
#[derive(Subcommand)]
enum VoiceFilterCommand {
Render {
input: PathBuf,
#[arg(long, default_value = "muffled")]
mode: String,
#[arg(long)]
params: Option<String>,
#[arg(long = "dictation")]
dictation: Vec<String>,
#[arg(long, default_value_t = 1.0)]
strength: f64,
#[arg(short = 'o', long = "output")]
output: PathBuf,
},
}
#[cfg(debug_assertions)]
#[cfg(target_os = "macos")]
#[derive(Subcommand)]
enum MeetingCommand {
Record {
#[arg(long)]
title: Option<String>,
},
List,
Show { id: String },
Probe,
Transcribe { id: String },
Summarize {
target: String,
#[arg(long)]
excerpt_chars: Option<usize>,
},
Chapters { path: PathBuf },
Echo {
directory: PathBuf,
#[arg(long)]
microphone_offset_ms: Option<u64>,
#[arg(long)]
transcribe: bool,
},
}
#[cfg(target_os = "macos")]
fn main() -> Result<()> {
color_eyre::install()?;
let cli = Cli::parse_from(mcp_alias_arguments(std::env::args_os().collect()));
let executable_name = std::env::current_exe()
.ok()
.and_then(|path| path.file_stem().map(ToOwned::to_owned));
let bundled_watcher = is_bundled_app_executable(executable_name.as_deref());
if opens_app(cli.command.as_ref(), bundled_watcher) {
rune::prewarm_gpu();
}
#[cfg(hex_capture)]
if let Some(Command::CapsulePlayground { rune_args }) = &cli.command {
keyboard::initialize_layout()?;
rune_ui::capture_capsule::playground::run(rune_args.clone());
return Ok(());
}
let cli = match cli.command {
Some(Command::Control(command)) => control::cli::finish(control::cli::run(command)),
command => Cli { command },
};
#[cfg(debug_assertions)]
match &cli.command {
Some(Command::Meeting {
command: MeetingCommand::List,
}) => {
return control::transcript_cli::meetings(
&control::transcript_cli::MeetingsCommand::List(Default::default()),
);
}
Some(Command::Meeting {
command: MeetingCommand::Show { id },
}) => {
return control::transcript_cli::meetings(
&control::transcript_cli::MeetingsCommand::Show {
id: id.clone(),
md: true,
json: false,
srt: false,
vtt: false,
from: None,
to: None,
chapter: None,
},
);
}
_ => {}
}
if let Some(Command::Dev {
command:
DevCommand::VoiceFilter {
command:
VoiceFilterCommand::Render {
input,
mode,
params,
dictation,
strength,
output,
},
},
}) = &cli.command
{
return audio::voice_filter::render_wav_command(
input,
mode,
params.as_deref(),
dictation,
*strength,
output,
);
}
if let Some(Command::App { rune_args, .. }) = &cli.command
&& !rune_args.is_empty()
{
rune_ui::render_app(rune_args.clone()).run();
return Ok(());
}
let log_dir = app_paths::init_process_logging(&SHUTDOWN)?;
let event_path = log_dir.join("live.ndjson");
let bundled_service = executable_name.as_deref() == Some(std::ffi::OsStr::new("hex-service"));
let command = cli.command.unwrap_or({
if bundled_watcher {
Command::App {
hidden: false,
device: None,
rune_args: Vec::new(),
}
} else if bundled_service {
Command::Service { embedded: false }
} else {
Command::Listen { device: None }
}
});
match command {
Command::App { hidden, device, .. } => open_app(event_path, device, hidden),
Command::Listen { device } => {
let _instance = desktop::instance::acquire("listener")?;
labs::log_launch();
let settings = app_settings::AppSettings::load()?;
audio::hex_microphone::host_service();
let events = desktop::events::EventLog::create(&event_path)?;
let history = match history::History::open_default(settings.history_retention) {
Ok(history) => Some(history),
Err(error) => {
tracing::warn!(%error, "dictation history is unavailable");
None
}
};
recognition::listen(events, device.as_deref(), &SHUTDOWN, None, history, None)
}
Command::Service { embedded } => {
if !embedded {
app_settings::AppSettings::load()?;
}
local_api::run_service(&log_dir, event_path, embedded, &SHUTDOWN)
}
Command::Control(_) => unreachable!("handled before startup"),
#[cfg(hex_capture)]
Command::CapsulePlayground { .. } => unreachable!("handled before startup"),
Command::Dev { command } => {
#[cfg(debug_assertions)]
{
use control::developer_control::{DeveloperCommand, DeveloperReply};
let command = match command {
DevCommand::Dashboard => {
return control::dashboard::run(event_path);
}
DevCommand::Status => DeveloperCommand::Status,
DevCommand::Hud { state } => DeveloperCommand::Hud { state },
DevCommand::Show { pane } => DeveloperCommand::ShowPane { pane },
#[cfg(all(debug_assertions, hex_capture))]
DevCommand::CaptureReplay { wavs } => {
labs::log_launch();
app_settings::AppSettings::load()?;
let bundle = capture_host::replay(&wavs)?;
println!("{}", bundle.display());
return Ok(());
}
DevCommand::VoiceFilter { .. } => unreachable!("handled before startup"),
};
let reply = local_api::call_developer(&command)?;
if let DeveloperReply::Error { code, message } = &reply {
return Err(eyre!("{code}: {message}"));
}
println!("{}", serde_json::to_string_pretty(&reply)?);
Ok(())
}
#[cfg(not(debug_assertions))]
{
match command {
DevCommand::VoiceFilter { .. } => unreachable!("handled before startup"),
}
}
}
#[cfg(debug_assertions)]
Command::Meeting {
command: MeetingCommand::Record { title },
} => {
app_settings::AppSettings::load()?;
meeting::record(
meeting::MeetingStart {
title,
..Default::default()
},
&SHUTDOWN,
None,
)
.map(|_| ())
}
#[cfg(debug_assertions)]
Command::Meeting {
command: MeetingCommand::List,
} => {
for meeting in meeting::list()? {
println!(
"{}\t{:?}\t{}ms\t{}",
meeting.id,
meeting.status,
meeting.duration_ms.unwrap_or_default(),
meeting.title
);
}
Ok(())
}
#[cfg(debug_assertions)]
Command::Meeting {
command: MeetingCommand::Show { id },
} => {
print!("{}", meeting::show(&id)?);
Ok(())
}
#[cfg(debug_assertions)]
Command::Meeting {
command: MeetingCommand::Probe,
} => meeting::watcher::probe(),
#[cfg(debug_assertions)]
Command::Meeting {
command: MeetingCommand::Transcribe { id },
} => {
app_settings::AppSettings::load()?;
let manifest = meeting::transcribe_meeting(&id)?;
println!("{}", meeting::display_title(&manifest));
if let Some(path) = manifest.export_path {
println!("{}", path.display());
}
if let Some(error) = manifest.export_error {
println!("{error}");
}
Ok(())
}
#[cfg(debug_assertions)]
Command::Meeting {
command:
MeetingCommand::Summarize {
target,
excerpt_chars,
},
} => {
let path = PathBuf::from(&target);
if path.is_file() {
print!("{}", meeting::summarize_file(&path, excerpt_chars)?);
} else {
app_settings::AppSettings::load()?;
let manifest = meeting::summarize(&target)?;
println!("{}", meeting::display_title(&manifest));
if let Some(path) = manifest.export_path {
println!("{}", path.display());
}
if let Some(error) = manifest.notes_error {
println!("{error}");
}
}
Ok(())
}
#[cfg(debug_assertions)]
Command::Meeting {
command: MeetingCommand::Chapters { path },
} => {
print!("{}", meeting::chapters_file(&path)?);
Ok(())
}
#[cfg(debug_assertions)]
Command::Meeting {
command:
MeetingCommand::Echo {
directory,
microphone_offset_ms,
transcribe,
},
} => {
if transcribe {
app_settings::AppSettings::load()?;
}
print!(
"{}",
meeting::echo_report(&directory, microphone_offset_ms, transcribe)?
);
Ok(())
}
Command::BenchmarkTranscription {
manifest,
backend,
model,
warmups,
runs,
} => {
let backend = match backend {
TranscriptionBenchmarkBackend::Onnx => transcription::benchmark::Backend::Onnx,
TranscriptionBenchmarkBackend::TranscribeCpp => {
transcription::benchmark::Backend::TranscribeCpp {
model: match model {
Some(model) => model,
None => parakeet::default_model_path()?,
},
}
}
};
transcription::benchmark::run(&manifest, warmups, runs, backend)
}
}
}
#[cfg(target_os = "macos")]
fn open_app(event_path: PathBuf, device: Option<String>, hidden: bool) -> Result<()> {
let _instance = desktop::instance::acquire("listener")?;
labs::log_launch();
audio::hex_microphone::host_service();
macos_rune::run(
&SHUTDOWN,
meeting::watcher::ListenerConfig { event_path, device },
hidden,
)
}
#[cfg(target_os = "macos")]
fn mcp_alias_arguments(mut arguments: Vec<std::ffi::OsString>) -> Vec<std::ffi::OsString> {
let alias = arguments
.first()
.and_then(|program| std::path::Path::new(program).file_stem())
.is_some_and(|stem| stem == "hex-meetings-mcp");
if alias && arguments.get(1).is_none_or(|argument| argument != "mcp") {
arguments.insert(1, "mcp".into());
}
arguments
}
#[cfg(target_os = "macos")]
fn is_bundled_app_executable(name: Option<&std::ffi::OsStr>) -> bool {
name.is_some_and(|name| name == "hex")
}
#[cfg(target_os = "macos")]
fn opens_app(command: Option<&Command>, bundled_watcher: bool) -> bool {
match command {
Some(Command::App {
hidden, rune_args, ..
}) => !hidden && rune_args.is_empty(),
None => bundled_watcher,
Some(_) => false,
}
}
#[cfg(all(test, target_os = "macos"))]
mod tests {
use super::{Cli, Command, is_bundled_app_executable, mcp_alias_arguments, opens_app};
use crate::control::cli::ControlCli;
use clap::Parser;
#[test]
fn the_meetings_mcp_alias_always_serves_meetings() {
let parse = |arguments: &[&str]| {
Cli::parse_from(mcp_alias_arguments(
arguments.iter().map(Into::into).collect(),
))
.command
};
assert!(matches!(
parse(&["/Users/kit/.local/bin/hex-meetings-mcp"]),
Some(Command::Control(ControlCli::Mcp { meetings_dir: None }))
));
assert!(matches!(
parse(&["hex-meetings-mcp", "--meetings-dir", "/tmp/mirror"]),
Some(Command::Control(ControlCli::Mcp { meetings_dir: Some(directory) })) if directory.ends_with("mirror")
));
assert!(matches!(
parse(&["hex-meetings-mcp", "mcp"]),
Some(Command::Control(ControlCli::Mcp { .. }))
));
assert!(parse(&["hex"]).is_none());
}
#[cfg(debug_assertions)]
#[test]
fn developer_show_accepts_every_developer_pane_spelling() {
use super::DevCommand;
use crate::control::developer_control::DeveloperPane;
for (pane, expected) in [
("hud-lab", DeveloperPane::HudLab),
("history", DeveloperPane::History),
] {
let cli = Cli::try_parse_from(["hex", "dev", "show", pane]).unwrap();
let Some(Command::Dev {
command: DevCommand::Show { pane },
}) = cli.command
else {
panic!("expected dev show command");
};
assert_eq!(pane, expected);
}
assert!(Cli::try_parse_from(["hex", "dev", "show", "hudlab"]).is_err());
assert!(Cli::try_parse_from(["hex", "dev", "hud", "recording"]).is_ok());
}
#[test]
fn control_commands_parse_beside_the_read_commands() {
use crate::control::cli::CommandsCommand;
use crate::control::transcript_cli;
use clap::CommandFactory;
Cli::command().debug_assert();
let parse = |arguments: &[&str]| Cli::try_parse_from(arguments).map(|cli| cli.command);
#[cfg(hex_capture)]
{
assert!(matches!(
parse(&["hex", "capture", "stop", "--json"]),
Ok(Some(Command::Control(ControlCli::Annotate { command: crate::control::cli::CaptureCommand::Stop { output } }))) if output.json
));
assert!(matches!(
parse(&["hex", "capture", "send", "latest", "--to", "ses_1"]),
Ok(Some(Command::Control(ControlCli::Annotate {
command: crate::control::cli::CaptureCommand::Send { capture, to: Some(to), .. }
}))) if capture == "latest" && to == "ses_1"
));
assert!(matches!(
parse(&["hex", "capture", "send", "latest", "--always"]),
Ok(Some(Command::Control(ControlCli::Annotate {
command: crate::control::cli::CaptureCommand::Send {
to: None,
always: true,
..
}
})))
));
assert!(parse(&["hex", "capture", "route", "latest", "--explain"]).is_ok());
}
assert!(matches!(
parse(&["hex", "status", "--json"]),
Ok(Some(Command::Control(ControlCli::Status { output }))) if output.json
));
assert!(matches!(
parse(&["hex", "dictation", "toggle"]),
Ok(Some(Command::Control(ControlCli::Dictation { .. })))
));
assert!(matches!(
parse(&[
"hex",
"settings",
"set",
"release_microphone_while_idle",
"on"
]),
Ok(Some(Command::Control(ControlCli::Settings { .. })))
));
assert!(matches!(
parse(&["hex", "window", "show", "history"]),
Ok(Some(Command::Control(ControlCli::Window { .. })))
));
for verb in ["reset", "restart"] {
assert!(matches!(
parse(&["hex", "onboarding", verb]),
Ok(Some(Command::Control(ControlCli::Onboarding { .. })))
));
}
assert!(matches!(
parse(&["hex", "history", "clear", "--yes"]),
Ok(Some(Command::Control(ControlCli::History(
transcript_cli::HistoryArgs {
action: Some(transcript_cli::HistoryAction::Clear { yes: true, .. }),
..
}
))))
));
assert!(matches!(
parse(&["hex", "history", "--json"]),
Ok(Some(Command::Control(ControlCli::History(
transcript_cli::HistoryArgs { action: None, .. }
))))
));
assert!(parse(&["hex", "history", "--limit", "5", "clear", "--yes"]).is_err());
assert!(matches!(
parse(&["hex", "meetings", "record", "--title", "Design sync"]),
Ok(Some(Command::Control(ControlCli::Meetings {
command: transcript_cli::MeetingsCommand::Record { title: Some(_), .. }
})))
));
assert!(matches!(
parse(&["hex", "commands", "check"]),
Ok(Some(Command::Control(ControlCli::Commands {
command: CommandsCommand::Check
})))
));
assert!(parse(&["hex", "meetings", "cancel"]).is_err());
assert!(matches!(
parse(&[
"hex",
"vocabulary",
"add",
"cappuccino",
"--sounds-like",
"capuchin,cap a chino"
]),
Ok(Some(Command::Control(ControlCli::Vocabulary {
command: crate::control::cli::VocabularyCommand::Add {
sounds_like: Some(_),
..
}
})))
));
assert!(matches!(
parse(&["hex", "vocabulary", "list", "--json"]),
Ok(Some(Command::Control(ControlCli::Vocabulary {
command: crate::control::cli::VocabularyCommand::List { output }
}))) if output.json
));
assert!(parse(&["hex", "vocabulary", "remove"]).is_err());
}
#[test]
fn labs_parse_as_a_read_command() {
use super::labs::LabsCommand;
assert!(matches!(
Cli::try_parse_from(["hex", "labs", "on", "live-text"]).map(|cli| cli.command),
Ok(Some(Command::Control(ControlCli::Labs {
command: Some(LabsCommand::On { feature })
}))) if feature == "live-text"
));
assert!(matches!(
Cli::try_parse_from(["hex", "labs"]).map(|cli| cli.command),
Ok(Some(Command::Control(ControlCli::Labs { command: None })))
));
assert!(Cli::try_parse_from(["hex", "labs", "off"]).is_err());
#[cfg(target_os = "macos")]
assert!(matches!(
Cli::try_parse_from(["hex", "labs", "link", "live-text"]).map(|cli| cli.command),
Ok(Some(Command::Control(ControlCli::Labs {
command: Some(LabsCommand::Link { feature })
}))) if feature == "live-text"
));
assert!(matches!(
Cli::try_parse_from(["hex", "experiments", "enable", "live-text"])
.map(|cli| cli.command),
Ok(Some(Command::Control(ControlCli::Experiments { .. })))
));
}
#[test]
fn the_packaged_executable_launches_the_app() {
assert!(is_bundled_app_executable(Some("hex".as_ref())));
assert!(!is_bundled_app_executable(Some("hex-service".as_ref())));
}
#[test]
fn only_app_launches_prewarm_the_gpu() {
let opens = |arguments: &[&str]| {
let arguments = mcp_alias_arguments(arguments.iter().map(Into::into).collect());
let bundled =
is_bundled_app_executable(std::path::Path::new(&arguments[0]).file_stem());
opens_app(Cli::parse_from(arguments).command.as_ref(), bundled)
};
assert!(opens(&["hex"]));
assert!(opens(&["/Applications/Hex.app/Contents/MacOS/hex"]));
assert!(opens(&["hex", "app"]));
assert!(opens(&["hex", "app", "--device", "MacBook Pro Microphone"]));
assert!(!opens(&["hex", "app", "--", "--snap", "/tmp/a.png"]));
assert!(!opens(&["hex", "app", "--hidden"]));
assert!(!opens(&["hex-service"]));
assert!(!opens(&["hex-meetings-mcp"]));
for command in [
&["status"][..],
&["quit"],
&["mcp"],
&["history"],
&["listen"],
&["meetings", "list"],
&["settings", "list"],
&["capture", "status"],
&["dev", "dashboard"],
] {
let arguments = [&["hex"][..], command].concat();
assert!(!opens(&arguments), "{command:?}");
}
}
}
#[cfg(target_os = "linux")]
fn main() -> Result<()> {
#[cfg(target_arch = "x86_64")]
transcription::cpu_floor::require();
linux::input_route::mark_streams();
linux::run(&SHUTDOWN)
}
#[cfg(target_os = "windows")]
fn main() -> Result<()> {
let dll_search = windows::restrict_dll_search();
windows::attach_parent_console();
if let Err(error) = dll_search {
eprintln!("hex: SetDefaultDllDirectories failed: {error}");
}
windows::vulkan::check();
windows::gpu_fallback::choose();
#[cfg(target_arch = "x86_64")]
transcription::cpu_floor::require();
windows::run(&SHUTDOWN)
}
#[cfg(test)]
mod code_shape_tests;