A Signal messaging bot that integrates Claude AI for intelligent conversations, code assistance, and autonomous development tasks with independent verification, parallel execution, and production-grade reliability.
Most AI coding tools require you to sit at your computer. Sidechannel lets you manage your entire development workflow from your phone through Signal's end-to-end encrypted messaging. Whether you're commuting, in a meeting, or just away from your desk, you can:
- Ship code from your phone - Ask Claude to implement features, fix bugs, or refactor code on any of your projects, all from a Signal message
- Delegate complex projects - Describe what you want built, and Sidechannel breaks it into a full PRD with stories and tasks, then executes them autonomously with parallel workers
- Never lose context - Episodic memory with vector embeddings means Sidechannel remembers your conversations, project preferences, and past decisions across sessions
- Trust the output - Every autonomous task is independently verified by a separate Claude context using a fail-closed security model. Code that introduces security issues or logic errors is rejected automatically
- Powered by Claude - All code analysis, generation, and autonomous tasks run through Claude (via Claude CLI). Optionally add OpenAI or Grok as lightweight quick-response assistants for general questions that don't need project access
- Stay secure - Phone number allowlist, end-to-end encryption via Signal, rate limiting, path validation hardening, and no message content logging
Memory That Actually Works
Unlike chat-based tools that forget everything when you close a tab, Sidechannel's episodic memory system stores conversations with vector embeddings. When you ask Claude to work on something, it automatically retrieves relevant context from past conversations, stored memories, and project-specific knowledge. Your /remember facts persist forever. Session context groups related messages automatically.
Autonomous Development at Scale
Send /complex Add user authentication with JWT tokens and Sidechannel will:
- Use Claude to analyze the task and generate a full PRD
- Break it into stories with focused, atomic tasks
- Dispatch tasks to parallel workers (up to 10 concurrent)
- Independently verify each task's output for security and correctness
- Run quality gates (test baselines + regression detection)
- Auto-fix verification failures (up to 2 attempts)
- Send you progress updates via Signal as work completes
Production-Grade Reliability
- Transient errors (rate limits, timeouts) retried with exponential backoff
- Stale tasks automatically recovered on restart
- Circular dependency detection prevents deadlock
- Git checkpoints before every task, atomic commits after
- Baseline test snapshots distinguish new regressions from pre-existing failures
+-------------------+ +-------------------+ +---------------------+
| | | | | |
| Signal Mobile |<--->| Signal CLI API |<--->| sidechannel |
| | | (Docker) | | (Python) |
+-------------------+ +-------------------+ +---------------------+
| |
+--------+------+ +--+-------------+
| | | |
| Autonomous | | Memory System |
| Loop | | (SQLite + Vec) |
+-------+-------+ +----------------+
|
+-------------------------+-------------------------+
| | |
+---------+---------+ +-----------+---------+ +-----------+---------+
| Worker 1 | | Worker 2 | | Worker N |
| (Claude CLI) | | (Claude CLI) | | (Claude CLI) |
+---------+---------+ +-----------+---------+ +-----------+---------+
| | |
+---------+---------+ +-----------+---------+ +-----------+---------+
| Verifier 1 | | Verifier 2 | | Verifier N |
| (Independent | | (Independent | | (Independent |
| Claude Context) | | Claude Context) | | Claude Context) |
+-------------------+ +---------------------+ +---------------------+
| | |
+---------+---------+ +-----------+---------+ +-----------+---------+
| Quality Gate | | Quality Gate | | Quality Gate |
| (Test Baseline | | (Test Baseline | | (Test Baseline |
| + Regression) | | + Regression) | | + Regression) |
+-------------------+ +---------------------+ +---------------------+
# Clone the repository
git clone https://github.com/hackingdave/sidechannel.git
cd sidechannel
# Run the installer
./install.shThe installer walks you through everything: Python venv setup, phone number, Signal pairing (scan a QR code), and starting the service. No manual setup needed.
The installer supports flags for advanced usage:
./install.sh --skip-signal # Skip Signal pairing setup
./install.sh --skip-systemd # Skip service installation
./install.sh --restart # Restart the sidechannel service
./install.sh --uninstall # Remove sidechannel service and containers| Dependency | Notes |
|---|---|
| Python 3.9+ | Required |
| Docker | Required for Signal bridge container |
| Claude CLI | Recommended (powers /ask, /do, /complex) |
| Signal account | Required |
The bot runs natively in a Python venv, managed by systemd (Linux) or launchd (macOS). Docker is only used for the Signal bridge (signal-cli-rest-api).
These commands let you register, select, and navigate between multiple codebases.
| Command | Description |
|---|---|
/projects |
List all registered projects |
/select <project> |
Set the active project for all code operations |
/add <name> [path] [description] |
Register an existing project directory |
/remove <project> |
Unregister a project from the bot |
/new <name> [description] |
Create a new project from scratch |
/status |
Show current project, running tasks, and autonomous loop state |
/help |
Show all available commands |
Examples:
/projects
→ Lists: myapp, backend-api, ml-pipeline
/select myapp
→ Active project: myapp (/home/user/projects/myapp)
/add backend-api /home/user/projects/api "REST API service"
→ Project 'backend-api' registered
/remove backend-api
→ Removed project: backend-api
/status
→ Project: myapp
Running task (3 min elapsed): Add user authentication...
[Autonomous Loop] Queued: 4 tasks | Completed today: 7
These are the core commands for interacting with Claude on your selected project. Claude has full access to the project's codebase and can read, analyze, and modify files.
| Command | Description |
|---|---|
/ask <question> |
Ask Claude a read-only question about the project (architecture, how something works, debugging advice) |
/do <task> |
Have Claude make actual changes to the codebase (implement features, fix bugs, refactor) |
/complex <task> |
Break a large task into a PRD with stories and autonomous tasks, then execute them in parallel |
/summary |
Get a comprehensive summary of the project structure, technologies, and recent git changes |
/cancel |
Stop the currently running Claude task |
Examples:
/ask How does the authentication middleware work?
→ Claude analyzes the codebase and explains the auth flow,
middleware chain, and token validation logic
/do Add input validation to the /users POST endpoint
→ Claude reads the endpoint, adds Pydantic models for
request validation, and updates tests
/do Fix the race condition in the cache invalidation
→ Claude identifies the bug, adds proper locking, and
verifies the fix doesn't break existing tests
/complex Build a REST API for task management with CRUD
operations, authentication, pagination, and tests
→ Creates PRD #3: Task Management API
Stories:
- Database models and migrations (3 tasks)
- CRUD endpoints (4 tasks)
- Authentication middleware (2 tasks)
- Pagination and filtering (2 tasks)
- Test suite (3 tasks)
Total: 14 tasks queued
Autonomous loop: Started
/summary
→ Project: myapp (Python/FastAPI)
Structure: 47 files, 3200 LOC
Dependencies: FastAPI, SQLAlchemy, Pydantic
Recent changes: Added user auth (3 commits today)
The autonomous system handles complex, multi-step development tasks. It breaks work into atomic tasks, executes them in parallel, verifies each one independently, and runs quality gates.
| Command | Description |
|---|---|
/prd <title> |
Create a new Product Requirements Document |
/prd list |
List all PRDs |
/prd <id> |
View PRD details with stories and task status |
/story <prd_id> <title> | <description> |
Add a user story to a PRD |
/task <story_id> <title> | <description> |
Add an individual task to a story |
/tasks [status] |
List tasks, optionally filtered by status (queued, running, completed, failed) |
/queue story|prd <id> |
Queue all tasks from a story or entire PRD for execution |
/autonomous start |
Start the autonomous task execution loop |
/autonomous pause |
Pause execution (current task finishes, no new ones start) |
/autonomous stop |
Stop the loop and clear the queue |
/autonomous status |
Show detailed loop status: workers, queue depth, completion stats |
/learnings [search] |
View or search learnings captured from completed tasks |
Examples:
/prd Add OAuth2 social login
→ Created PRD #5: Add OAuth2 social login
/story 5 Google OAuth | Implement Google OAuth2 login flow
with token exchange and profile creation
→ Created Story #12 under PRD #5
/task 12 Add Google callback endpoint | Create GET /auth/google/callback
that exchanges the auth code for tokens and creates/links user
→ Created Task #34 under Story #12
/queue prd 5
→ Queued 8 tasks from PRD #5
/autonomous start
→ Starting autonomous loop (3 parallel workers)...
→ [2 min] Worker 1: Implementing Google callback endpoint
→ [3 min] Worker 2: Adding OAuth config model (parallel)
→ [5 min] Task #34 verified and passed quality gates
/tasks running
→ #35 [RUNNING] Add token refresh logic (2 min)
#36 [RUNNING] Create OAuth settings page (1 min)
/autonomous status
→ Loop: RUNNING (3 workers)
Current: 2 tasks executing
Queued: 4 tasks remaining
Completed today: 12 | Failed: 1
/learnings oauth
→ #7: Google OAuth requires PKCE flow for mobile clients
#8: Token refresh should use sliding expiration window
The memory system gives Sidechannel persistent context across sessions. Conversations are automatically stored and indexed with vector embeddings for semantic search. You can also store explicit facts that persist forever.
| Command | Description |
|---|---|
/remember <text> |
Store a fact or preference for the current project |
/recall <query> |
Semantically search past conversations and memories |
/memories |
List all stored memories for the current project |
/history [count] |
View recent message history (default: 10 messages) |
/forget all|preferences|today |
Delete your data by scope (all data, preferences only, or today's conversations) |
/preferences |
View your stored preferences |
/global remember <text> |
Store a cross-project memory |
/global recall <query> |
Search across all projects |
/global memories |
List all cross-project memories |
/global history [count] |
View history across all projects |
Examples:
/remember We use Black for formatting and Ruff for linting
→ Stored memory for project 'myapp'
/remember Always run tests with --tb=short flag
→ Stored memory for project 'myapp'
/recall How did we set up the database migrations?
→ Found 3 relevant results:
[2 days ago] Set up Alembic with async SQLAlchemy...
[3 days ago] Created initial migration for users table...
[Memory] We use PostgreSQL 15 with asyncpg driver
/memories
→ 1. We use Black for formatting and Ruff for linting
2. Always run tests with --tb=short flag
3. Deploy via GitHub Actions to AWS ECS
/history 5
→ Shows your last 5 messages and responses
/global remember Use conventional commits across all projects
→ Stored global memory
/global recall deployment process
→ Searches memories and conversations across all projects
All code commands (/ask, /do, /complex) are powered by Claude via Claude CLI. Separately, you can enable a lightweight quick-response assistant backed by OpenAI (GPT-4o) or Grok for general knowledge questions that don't need project file access. This is optional — Claude handles all the real work.
| Command | Description |
|---|---|
/sidechannel <question> |
Ask the AI assistant anything |
sidechannel <question> |
Same thing, without the slash |
Examples:
/sidechannel what is the difference between REST and GraphQL?
→ Quick response comparing the two approaches
sidechannel, explain kubernetes pods
→ Concise explanation of K8s pod concepts
sidechannel what's the best way to handle JWT refresh tokens?
→ Practical advice on token refresh patterns
The provider is auto-detected from your API keys. If only OPENAI_API_KEY is set, it uses OpenAI. If only GROK_API_KEY is set, it uses Grok. You can also set it explicitly in config.
Sidechannel can check for updates automatically and notify you via Signal. Disabled by default.
auto_update:
enabled: true
check_interval: 21600 # 6 hours (in seconds)
branch: "main"When an update is detected, the bot sends a Signal message to the admin (first number in allowed_numbers). Reply /update to apply. On failure, the bot rolls back to the previous version and notifies you.
# Phone numbers authorized to use the bot (E.164 format)
allowed_numbers:
- "+15551234567"
# Signal CLI REST API
signal_api_url: "http://127.0.0.1:8080"
# Claude CLI settings
claude_timeout: 600 # Max seconds per Claude invocation (default: 1800)
claude_max_turns: 15 # Max conversation turns per invocation
# claude_path: "/usr/local/bin/claude" # Override Claude CLI path
# Project directories
# projects_base_path: "/home/user/projects" # Base path for project auto-discovery
# allowed_paths: # Additional allowed paths outside base
# - "/home/user/other-projects"
# Memory System
memory:
session_timeout: 30 # Minutes before session expires
max_context_tokens: 1500 # Max tokens for context window
# embedding_model: "all-MiniLM-L6-v2" # Sentence transformer model for vector search
# Autonomous Tasks
autonomous:
enabled: true
poll_interval: 30 # Seconds between task checks
quality_gates: true # Run tests after each task
max_parallel: 3 # Concurrent task workers (1-10)
verification: true # Independent code review per task
# max_retries: 2 # Max retry attempts for failed tasks
# effort_levels: # Override effort per task type
# implementation: "high"
# bug_fix: "high"
# refactor: "medium"
# Rate Limiting
# Currently hardcoded to 30 requests per 60-second window per user.
# Optional: sidechannel AI assistant (supports OpenAI and Grok)
sidechannel_assistant:
enabled: false
# provider: "openai" # or "grok" — auto-detected from API keys if omitted
# model: "gpt-4o" # Default: gpt-4o (OpenAI) or grok-3-latest (Grok)
# max_tokens: 1024Claude CLI handles its own authentication — no API key needed in .env.
claude login# Optional (for sidechannel AI assistant) — set one or both
OPENAI_API_KEY=sk-...
GROK_API_KEY=xai-...
# Optional: Override Signal API URL (takes precedence over settings.yaml)
# SIGNAL_API_URL=http://127.0.0.1:8080During installation, the installer prompts for your projects directory and offers to auto-register all subdirectories. You can also manage projects at runtime with /add, /remove, and /select.
To manually edit config/projects.yaml:
projects:
- name: myapp
path: /home/user/projects/myapp
description: "My web application"
- name: backend
path: /home/user/projects/backend-api
description: "REST API service"
# Optional: restrict access to specific phone numbers
- name: private-project
path: /home/user/projects/private
description: "Restricted access"
allowed_numbers:
- "+15551234567"Per-user project scoping: Each phone number has its own active project selection, so multiple users can work on different projects simultaneously. Projects without allowed_numbers are visible to everyone. Projects with allowed_numbers are only shown in /projects and selectable via /select for listed numbers.
cd /path/to/sidechannel
./run.sh# Start
systemctl --user start sidechannel
# View logs
journalctl --user -u sidechannel -f
# Stop
systemctl --user stop sidechannel# Start
launchctl load ~/Library/LaunchAgents/com.sidechannel.bot.plist
# View logs
tail -f /path/to/sidechannel/logs/sidechannel.log
# Stop
launchctl unload ~/Library/LaunchAgents/com.sidechannel.bot.plistSidechannel supports custom plugins so you can add your own functionality without touching the core codebase. Plugins are auto-discovered at startup, get their own config section, and integrate seamlessly with the bot's command system, message routing, and help output.
plugins/
├── my_plugin/
│ ├── __init__.py # Required (can be empty)
│ ├── plugin.py # Required — contains your SidechannelPlugin subclass
│ └── README.md # Optional — plugin documentation
└── another_plugin/
├── __init__.py
└── plugin.py
At startup, the bot scans plugins/ for directories containing plugin.py, finds your SidechannelPlugin subclass, and registers its commands, message matchers, and help sections. Broken or disabled plugins are logged and skipped — they never crash the bot.
1. Create the plugin directory and files:
mkdir -p plugins/hello_world
touch plugins/hello_world/__init__.py2. Write plugins/hello_world/plugin.py:
from sidechannel.plugin_base import SidechannelPlugin, HelpSection
class HelloWorldPlugin(SidechannelPlugin):
name = "hello_world"
description = "A simple example plugin"
version = "1.0.0"
def commands(self):
return {"hello": self._handle_hello}
async def _handle_hello(self, sender: str, args: str) -> str:
greeting = self.ctx.get_config("greeting", "Hello")
return f"{greeting}, {args or 'world'}!"
def help_sections(self):
return [HelpSection(
title="Hello World",
commands={"hello": "Say hello (usage: /hello [name])"},
)]3. Add config (optional) to config/settings.yaml:
plugins:
hello_world:
enabled: true
greeting: "Hey there"4. Restart the bot. Your plugin is live — send /hello Dave to test it.
Every plugin inherits from SidechannelPlugin and receives a PluginContext object (self.ctx) for interacting with the bot.
| Attribute | Type | Description |
|---|---|---|
name |
str |
Plugin identifier (must be unique) |
description |
str |
One-line description shown in logs |
version |
str |
Semantic version string |
| Method | Returns | Description |
|---|---|---|
commands() |
dict[str, handler] |
Register /slash commands. Handler signature: async (sender: str, args: str) -> str |
message_matchers() |
list[MessageMatcher] |
Register message interceptors checked before default routing |
on_start() |
None |
Called after bot connects — start schedulers, open connections |
on_stop() |
None |
Called during shutdown — cancel tasks, close sessions |
help_sections() |
list[HelpSection] |
Add entries to the /help command output |
| Method / Attribute | Description |
|---|---|
await ctx.send_message(recipient, text) |
Send a Signal message to any phone number |
ctx.get_config(key, default=None) |
Read plugins.<name>.<key> from settings.yaml |
ctx.get_env(key) |
Read an environment variable |
ctx.logger |
Structured logger (structlog) tagged with your plugin name |
ctx.allowed_numbers |
List of authorized phone numbers |
ctx.data_dir |
Path to a persistent data directory for your plugin |
ctx.enabled |
Whether the plugin is enabled in config (default: True) |
ctx.plugin_name |
Your plugin's name string |
A plugin that adds a /weather command:
import aiohttp
from sidechannel.plugin_base import SidechannelPlugin, HelpSection
class WeatherPlugin(SidechannelPlugin):
name = "weather"
description = "Check the weather"
version = "1.0.0"
def __init__(self, ctx):
super().__init__(ctx)
self._session = None
def commands(self):
return {"weather": self._handle_weather}
async def _handle_weather(self, sender: str, args: str) -> str:
city = args.strip() or self.ctx.get_config("default_city", "New York")
api_key = self.ctx.get_env("WEATHER_API_KEY")
if not api_key:
return "Weather plugin requires WEATHER_API_KEY in .env"
if not self._session:
self._session = aiohttp.ClientSession()
url = f"https://api.weatherapi.com/v1/current.json?key={api_key}&q={city}"
async with self._session.get(url) as resp:
if resp.status != 200:
return f"Could not fetch weather for {city}"
data = await resp.json()
current = data["current"]
return f"Weather in {city}: {current['temp_f']}°F, {current['condition']['text']}"
async def on_stop(self):
if self._session:
await self._session.close()
def help_sections(self):
return [HelpSection(
title="Weather",
commands={"weather": "Check weather (usage: /weather [city])"},
)]Config:
plugins:
weather:
enabled: true
default_city: "Columbus, OH"A plugin that intercepts messages matching a pattern (no /command needed):
import re
from sidechannel.plugin_base import SidechannelPlugin, MessageMatcher
class ReminderPlugin(SidechannelPlugin):
name = "reminder"
description = "Set reminders with natural language"
version = "1.0.0"
_pattern = re.compile(r"remind me .+ in \d+ (minutes?|hours?)", re.IGNORECASE)
def message_matchers(self):
return [MessageMatcher(
priority=20, # Lower number = checked first
match_fn=self._is_reminder,
handle_fn=self._handle_reminder,
description="Natural language reminders",
)]
def _is_reminder(self, message: str) -> bool:
return bool(self._pattern.search(message))
async def _handle_reminder(self, sender: str, message: str) -> str:
# Parse and schedule the reminder...
return f"Reminder set! I'll message you when it's time."Message matchers are sorted by priority (lower = first). When a matcher's match_fn returns True, its handle_fn is called and no further matchers or default routing are checked.
A plugin that runs a background task on a schedule:
import asyncio
from sidechannel.plugin_base import SidechannelPlugin
class HealthCheckPlugin(SidechannelPlugin):
name = "health_check"
description = "Periodic health monitoring"
version = "1.0.0"
def __init__(self, ctx):
super().__init__(ctx)
self._task = None
self._interval = ctx.get_config("interval_seconds", 3600)
async def on_start(self):
"""Start the health check loop when the bot connects."""
self._task = asyncio.create_task(self._run_loop())
self.ctx.logger.info("health_check_started", interval=self._interval)
async def on_stop(self):
"""Cancel the loop on shutdown."""
if self._task and not self._task.done():
self._task.cancel()
try:
await self._task
except asyncio.CancelledError:
pass
async def _run_loop(self):
while True:
try:
await asyncio.sleep(self._interval)
# Perform health check...
status = "all systems operational"
admin = self.ctx.get_config("admin_number")
if admin:
await self.ctx.send_message(admin, f"Health: {status}")
except asyncio.CancelledError:
break
except Exception as e:
self.ctx.logger.error("health_check_error", error=str(e))
await asyncio.sleep(60) # Brief pause before retryEvery plugin gets its own section in config/settings.yaml under plugins.<name>:
plugins:
# Disable a plugin without removing it
my_plugin:
enabled: false
# Plugin with custom config
weather:
enabled: true
default_city: "Columbus, OH"
units: "imperial"
Access config values with self.ctx.get_config("key", default). If no config section exists for your plugin, all get_config calls return the default value. Plugins default to enabled: true unless explicitly set to false.
For secrets (API keys, tokens), use environment variables via self.ctx.get_env("KEY") rather than storing them in settings.yaml. Add them to config/.env:
# Plugin-specific API keys
WEATHER_API_KEY=abc123
MY_PLUGIN_SECRET=xyz789When a message arrives, Sidechannel processes it in this order:
/command— Core commands (help, projects, ask, do, etc.) checked first- Plugin commands —
/commandsregistered by plugins, in load order - Plugin message matchers — Sorted by priority (lower number = checked first)
- Sidechannel assistant — Messages starting with "sidechannel:" prefix
- Default — Treated as
/doif a project is selected
Core commands always take precedence. If two plugins register the same /command name, the first-loaded plugin wins and a warning is logged.
Sidechannel uses a multi-layered memory architecture built on SQLite with sqlite-vec for vector similarity search:
- Automatic conversation storage - Every message you send and every response is automatically stored with timestamps, project context, and command type
- Session grouping - Messages within a configurable timeout window (default: 30 min) are grouped into sessions for coherent context retrieval
- Vector embeddings - Stored messages are embedded using sentence-transformers (all-MiniLM-L6-v2) for semantic similarity search
- Context injection - When you run
/askor/do, Sidechannel automatically retrieves the most relevant past conversations and memories, injecting them into Claude's context window - Explicit memories -
/rememberfacts are stored permanently and weighted higher in retrieval - Project isolation - Memories are scoped to projects by default, with
/globalfor cross-project knowledge - Token budgeting - Retrieved context is capped at
max_context_tokens(default: 1500) to leave room for Claude's actual work
This means Claude gets progressively smarter about your projects over time - it knows your conventions, past decisions, and what's been tried before.
The autonomous system is designed for tasks too large for a single Claude invocation:
- PRD Creation (
/complex) - Claude analyzes your request and generates a structured PRD with stories and atomic tasks - Dependency Resolution - Tasks are analyzed for dependencies; independent tasks can run in parallel
- Parallel Dispatch - Up to
max_parallelworkers execute tasks concurrently, each with its own Claude CLI session - Adaptive Effort - Task type (feature, bugfix, refactor, test) is auto-detected and mapped to an effort level controlling Claude's thoroughness
- Independent Verification - A separate Claude context reviews each task's git diff. Security concerns or logic errors cause the task to fail (fail-closed). Only infrastructure failures (timeout, crash) pass through (fail-open)
- Auto-Fix Loop - If verification fails, a fresh Claude context gets the failure reason and attempts to fix the issue (up to 2 attempts)
- Quality Gates - Test baselines are captured before each task. After completion, tests run again and only new failures (regressions) block the task. Pre-existing failures don't
- Git Safety - Automatic git checkpoints before execution and atomic commits after, protected by asyncio locks for concurrent access
- Progress Updates - Signal notifications as tasks complete, fail, or need attention
- Only phone numbers in
allowed_numberscan interact with the bot - Rate limiting - Per-user configurable request throttling prevents abuse
- Path validation hardening - Directory traversal protection with strict prefix matching
- Phone number masking in logs - Sensitive identifiers are redacted in all log output
- Independent code verification - Every autonomous task is reviewed by a separate Claude context with a fail-closed security model
- API keys are stored in
.env(not committed to git) - Signal messages are end-to-end encrypted
- No message content is logged by default
- Claude CLI runs with your local user permissions
- Run as dedicated low-privilege user (never root)
- Configure firewall (outbound 443 only)
- Set
projects_base_pathandallowed_pathsin config - Enable
plugin_allowlistif using plugins - Review
SECURITY.mdfor full operational security guide
- Check Signal CLI is running:
docker ps | grep signal - Verify device is linked:
curl http://127.0.0.1:8080/v1/accounts - Check logs:
docker logs signal-api
- Verify Claude CLI works:
claude --version - Check authentication:
claude login - Test manually:
claude "hello"
- Check data directory exists:
ls /path/to/sidechannel/data - Verify SQLite database:
ls /path/to/sidechannel/data/*.db
Contributions are welcome! Please read CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.
- Anthropic for Claude
- Signal for secure messaging
- signal-cli-rest-api for the Signal API wrapper