Docs: link Clawd setup and current config

This commit is contained in:
Peter Steinberger
2025-11-27 18:17:06 +01:00
parent 537348d995
commit 549ad272fc
2 changed files with 41 additions and 25 deletions

View File

@@ -10,7 +10,7 @@
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a> <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
</p> </p>
Send, receive, auto-reply, and inspect WhatsApp messages over **Twilio** or your personal **WhatsApp Web** session. Ships with a one-command webhook setup (Tailscale Funnel + Twilio callback) and a configurable auto-reply engine (plain text or command/Claude driven). Send, receive, auto-reply, and inspect WhatsApp messages over **Twilio** or your personal **WhatsApp Web** session. Ships with a one-command webhook setup (Tailscale Funnel + Twilio callback) and a configurable auto-reply engine (plain text or command/Claude driven). Im using warelay to run my personal, pro-active assistant, Clawd—follow me on Twitter: [@steipete](https://twitter.com/steipete). See my exact Claude setup in [`docs/claude-config.md`](https://github.com/steipete/warelay/blob/main/docs/claude-config.md).
I'm using warelay to run **my personal, pro-active assistant, Clawd**. I'm using warelay to run **my personal, pro-active assistant, Clawd**.
Follow me on Twitter - @steipete, this project is brand-new and there's a lot to discover. Follow me on Twitter - @steipete, this project is brand-new and there's a lot to discover.

View File

@@ -1,6 +1,6 @@
# Claude Auto-Reply Setup (2025-11-25) # Claude Auto-Reply Setup (2025-11-27)
This guide shows the exact way to wire **warelay** to the Claude CLI so inbound WhatsApp messages get command-driven replies. It matches the current code paths and defaults in this repo. This is the live Claude configuration used by @steipetes personal assistant “Clawd”. It matches the current code paths and defaults in this repo.
## Prerequisites ## Prerequisites
- Node 22+, `warelay` installed globally (`npm install -g warelay`) or run via `pnpm warelay` inside the repo. - Node 22+, `warelay` installed globally (`npm install -g warelay`) or run via `pnpm warelay` inside the repo.
@@ -11,39 +11,55 @@ This guide shows the exact way to wire **warelay** to the Claude CLI so inbound
``` ```
- Optional: set `ANTHROPIC_API_KEY` in your shell profile for non-interactive use. - Optional: set `ANTHROPIC_API_KEY` in your shell profile for non-interactive use.
## Create your warelay config ## Current personal config (`~/.warelay/warelay.json`)
warelay reads `~/.warelay/warelay.json` (JSON5 accepted). Add a command-mode reply that points at the Claude CLI:
```json5 ```json5
{ {
logging: { level: "trace", file: "/tmp/warelay/warelay.log" },
inbound: { inbound: {
// Only people in this list can trigger the command reply (remove to allow anyone). allowFrom: ["***REMOVED***"],
allowFrom: ["+15551234567"],
reply: { reply: {
mode: "command", mode: "command",
// Working directory for command execution (useful for Claude Code project context). cwd: "/Users/steipete/clawd", // Clawds home/project context
cwd: "/Users/you/Projects/my-project", bodyPrefix: "ultrathink ", // prepended to every inbound body
// Prepended before the inbound body; good for system prompts. sessionIntro: "You are Clawd, an AI assistant running on the user's Mac. User writes messages via WhatsApp and you respond. This folder (/Users/steipete/clawd) is your personal workspace - you can store Markdown files, images, notes, and any data you like here. Keep WhatsApp replies under 1500 characters (platform limit), but feel free to save longer content to files in your folder.",
bodyPrefix: "You are a concise WhatsApp assistant. Keep replies under 1500 characters.\n\n", command: [
// Claude CLI argv; the final element is the prompt/body provided by warelay. "claude",
command: ["claude", "--model", "claude-3-5-sonnet-20240620", "{{BodyStripped}}"], "--model",
claudeOutputFormat: "text", // warelay injects --output-format text and -p for Claude "claude-opus-4-5-20251101",
timeoutSeconds: 120, "-p",
"--output-format",
"json",
"--dangerously-skip-permissions",
"{{BodyStripped}}"
],
session: { session: {
scope: "per-sender", // keep conversation per phone number scope: "per-sender",
resetTriggers: ["/new"], // send "/new" to reset context resetTriggers: ["/new"],
idleMinutes: 60 idleMinutes: 10080, // 7 days before a fresh session is forced
} heartbeatIdleMinutes: 10080, // same window for heartbeat-based expiry
sessionArgNew: ["--session-id", "{{SessionId}}"],
sessionArgResume: ["--resume", "{{SessionId}}"],
sessionArgBeforeBody: true,
sendSystemOnce: true // sessionIntro sent only on first turn
},
timeoutSeconds: 900
} }
} }
} }
``` ```
Notes on this configuration: Key behaviors:
- `cwd` sets the working directory where the command runs. This is essential for Claude Code to have the right project context—Claude will see the project's `CLAUDE.md`, have access to project files, and understand the codebase structure. - **System prompt once:** `sessionIntro` is injected only on the first turn of each session because `sendSystemOnce=true`. Later turns only see the per-message prefix.
- warelay automatically injects a Claude identity prefix and the correct `--output-format`/`-p` flags when `command[0]` is `claude` and `claudeOutputFormat` is set. - **Per-message prefix:** Every inbound body gets `ultrathink ` prepended (`bodyPrefix`) before being passed to Claude.
- Sessions are stored in `~/.warelay/sessions.json`; `scope: per-sender` keeps separate threads for each contact. - **Session stickiness:** Sessions are per-sender and live up to 7 days of inactivity; `/new` forces a reset.
- `bodyPrefix` is added before the inbound message body that reaches Claude. The string above mirrors the built-in 1500-character WhatsApp guardrail. - **Clawds home:** Claude runs in `/Users/steipete/clawd`, so it can read and write there.
## Heartbeats (proactive pings)
- Warelay can poll Claude on a cadence (default 30m when `mode=command`) using the prompt body **`HEARTBEAT ultrathink`**.
- Claude is instructed (via `CLAUDE_IDENTITY_PREFIX`) to reply with exactly `HEARTBEAT_OK` when nothing needs attention. If that token is returned, the outbound message is suppressed but the event is logged.
- Replies without `HEARTBEAT_OK` (or with media) are forwarded as alerts. Suppressed heartbeats do **not** extend session `updatedAt`, so idle expiry still works.
- You can trigger one manually: `warelay heartbeat --provider web --to ***REMOVED*** --session-id <session-uuid> --verbose`.
## How the flow works ## How the flow works
1. An inbound message (Twilio webhook, Twilio poller, or WhatsApp Web listener) arrives. 1. An inbound message (Twilio webhook, Twilio poller, or WhatsApp Web listener) arrives.
@@ -61,7 +77,7 @@ Notes on this configuration:
- Inbound media is downloaded (≤5MB) and exposed to your templates as `{{MediaPath}}`, `{{MediaUrl}}`, and `{{MediaType}}`. You can mention this in your prompt if you want Claude to reason about the attachment. - Inbound media is downloaded (≤5MB) and exposed to your templates as `{{MediaPath}}`, `{{MediaUrl}}`, and `{{MediaType}}`. You can mention this in your prompt if you want Claude to reason about the attachment.
- Outbound media from Claude (via `MEDIA:`) follows provider caps: Web resizes images to the configured target (`inbound.reply.mediaMaxMb`, default 5MB) within hard limits of 6MB (image), 16MB (audio/video voice notes), and 100MB (documents); Twilio still uses the Funnel host with a 5MB guard. - Outbound media from Claude (via `MEDIA:`) follows provider caps: Web resizes images to the configured target (`inbound.reply.mediaMaxMb`, default 5MB) within hard limits of 6MB (image), 16MB (audio/video voice notes), and 100MB (documents); Twilio still uses the Funnel host with a 5MB guard.
- Voice notes: set `inbound.transcribeAudio.command` to run a CLI that emits the transcript to stdout (e.g., OpenAI Whisper: `openai api audio.transcriptions.create -m whisper-1 -f {{MediaPath}} --response-format text`). If it succeeds, warelay replaces `Body` with the transcript and adds the original media path plus a `Transcript:` block into the prompt before invoking Claude. - Voice notes: set `inbound.transcribeAudio.command` to run a CLI that emits the transcript to stdout (e.g., OpenAI Whisper: `openai api audio.transcriptions.create -m whisper-1 -f {{MediaPath}} --response-format text`). If it succeeds, warelay replaces `Body` with the transcript and adds the original media path plus a `Transcript:` block into the prompt before invoking Claude.
- To avoid re-sending long system prompts every turn, set `inbound.reply.session.sendSystemOnce: true` and keep your prompt in `bodyPrefix` or `sessionIntro`; they are sent only on the first message of each session (resets on `/new` or idle expiry). - To avoid re-sending long system prompts every turn, set `inbound.reply.session.sendSystemOnce: true` and keep your prompt in `sessionIntro`; use `bodyPrefix` for lightweight per-message tags (e.g., `ultrathink `).
- Typing indicators: for long-running Claude/command replies, `inbound.reply.typingIntervalSeconds` (or the session-level equivalent) refreshes the “composing” indicator periodically (default 8s for command replies). - Typing indicators: for long-running Claude/command replies, `inbound.reply.typingIntervalSeconds` (or the session-level equivalent) refreshes the “composing” indicator periodically (default 8s for command replies).
## Testing the setup ## Testing the setup