Talk to Omi from your terminal. Designed for humans and agents.
omi-cli is the command-line interface to the Omi developer
API. It exposes scoped, agent-friendly verbs for the four primary nouns Omi
maintains about you:
- memories — facts and learnings the system knows about you
- conversations — captured & processed audio/text exchanges
- action items — tasks and follow-ups
- goals — tracked progress metrics
It's intentionally small, scriptable, and JSON-first — everything you need to plug Omi into shell pipelines, CI jobs, agent harnesses, or just your own personal automation.
- PyPI: pypi.org/project/omi-cli
- Docs: docs.omi.me/doc/developer/cli/introduction
- Source: github.com/BasedHardware/omi/tree/main/sdks/python-cli
pipx install omi-cli # recommended — isolated install
# or
pip install omi-cliAfter install, the binary on your $PATH is named omi:
omi --version
omi --helpThe PyPI distribution name is
omi-cli(the bareomislot belongs to an unrelated package). The console command isomiregardless.
# 1. Log in. With no flags, omi-cli asks how you want to authenticate:
omi auth login
# → 1) Browser — sign in with Google or Apple (recommended for humans)
# → 2) API key — paste a developer key from app.omi.me (recommended for agents/CI)
# 2. Start using it:
omi memory list
omi conversation list --limit 5
omi action-item list --open
omi goal listPass --json to any command (as a global flag, before the verb) to get
machine-readable output, ready for jq, agent harnesses, or whatever else:
omi --json memory list | jq '.[] | {id, content}'Pretty output displays returned text literally, including square brackets and
emoji-like codes such as :warning:. Styling applies to the table layout, not
to the contents of your memories or conversations.
Two auth methods, both fully wired:
| Method | Best for | How to use |
|---|---|---|
Dev API key (omi_dev_*) |
Agents, CI, headless, scoped permissions | omi auth login --api-key ... or env var |
| Firebase OAuth (Google/Apple) | Humans on a laptop | omi auth login --browser |
The browser flow opens your default browser for OAuth, captures the code on a localhost callback, and stores a Firebase ID token + refresh token. The ID token is auto-refreshed before each request when it's near expiry — you shouldn't need to think about it.
omi auth login # interactive picker (browser or key)
omi auth login --browser # force OAuth (default provider: google)
omi auth login --browser --provider apple
omi auth login --api-key K # force API-key path
omi auth login < key.txt # piped key, useful in CI
omi auth status # show profile + masked credential + expiry
omi auth whoami # round-trip to verify the credential works
omi auth refresh # force a Firebase refresh (no-op for API keys)
omi auth logout # wipe the credentialAn API-key login candidate is checked before replacing the saved credentials. If verification rejects it with HTTP 401 or 403, the existing profile and active profile selection remain unchanged. Other HTTP errors retain the existing store-and-warn behavior. A transport failure leaves saved credentials unchanged; browser OAuth is a separate flow.
You can also set OMI_API_KEY in the environment to bypass on-disk config
entirely — handy in containers and CI:
export OMI_API_KEY=omi_dev_...
omi memory listState lives at ~/.omi/config.toml (overridable via $OMI_CONFIG). The file
holds one or more named profiles, each with its own auth method and API base.
Saving configuration preserves unknown settings at both the root and profile
levels, so editing a known setting does not discard extensions from newer clients.
Switch between them with --profile:
omi config profile use work
omi auth login # logs in the active profile (work)
omi --profile personal memory listCommon config:
omi config show
omi config path
omi config set api_base https://api.staging.omi.me
omi config set local_api_url http://127.0.0.1:47778
omi config set local_token ...
omi config profile list
omi config profile delete old-account --yesomi local talks to a running Omi Desktop local API. Configure the active
profile once, or use env vars for ephemeral agent sessions:
omi local configure --url http://127.0.0.1:47778 --token ...
export OMI_LOCAL_API_URL=http://127.0.0.1:47778
export OMI_LOCAL_TOKEN=...Common local tools:
omi --json local status
omi --json local tools
omi --json local call search_screen_history --args-json '{"query":"pricing page","days":7}'
omi --json local search-screen "pricing page" --days 7 --app Safari
omi --json local screenshot 123 --output /tmp/omi-shot.jpg
omi --json local recap --days-ago 1
omi --json local sql "SELECT appName, COUNT(*) FROM screenshots GROUP BY appName"
omi --json local task search "taxes" --include-completedAgent screen-history workflow:
- Check availability with
omi --json local status; look forscreen_history_available,screenshot_count, andindexed_screenshot_count. - Discover tool schemas with
omi --json local tools. - Search OCR/screen history with
omi --json local search-screen "query" --days 7or run exact SQL againstscreenshotswhen you need app/window filters. - Use a returned
screenshot_idwithomi --json local screenshot <id> --output /tmp/omi-shot.jpg. - Validate the file before handing it to vision tooling, for example
file /tmp/omi-shot.jpg.
When semantic search returns no results, JSON mode also tries a literal
substring search across app names, window titles, and OCR text. In this
fallback, % and _ in the query or --app filter match those characters
literally, rather than acting as SQL wildcards.
If pixels are not available, JSON-mode errors preserve Desktop's structured
fields such as status_code, error, reason, hint, and screenshot_id.
For example, screenshot_pending means the frame is still in the active video
segment; retry shortly or choose an older screenshot ID from search results.
Task writes should only run after the user clearly asks for that change:
omi --json local task complete task_123
omi --json local task delete task_123 --yesThe full tree (run omi --help for the live version):
omi
├── auth
│ ├── login [--browser] [--api-key KEY]
│ ├── logout
│ ├── status
│ ├── whoami
│ └── refresh
├── config
│ ├── show
│ ├── path
│ ├── set <key> <value>
│ └── profile
│ ├── list
│ ├── use <name>
│ └── delete <name>
├── memory
│ ├── list [--limit N] [--offset N] [--categories ...]
│ ├── get <id>
│ ├── create <content> [--category ...] [--visibility ...] [--tag ...]
│ ├── update <id> [--content ...] [--category ...] [--visibility ...] [--tag ...]
│ └── delete <id> [-y]
├── conversation
│ ├── list [--limit N] [--start-date ...] [--end-date ...] [--include-transcript]
│ ├── get <id> [--include-transcript]
│ ├── create [--text ...] [--text-source ...] [...]
│ ├── from-segments <file.json> [--source ...]
│ ├── update <id> [--title ...] [--discarded/--no-discarded]
│ └── delete <id> [-y]
├── action-item
│ ├── list [--completed/--open] [--conversation-id ...] [...]
│ ├── get <id>
│ ├── create <description> [--due-at ...]
│ ├── update <id> [--description ...] [--completed/--open] [--due-at ...]
│ ├── complete <id>
│ └── delete <id> [-y]
├── local
│ ├── configure --url URL --token TOKEN
│ ├── status
│ ├── tools
│ ├── call <tool> [--args-json JSON]
│ ├── search-screen <query> [--days N] [--app NAME]
│ ├── screenshot <id> [--output PATH]
│ ├── recap [--days-ago N]
│ ├── sql <query>
│ └── task
│ ├── search <query> [--include-completed]
│ ├── complete <id>
│ └── delete <id> [-y]
└── goal
├── list [--limit N] [--include-inactive]
├── get <id>
├── create <title> --target N [--type ...] [--current N] [--unit ...]
├── update <id> [--unit ... | --clear-unit] [...]
├── progress <id> <value>
├── history <id> [--days N]
└── delete <id> [-y]
conversation from-segments reads JSON files as UTF-8 (with or without a BOM),
UTF-16, or UTF-32, independently of the system's default text encoding.
--json Emit JSON to stdout (machine-readable, agent-friendly).
--profile, -p NAME Use a specific profile.
--api-base URL Override the API base URL.
--verbose, -v Log HTTP traffic to stderr.
--no-color Disable colored output (also honors $NO_COLOR).
--version Print the version.
--help Show contextual help.
0 success
1 usage error (bad flags, missing args, validation)
2 auth error (no creds, expired token, insufficient scope)
3 server error (5xx, connection failure)
4 rate limited (429) — retry recommended
5 not found (404)
The CLI is built so an LLM can use it without a wrapper:
--jsonreturns valid JSON to stdout. Nothing else writes to stdout in JSON mode (errors go to stderr as{"error": "...", "detail": "..."}).- Stable exit codes (above) let an agent disambiguate retryable vs terminal errors.
- Successful resource
delete --yescommands preserve the API response in JSON mode. A successful response without a body is emitted as JSONnull. - Rate-limit errors include a
Retry-Afterwindow in the message and surface the policy name (dev:conversations, etc.) so an agent can back off intelligently. OMI_API_KEYandOMI_API_BASEenv vars work without any priorauth login.OMI_LOCAL_API_URLandOMI_LOCAL_TOKENoverride profile-local Desktop API settings foromi local.
See examples/agent_quickstart.md for a worked
example.
The dev API enforces per-policy hourly limits:
| Policy | Limit |
|---|---|
dev:conversations |
25/hour |
dev:memories |
120/hour |
dev:memories_batch |
15/hour |
The CLI retries 429 automatically with exponential backoff and honors the
server's Retry-After hint where present. After all retries are exhausted you
get exit code 4 plus a message telling you how long to wait.
Conversation and action-item datetime options accept ISO timestamps with Z
(UTC), numeric offsets, and optional fractional seconds, for example
--due-at 2026-09-08T12:30:00Z or
--start-date 2026-09-08T12:30:00.123456+05:30. Offsets are preserved in API
requests. Date-only values and timestamps without an offset remain supported;
the CLI does not assign a timezone to those inputs.
# Editable install with dev extras
pip install -e .[dev]
# Run the test suite
pytest -q
# Lint
black --check --line-length 120 --skip-string-normalization sdks/python-cli/
mypy omi_cli
# Build a wheel + sdist (no upload, no tag)
bash release.sh --build-onlyMIT — see LICENSE.