Google Messages for your terminal and your AI agents — local-first, with a human approval queue for anything an agent wants to send.
gmkit ships one standalone Go binary, gmcli (CLI, sync daemon, and MCP
server), that pairs with Google Messages and archives conversations into a
local SQLite + FTS5 database, plus gmtui (gmtui/), a Rust
terminal UI. gmkit builds on
fdsouvenir/gmcli and extends it with
the daemon, agent, and TUI layers.
┌───────────────────────────────┐
│ gmcli serve (Go daemon) │
│ libgm session · sync · SQLite │
└──────────────┬────────────────┘
│ JSON-RPC over unix socket
┌──────────────────┼─────────────────────┐
│ │ │
gmtui (Rust TUI) gmcli mcp gmcli CLI + --json
human cockpit + MCP tools for scripts and ad-hoc
send approvals Claude & agents agent use
Status: beta. Pairing, session persistence, sync loop, query CLI (
messages,contacts,chats), best-effort history backfill, send commands, media download, and an LLM skill (skills/google-messages) are wired up and covered by the automated test suite. Live-device behavior still depends on the unofficial Google Messages web protocol, so validate auth, sync, history, media, and send flows on your own account before relying on unattended operation. Seedocs/research/phase-1-libgmessages.mdfor the design notes that motivated this layout, andskills/README.mdfor the skill installation guide.
- Standalone. No Matrix server, no Docker, no bridge daemon. Just a Go binary, a SQLite file, and a phone running Google Messages.
- Read-first. Phone-mutating operations (sending texts and reactions) are gated behind explicit flags. The default is to observe, not to send.
- Local. Messages live in a single SQLite database under your data directory (XDG-compliant). Nothing is uploaded anywhere.
- AGPL-3.0. gmcli imports
pkg/libgmfrom mautrix/gmessages, which is licensed AGPL-3.0. That makes gmcli a derivative work and obligates the same license for the whole program. SeeLICENSEandNOTICE.
pkg/libgm reverse-engineers the Google Messages web client protocol. After a
one-time QR pairing handshake, it maintains an authenticated session with
your paired phone — all messages flow through the phone, which proxies them
to Google's relay infrastructure. gmcli wraps that session with an event loop
that writes incoming messages, conversation updates, and contact data to a
local SQLite database, and exposes the database through a CLI.
The phone must be online and have Google Messages installed for the relay to work. Pairing tokens are refreshed automatically; full re-pairing is required roughly every 14 days of inactivity (Google's policy, not ours).
No toolchains needed — the installer downloads prebuilt binaries (macOS and Linux, arm64 and x86_64) from GitHub Releases:
curl -fsSL https://raw.githubusercontent.com/johnlindquist/gmkit/main/install.sh | bashOr from a clone: ./install.sh. Binaries land in ~/.local/bin (override
with GMKIT_BIN_DIR). To build from source instead, pass --source
(requires Go 1.24+; Rust optional, for gmtui). Manual equivalents:
go install github.com/johnlindquist/gmkit/cmd/gmcli@latest
cargo install --git https://github.com/johnlindquist/gmkit gmtuiFor a source build whose gmcli version output includes the current tag or
commit, inject it at link time:
go build -ldflags "-X github.com/johnlindquist/gmkit/internal/cmd.Version=$(git describe --tags --always --dirty)" -o gmcli ./cmd/gmcliPre-built binary distribution and Homebrew packaging are planned after the initial beta releases.
- Live-device coverage is still limited. Before relying on gmcli unattended,
test
auth,sync, query commands,history backfill,media download, and a deliberate send with your own Google Messages account. - History backfill is best-effort and depends on what Google Messages returns through the paired phone.
- The phone must be online for sync, backfill, sends, and media downloads.
- The SQLite database is local but unencrypted. Use filesystem encryption if you need at-rest protection.
- The protocol depends on the unofficial
libgmreverse-engineered Google Messages web protocol and can break if Google changes that protocol.
# 1. One-time pairing (renders a QR code in the terminal — scan with the
# Google Messages app on your phone, Settings → Device pairing → QR code).
gmcli auth
# 2. Sync messages from the phone into the local database. --follow keeps
# the connection open and writes new messages as they arrive.
gmcli sync --follow
# 3. Query the local archive (read-only).
gmcli chats list # most-recent conversations
gmcli chats show <conversation-id> # header + recent messages
gmcli messages search "dinner" # FTS5 across all conversations
gmcli messages list --conv <conv-id> # message list with filters
gmcli messages show <message-id> # single message detail
gmcli messages context <message-id> # surrounding messages
gmcli contacts search alice # name/number/alias substring match
gmcli contacts show <participant-id-or-num> # contact detail
# 4. Local-only labels.
gmcli contacts alias set --id <pid> --alias "Mom"
gmcli contacts alias list # list all set aliases
gmcli contacts alias rm --id <pid>
# 5. Best-effort history backfill, modeled after wacli.
gmcli history backfill --chat <conv-id> --requests 10 --count 50
# JSON output reports protocol records separately from the chat message delta:
# fetched_messages, sync_records_processed, messages_before, messages_after,
# messages_added_for_chat.
# 6. Write to the phone (always requires --read-only=false).
gmcli --read-only=false send text --to <conv-id> --message "on my way"
gmcli --read-only=false send react --message <msg-id> --emoji "👍"
gmcli media download --message <msg-id>
# `send text` only reports success after Google Messages echoes the outgoing
# message back with its canonical message_id.
# Every command supports --json for machine-readable output and --full to
# disable truncation in tables.
gmcli --json chats list | jq '.[0].name'The daemon keeps the session alive, streams events into the archive, and
exposes a JSON-RPC surface over a unix socket (<store>/gmcli.sock,
documented in docs/rpc-protocol.md). You
normally never start it yourself: gmtui, gmcli mcp, and gmcli approvals auto-start it on demand (serve --auto) and it exits on its own
after ten minutes without clients. Run it manually only when you want
different behavior:
gmcli serve # always-on, read-only: query surface only
gmcli --read-only=false serve # always-on with sends (approval queue)
gmcli serve --offline # archive queries without a phone connectionAuto-started daemons use the approval queue for sends — safe by design,
since nothing reaches the phone without a human approving — and log to
<store>/daemon.log.
gmtui (gmtui/) is a Rust/ratatui terminal client:
conversation browser, live message stream, full-text search, compose, and
the approval queue UI. After pairing once with gmcli auth, running gmtui
is all a new user needs — it brings the daemon up itself.
cd gmtui && cargo install --path .
AI agents connect through gmcli mcp, an MCP (Model Context Protocol)
stdio server. Read tools (find_chats, search_messages, list_chats,
list_messages, get_message_context, search_contacts, get_status)
work standalone against the local archive; send_text and
backfill_history go through the daemon (auto-started as needed). Search is
agent-friendly: natural-language queries fall back gracefully from FTS5
syntax to literal AND then OR term matching, results carry sender and
conversation names plus ISO timestamps, and since/until accept ISO dates
or relative durations like 7d. Register with your agent runtime, e.g. for
Claude Code:
claude mcp add google-messages -- gmcli mcpThe approval queue is the piece that makes agent sends safe: under the
default daemon policy (--sends approve), an agent's send_text only
proposes a message. It lands in a local queue, gmtui pops a warning, and a
human approves (y in gmtui, or gmcli approvals approve <id>) or denies
it. Nothing touches your phone until then. --sends direct skips the queue
for people who trust their agents; every send still leaves an audit row.
gmcli approvals list # review the queue headlessly
gmcli --read-only=false approvals approve <id> # approve = send
gmcli approvals deny <id> --reason "nope"While serve is running, prefer the socket/MCP surface over gmcli sync/send/history CLI commands on the same store — the daemon owns the
one allowed session to your phone.
| Flag | Default | Purpose |
|---|---|---|
--store DIR |
$XDG_STATE_HOME/gmcli |
Where session, SQLite, and downloaded media live. |
--read-only |
true |
Block commands that send texts or reactions through the phone. |
--json |
false |
Emit machine-readable output. |
--full |
false |
Disable truncation in tabular output. |
--log-level |
info |
Verbosity (trace/debug/info/warn). |
cmd/ Cobra command tree (auth, sync, serve, mcp, approvals,
version, doctor, messages, contacts, chats, send, media)
internal/
gm/ libgm wrapper — pairing, session, events, send/react,
WaitForReady, DownloadMedia
store/ SQLite + FTS5 store (schema v3: aliases + approvals)
sync/ Event-to-store pump
history/ Best-effort backfill engine (shared by CLI and daemon)
rpc/ Daemon: JSON-RPC over unix socket, event stream,
approval queue (docs/rpc-protocol.md)
output/ Shared JSON / tab-aligned table renderers
paths/ XDG path resolution (XDG_STATE_HOME)
logging/ zerolog setup
gmtui/ Rust ratatui terminal client for the daemon
skills/
google-messages/ LLM skill bundle - archive playbook for assistants
docs/research/ Phase 1 research notes
docs/rpc-protocol.md Daemon socket protocol reference
The first-class integration is gmcli mcp (see “Daemon, TUI, and agents”
above). The bundled OpenClaw skill lives in skills/google-messages. It is
published on ClawHub as
Google Messages Local Archive
(google-messages-local-archive) for searching, summarizing, and answering
questions from a local Google Messages SMS/RCS archive with read-only commands
by default.
- All data is local. gmcli does not phone home.
- Session tokens are stored in
$XDG_STATE_HOME/gmcli/session.jsonwith mode 0600. - Media attachments are referenced by ID in the database; bytes are not
downloaded by default. Use
gmcli media download --message <message-id>for explicit downloads. - The SQLite file is unencrypted. If you need at-rest encryption, layer your own filesystem encryption (FileVault, LUKS, etc.).
- libgm — the Google Messages protocol library this CLI depends on — was written by Tulir Asokan and the mautrix contributors. License: AGPL-3.0. gmcli would not be possible without their reverse-engineering work.
- The CLI verb structure is inspired by Peter Steinberger's wacli for WhatsApp.
- Storage and MCP-tool patterns draw from openmessage by Max Ghenis, released under the Unlicense.
GNU Affero General Public License, version 3 or later. See LICENSE for the
full text and NOTICE for the third-party notices required by upstream
licenses.