Hex.pm Hex Docs CI License Website Ecosystem Discord

jido_chat_signal adapts Signal messages to Jido.Chat.Adapter through signal-cli.

Installation

def deps do
  [
    {:jido_chat_signal, "~> 0.1"}
  ]
end

signal-cli setup

Install and link/register signal-cli. For JSON-RPC send-only mode, start the daemon:

signal-cli -a +15555550100 daemon --http=127.0.0.1:8080

For production JSON-RPC polling ingress, start the daemon in manual receive mode:

signal-cli -a +15555550100 daemon --http=127.0.0.1:8080 --receive-mode manual --no-receive-stdout

For CLI live tests, the one-shot signal-cli send path is enough. For multi-account mode, omit -a from the daemon command and set SIGNAL_ACCOUNT.

Live testing

Set:

RUN_LIVE_SIGNAL_TESTS=true
SIGNAL_TRANSPORT=cli
SIGNAL_ACCOUNT=+15555550100
SIGNAL_TEST_RECIPIENT=+15555550101

Run:

mix test --include live

The live test sends a text message, a small file attachment, a GIF attachment, and a quoted reply. Use SIGNAL_TRANSPORT=json_rpc when testing against signal-cli daemon --http. In that mode, also set SIGNAL_RPC_ENDPOINT.

Ingress

For production, prefer JSON-RPC polling against signal-cli daemon --http:

opts: %{
  ingress: %{
    mode: "rpc_polling",
    endpoint: System.fetch_env!("SIGNAL_RPC_ENDPOINT"),
    account: System.fetch_env!("SIGNAL_ACCOUNT"),
    receive_timeout_s: 3,
    max_messages: 10,
    poll_interval_ms: 1_000
  }
}

This calls the daemon's JSON-RPC receive method on each poll. The daemon must run with --receive-mode manual; otherwise Signal may already be receiving and the explicit receive RPC can be rejected.

For local CLI-only setups, use adapter-owned CLI polling:

opts: %{
  ingress: %{
    mode: "polling",
    account: System.fetch_env!("SIGNAL_ACCOUNT"),
    receive_timeout_s: 1,
    max_messages: 10,
    poll_interval_ms: 1_000
  }
}

Both polling modes emit each envelope through the bridge sink_mfa and let the runtime call Jido.Chat.Signal.Adapter.transform_incoming/1.

signal-cli daemon --http also exposes GET /api/v1/events as a Server-Sent Events stream. The RPC poller is simpler to supervise and keeps receive ownership inside the bridge runtime.