Files
nanoclaw/.claude/skills/add-signal/SKILL.md
gavrielc 5f3bd9c880 fix(signal): address review feedback from #1953
Correctness fixes:
- parseSignalStyles now uses a recursive walker so nested styles (e.g.
  **bold with `code` inside**) produce correct offsets against the final
  plain text. Previous impl recorded styles against intermediate text and
  didn't reindex when later passes stripped prefix characters.
- *single-asterisk* maps to ITALIC (was BOLD, divergent from standard
  Markdown). _underscore_ also maps to ITALIC.
- EchoCache keys on (platformId, text) so an outbound "hi" to Alice no
  longer drops a real "hi" inbound from Bob.
- On TCP socket close, flip adapter connected=false and log a warning so
  operators see lost daemon connections instead of silently failing sends.
- signalTcpCheck clears its 5s timeout on success so successful checks
  don't leak a setTimeout handle.

Config hygiene:
- Rename SIGNAL_HTTP_HOST/PORT to SIGNAL_TCP_HOST/PORT (transport is TCP
  JSON-RPC, not HTTP) and add SIGNAL_CLI_PATH for non-PATH installs.
- Remove unused readFileSync import.
- Log a warning in deliver() when outbound files are dropped (native
  adapter doesn't forward attachments to signal-cli yet).

Tests:
- Nested style offset correctness
- *italic* and _italic_ ITALIC mapping
- Cross-recipient echo isolation
- Same-recipient echo still suppressed
- isConnected() flips on socket close
- Outbound-files warn-and-drop path

SKILL.md realigned to the add-telegram / add-whatsapp template: fetches
from the `channels` branch (not a `skill/*` branch), lists pre-flight
idempotency checks, adds Features / Troubleshooting sections. Added
VERIFY.md and REMOVE.md siblings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 22:54:27 +03:00

5.3 KiB

name, description
name description
add-signal Add Signal channel integration via signal-cli TCP daemon. Native adapter — no Chat SDK bridge.

Add Signal Channel

Adds Signal messaging support via a native adapter that speaks JSON-RPC to a signal-cli TCP daemon. No Chat SDK bridge, no npm deps — only Node.js builtins.

Prerequisites

signal-cli installed and a Signal account linked:

  • macOS: brew install signal-cli
  • Linux: download from GitHub releases
  • Link your account: signal-cli -a +1YOURNUMBER link (follow the QR instructions)

Install

NanoClaw doesn't ship channels in trunk. This skill copies the Signal adapter and its tests in from the channels branch.

Pre-flight (idempotent)

Skip to Credentials if all of these are already in place:

  • src/channels/signal.ts and src/channels/signal.test.ts both exist
  • src/channels/index.ts contains import './signal.js';

Otherwise continue. Every step below is safe to re-run.

1. Fetch the channels branch

git fetch origin channels

2. Copy the adapter and tests

git show origin/channels:src/channels/signal.ts      > src/channels/signal.ts
git show origin/channels:src/channels/signal.test.ts > src/channels/signal.test.ts

3. Append the self-registration import

Append to src/channels/index.ts (skip if the line is already present):

import './signal.js';

4. Build

pnpm run build

No npm packages to install — the adapter uses only Node.js builtins (node:net, node:child_process, node:fs).

Credentials

Add to .env:

SIGNAL_ACCOUNT=+1YOURNUMBER

Optional settings

# TCP daemon host and port (default: 127.0.0.1:7583)
SIGNAL_TCP_HOST=127.0.0.1
SIGNAL_TCP_PORT=7583

# Path to the signal-cli binary (default: resolved on PATH)
SIGNAL_CLI_PATH=/usr/local/bin/signal-cli

# Whether NanoClaw manages the daemon lifecycle (default: true).
# Set to false if you run signal-cli daemon externally.
SIGNAL_MANAGE_DAEMON=true

# signal-cli data directory (default: ~/.local/share/signal-cli)
SIGNAL_DATA_DIR=~/.local/share/signal-cli

Security note: keep the TCP host on 127.0.0.1. The daemon has no auth — binding it to a public interface would expose your full Signal account to the network.

Sync to container: mkdir -p data/env && cp .env data/env/env

Restart

# macOS
launchctl kickstart -k gui/$(id -u)/com.nanoclaw

# Linux
systemctl --user restart nanoclaw

Next Steps

If you're in the middle of /setup, return to the setup flow now.

Otherwise, run /init-first-agent to create an agent and wire it to your Signal DM, or /manage-channels to wire this channel to an existing agent group. Signal is direct-addressable — your phone number is the platform ID.

Channel Info

  • type: signal
  • terminology: Signal has "chats" (1:1 DMs) and "groups."
  • how-to-find-id: DMs use your phone number (e.g. +15555550123). Groups use group:<groupId> — find group IDs via signal-cli -a +1YOURNUMBER listGroups.
  • supports-threads: no
  • typical-use: Personal assistant via Signal DMs or small group chats
  • default-isolation: One agent per Signal account. Multiple chats with the same operator can share an agent group; groups with other people should typically be separate.

Features

  • Markdown formatting — **bold**, *italic* / _italic_, `code`, ```code fence```, ~~strike~~, ||spoiler|| (converted to Signal's offset-based text styles)
  • Quoted replies — replyTo* fields populated from Signal quotes
  • Typing indicators — DMs only (Signal doesn't support group typing)
  • Echo suppression — outbound messages are matched on (platformId, text) within a 10 s TTL to avoid syncMessage loops
  • Note to Self — messages you send to your own account from another device route to the agent as inbound with isFromMe: true
  • Voice attachments — detected but not transcribed by default; the agent receives [Voice Message] placeholder text. Run /add-voice-transcription for local transcription via parakeet-mlx

Not supported yet: outbound file attachments (logged and dropped), edit/delete messages, reactions.

Troubleshooting

Daemon not reachable

grep "Signal" logs/nanoclaw.log | tail

If you see Signal daemon failed to start. Is signal-cli installed and your account linked?:

  • Confirm signal-cli is on PATH (or set SIGNAL_CLI_PATH)
  • Confirm the account is linked: signal-cli -a +1YOURNUMBER listIdentities should succeed without prompting

If you see Signal daemon not reachable at 127.0.0.1:7583 and SIGNAL_MANAGE_DAEMON=false, start the daemon yourself: signal-cli -a +1YOURNUMBER daemon --tcp 127.0.0.1:7583.

Bot not responding

  1. Channel initialized: grep "Signal channel connected" logs/nanoclaw.log | tail -1
  2. Channel wired: sqlite3 data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id WHERE mg.channel_type='signal'"
  3. Service running: launchctl print gui/$(id -u)/com.nanoclaw (macOS) / systemctl --user status nanoclaw (Linux)

Lost connection mid-session

If you see Signal channel lost TCP connection to signal-cli daemon in the logs, the daemon dropped us. There's no auto-reconnect yet — restart the service to re-establish.