guide
Send, cast and script
Four ways to type through omni: one command at a time, a stream from standard input, a macro file with delays, or an agent over MCP. All of them reach the same targets.
Install
omni is a single Rust binary built with Nix. Add the flake as an input and install its package, or let a machine configuration do it:
inputs.omni-cli.url = "github:harryaskham/omni-cli";# packages.${system}.omni-cli provides bin/omni
Releases are tagged and built on self-hosted runners; an installed binary updates itself from them with omni update. To run omni as a service on a desktop or relay host, use the NixOS and nix-darwin modules.
omni statusomni v0.3.0update repo: harryaskham/omni-clifeedback: true (stderr)
One command: send
omni send takes a command in the same words a macro uses: text, key, combo, scroll or event. Where it goes depends on the flags:
| Flags | Delivers to |
|---|---|
| (none, no default target) | Standard output, as one NDJSON line |
--target NAME | A named target or group from the configuration (or its default_target) |
--adb [--serial S] | An Android device over adb, as a broadcast to the omni keyboard |
--relay HOST:PORT --device ID | A device connected to a relay hub (all devices if --device is omitted) |
--daemon HOST:PORT | A desktop running omni daemon |
--dry-run | Nothing: prints the adb commands it would run |
omni send text "hello"omni send --adb --serial emulator-5554 key enteromni send --relay hub:8765 --device quill --require-delivery text "hi"
With --require-delivery, a relay cast that reached no device exits with code 3, so a script can tell "device offline" apart from success.
A stream: cast
omni cast reads standard input and sends as it goes, keeping one connection open. Input is framed one of three ways:
--frame | Each | Becomes |
|---|---|---|
line (default) | line | a text command |
char | character, as typed | a key or text command, in real time |
ndjson | line | a wire command, passed through |
echo "dictated paragraph" | omni cast --target phoneomni cast --frame char --daemon 127.0.0.1:8766 # type live into a desktop
Streaming casts do not wait for delivery receipts, to keep latency low.
Macros: run
A macro is a plain-text file of directives, one per line, run in order against the same targets as send. Each line is either a command (text, key, combo, scroll) or wait ms. Consecutive commands are dispatched together; only wait lines pause. Blank lines and # comments are ignored.
# a login: wait for the password field to appeartext quillkey tabwait 150combo ctrl,a,g
# with no target configured, a macro prints its wire lines omni run login.omni{"v":1,"type":"text","text":"quill"} {"v":1,"type":"key","key":"tab"} {"v":1,"type":"combo","combo":"ctrl,a,g"} omni run --dry-run login.omniadb shell 'am broadcast -a com.harryaskham.omni.INJECT --es type text --es text quill --es sender_package com.harryaskham.omnicli'adb shell 'am broadcast -a com.harryaskham.omni.INJECT --es type key --ei keycode 61 --ei meta_state 0 --es sender_package com.harryaskham.omnicli'wait 150msadb shell 'am broadcast -a com.harryaskham.omni.INJECT --es type key --ei keycode 29 --ei meta_state 4096 --es sender_package com.harryaskham.omnicli'adb shell 'am broadcast -a com.harryaskham.omni.INJECT --es type key --ei keycode 35 --ei meta_state 4096 --es sender_package com.harryaskham.omnicli'
Without a target, a macro prints its wire lines, so omni run macro.omni | … composes with anything that reads NDJSON. - reads the macro from standard input.
Targets and groups
Connections are named in ~/.config/omni/config.yaml (or $OMNI_CONFIG, or --config). Each target is either one transport or a group of other targets, which casts to all of them.
default_target: phonetargets: phone: transport: { type: adb-broadcast, serial: emulator-5554 } desk: transport: { type: tcp, host: 127.0.0.1, port: 8766 } tablet: transport: { type: relay, url: tcp://hub.example:8765, device: quill, token_env: OMNI_RELAY_TOKEN } everywhere: group: [phone, desk, tablet]
omni config --target everywhereresolved --target everywhere -> 3 endpoint(s): phone [adb-broadcast] adb device=emulator-5554 sender_package=com.harryaskham.omnicli desk [tcp] tcp 127.0.0.1:8766 tablet [relay] relay tcp://hub.example:8765 device=quill
Relay tokens are never written in the file: token_env names the environment variable that holds one. omni config --example prints an annotated starter file, and omni config --schema its JSON Schema for editors. The reference lists every transport.
Health and completions
omni doctor runs read-only checks and prints one line per probe. It fails only on a real failure; warnings are advice.
omni doctoromni doctor — omni v0.3.0 [ok ] binary: omni v0.3.0 [ok ] config: 4 target(s) at ~/.config/omni/config.yaml [warn] adb: not on PATH — `omni send --adb` needs the Android platform-tools `adb` [ok ] feedback: routing to stderr [ok ] update: self-update from harryaskham/omni-clihealthy (1 warning(s))
--probe adds bounded reachability checks (attached adb devices, and a 500 ms connection attempt to each TCP and relay target), and --json gives a structured report. For shell completion, omni completions bash|zsh|fish|powershell|elvish prints a static script; registering the dynamic completer also completes your target names after --target:
source <(COMPLETE=bash omni)