- Scheme 86.2%
- Shell 7.5%
- Makefile 3.4%
- C 2.8%
| .builds | ||
| .jerboa | ||
| docs | ||
| scripts | ||
| signal | ||
| support | ||
| tests | ||
| .gitignore | ||
| .jerbuild | ||
| .jpkgignore | ||
| AGENTS.md | ||
| handoff-corruption.md | ||
| jpkg.lock | ||
| jpkg.policy.sexp | ||
| jpkg.sexp | ||
| kimi3.md | ||
| LICENSE | ||
| Makefile | ||
| no-blinky.md | ||
| README.md | ||
| SECURITY.md | ||
| signal.md | ||
jerboa-signal
A Signal client written in Jerboa, driving
signal-cli (Java) as a JSON-RPC
subprocess. All orchestration, arg parsing, and message routing is Jerboa;
the Signal protocol itself is delegated to signal-cli's libsignal-service-java.
What it does
Bidirectional bridge between Signal and your shell:
jerboa-signal send [-a +PHONE] RECIPIENT MESSAGE # send one message, exit
jerboa-signal listen [-a +PHONE] # stream inbound as NDJSON
jerboa-signal tui [-a +PHONE] # terminal UI shell
-a is required only if multiple Signal accounts are linked to signal-cli.
Prerequisites
signal-cli0.14.5 or newer onPATH(Homebrew may lag; the localscripts/signal-cli-jvmwrapper uses~/.local/opt/signal-cli-0.14.5). Older 0.14.4.1 builds drop current sealed-sender receive envelopes withgetServerGuid(...) must not be null. Release evidence currently reviews exactlysignal-cli 0.14.5; a newer installed version must be reviewed before publication.- Jerboa build tools.
make binary/make installuse project-local./jerbuildfirst, then.jerboa/bin, thenPATH, and download the matching release artifact if none are available.
One-time setup: link as a secondary device
jerboa-signal expects signal-cli to already be linked. Run this once:
signal-cli link -n "jerboa-signal"
It prints a sgnl://linkdevice?uuid=... URI. On your phone:
Signal → Settings → Linked devices → "+" → scan/paste the URI. signal-cli
will register the device and exit. Verify:
signal-cli listAccounts
Usage
make install # installs ~/.local/bin/jerboa-signal
jerboa-signal send +15551234567 "hello world"
jerboa-signal listen | jq . # pretty-print incoming envelopes
jerboa-signal --trace /tmp/jerboa-signal.trace tui
Without make install, run via the Makefile:
make run ARGS="send +15551234567 hello"
make run ARGS="listen"
make run-tui
The TUI needs the small termbox2 shim:
make tui-shim
./jerboa-signal tui
The TUI opens signal-cli jsonRpc --receive-mode manual, then subscribes with
subscribeReceive once the app reader is active, so new Signal events arrive
while it is running without racing the UI startup. At startup it preloads
active groups and known contacts into the conversation pane. The current interface keeps
in-memory conversations for this session, routes incoming
messages, edits, reactions, deletes, calls, receipts, and typing events into
threads, and sends plain text from the
composer to the selected conversation with Enter. Use Up/Down to change the
selected conversation. Use Ctrl-N to open the new-message picker, type to
filter contacts/groups or enter a +number, and press Enter to select. Use
Ctrl-F to search message history across all conversations at once: type a
query, use Up/Down to move through matches, and press Enter to jump to the
conversation holding that message. Search matches message text and sender, and
covers everything received or sent during the current session — history is
in-memory only, so it does not include messages from before the TUI was
opened. Use
Ctrl-D to delete the selected conversation; a y/n prompt confirms first. For a
direct contact this calls signal-cli removeContact --forget, which erases all
local Signal data for that contact (identity keys, sessions, profile) — use it
to remove someone entirely. For a group it just drops the conversation from the
list. Deleted conversations are remembered in
~/.local/share/jerboa-signal/removed-<account>.txt and stay hidden across
relaunches, even though signal-cli (a linked device) keeps re-syncing your
phone's contacts. If a removed person messages you again the thread reappears;
to remove someone from your Signal account everywhere, delete them on your phone
too. Use Ctrl-Q or Ctrl-C to quit.
Debug tracing
Runtime tracing is opt-in and writes a chmod 600 file:
jerboa-signal --trace /tmp/jerboa-signal.trace tui
The default trace records safe internal telemetry such as send phases, timings, byte counts, log backend selection, history replay counts, and encrypted log persistence stages. It does not include message bodies or raw Signal RPC frames.
For maximum debugging detail, including raw RPC frames and signal-cli verbose stderr, use:
jerboa-signal --trace-all /tmp/jerboa-signal.raw.trace tui
That file is sensitive: it can include message content and Signal identifiers. To force the encrypted log to be rewritten after every row while debugging durability issues, use:
jerboa-signal --trace-all /tmp/jerboa-signal.raw.trace --log-persist immediate tui
The default is batch: the encrypted container is rewritten after 64 rows,
1 MiB of new retained payload, or two seconds, whichever comes first, and at
clean close. This avoids whole-database work for every message while bounding
the crash window. --log-persist immediate forces a rewrite after every row;
--log-persist close persists only at clean exit. Operators can lower the
bounded batch thresholds with JERBOA_SIGNAL_LOG_BATCH_ROWS,
JERBOA_SIGNAL_LOG_BATCH_BYTES, and JERBOA_SIGNAL_LOG_BATCH_MS.
Security
jerboa-signal is not a Signal protocol implementation. It delegates Signal
cryptography, account state, server communication, and device linking to
signal-cli. The local security boundary is the Jerboa orchestration layer:
JSON-RPC framing, trace redaction, attachment export, encrypted local message
logs, terminal UI FFI, and process execution.
Before using this on production accounts, read SECURITY.md,
docs/threat-model.md, docs/credential-handling.md, and
docs/ffi-boundary.md. Run make verify from a clean checkout and preserve
make release-evidence output for release candidates. The release evidence
includes SBOM/provenance, repeated binary reproducibility, a no-secret binary
startup smoke, signal-cli advisory/version review, and explicit status fields
for live Signal smokes that require a non-production linked account.
Encrypted message log
The TUI can log every message to an encrypted SQLite database so you keep a copy
even when the sender later deletes it ("delete for everyone"). Logs use
jsqlite for new logs and a Jerboa-native encrypted container around the
database image; contents and metadata are unreadable without your passphrase.
Existing SQLCipher logs are still opened through the legacy shim so old history
continues to replay.
Enable it:
./jerboa-signal tui # prompts: passphrase for encrypted message log
At startup the TUI asks for a passphrase on the controlling terminal. It fails
closed if the exact terminal state cannot be saved, echo disabled, or the state
restored. Enter one to log; leave it blank to skip logging for that session.
For unattended use, prefer --log-key-fd FD with a dedicated inherited FD.
JERBOA_SIGNAL_DB_KEY remains available but exposes the secret to the process
environment.
The log lives at ~/.local/share/jerboa-signal/messages-<account>.db. Each
inbound notification and outbound send is one append-only row (with the full raw
JSON); remote-deletes, edits, reactions, calls, attachments, and other
displayable events are logged as new rows and never overwrite the
original.
Retention is bounded by default to 20,000 rows, 128 MiB of retained payload,
and 4 MiB per entry; oldest rows are pruned before an insert exceeds the row or
payload budget. The limits can be lowered with JERBOA_SIGNAL_LOG_MAX_ROWS,
JERBOA_SIGNAL_LOG_MAX_PAYLOAD_BYTES, and
JERBOA_SIGNAL_LOG_MAX_ENTRY_BYTES.
To convert an older SQLCipher log to the current jsqlite container format:
jerboa-signal migrate-log # add -a +PHONE if you use a named account
The converter verifies the new row count before swapping files and keeps the
old SQLCipher database beside it as messages-<account>.db.sqlcipher.bak.
Logging happens only while the TUI is open — messages that arrive while it is closed are not captured.
Architecture
┌─────────────────┐ argv ┌──────────────────┐ NDJSON ┌────────────┐
│ jerboa-signal │ ─────────► │ signal/main.ss │ ───────► │ signal-cli │
│ (CLI argv) │ │ (subcommands) │ │ jsonRpc │
└─────────────────┘ └──────────────────┘ ◄─────── └────────────┘
│ NDJSON │
▼ ▼
stdout / stderr Signal servers
Modules:
signal/main.ss— entry point, argv parsing, subcommand dispatchsignal/rpc.ss— JSON-RPC client over signal-cli's stdin/stdout (NDJSON framing, id correlation, notification queue)signal/rpc-actor.ss— long-lived JSON-RPC actor for the TUIsignal/cmd-send.ss—sendsubcommandsignal/cmd-listen.ss—listensubcommandsignal/cmd-tui.ss— TUI command entry point; seedocs/TUI_PLAN.mdsignal/store.ss— persistent local state (deleted-conversation list)signal/capture.ss— normalizes a notification into an encrypted log rowsignal/logdb.ss+signal/log_crypto.ss— encrypted log backendsignal/tui/— termbox2 FFI and the first terminal shell
License
Apache-2.0.