Skip to content

Repository files navigation

gmkit

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. See docs/research/phase-1-libgmessages.md for the design notes that motivated this layout, and skills/README.md for the skill installation guide.

What it is

  • 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/libgm from mautrix/gmessages, which is licensed AGPL-3.0. That makes gmcli a derivative work and obligates the same license for the whole program. See LICENSE and NOTICE.

How it works

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).

Install

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 | bash

Or 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 gmtui

For 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/gmcli

Pre-built binary distribution and Homebrew packaging are planned after the initial beta releases.

Current limits

  • 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 libgm reverse-engineered Google Messages web protocol and can break if Google changes that protocol.

Quick start

# 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'

Daemon, TUI, and agents

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 connection

Auto-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 mcp

The 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.

Global flags

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).

Layout

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

LLM integration

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.

Privacy

  • All data is local. gmcli does not phone home.
  • Session tokens are stored in $XDG_STATE_HOME/gmcli/session.json with 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.).

Attribution

  • 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.

License

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.

About

Google Messages for your terminal and your AI agents — CLI, sync daemon, MCP server, and a Rust TUI with a human approval queue for agent sends

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages