diff --git a/README.md b/README.md index b4864ed..7297fcd 100644 --- a/README.md +++ b/README.md @@ -321,16 +321,6 @@ Check the [ADE](https://app.letta.com) to see if your agent is attempting to use - Agent doesn't understand it needs to use the CLI - No delivery target set (user never messaged the bot first) -**Heartbeat/cron messages not reaching my chat** -Heartbeats and cron jobs run in "Silent Mode" - the agent's text output is private and not auto-delivered. To send messages during background tasks, the agent must run: -```bash -lettabot-message send --text "Your message here" -``` -Check the [ADE](https://app.letta.com) to see if your agent is attempting to use this command. Common issues: -- Bash tool not enabled (agent can't run CLI commands) -- Agent doesn't understand it needs to use the CLI -- No delivery target set (user never messaged the bot first) - ## Documentation - [Getting Started](docs/getting-started.md) diff --git a/docs/README.md b/docs/README.md index efb01d9..60531c6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,61 +1,74 @@ # LettaBot Documentation -LettaBot is a Telegram bot powered by [Letta](https://letta.com) that provides persistent memory and local tool execution. +LettaBot is a multi-channel AI assistant powered by [Letta](https://letta.com) that provides persistent memory and local tool execution across Telegram, Slack, Discord, WhatsApp, and Signal. ## Guides - [Getting Started](./getting-started.md) - Installation and basic setup +- [Configuration Reference](./configuration.md) - All config options +- [Commands Reference](./commands.md) - Bot commands reference +- [Scheduling Tasks](./cron-setup.md) - Cron jobs and heartbeats - [Gmail Pub/Sub](./gmail-pubsub.md) - Email notifications integration -- [Commands](./commands.md) - Bot commands reference + +### Channel Setup +- [Slack Setup](./slack-setup.md) - Socket Mode configuration +- [Discord Setup](./discord-setup.md) - Bot application setup +- [WhatsApp Setup](./whatsapp-setup.md) - Baileys/QR code setup +- [Signal Setup](./signal-setup.md) - signal-cli daemon setup ## Architecture +LettaBot uses a **single agent with unified memory** across all channels: + ``` -┌──────────────────────────────────────────────────────────────────────┐ -│ Your Server / Machine │ -│ │ -│ ┌────────────────┐ ┌───────────────────────────────────────┐ │ -│ │ Telegram │ │ LettaBot Core │ │ -│ │ Bot API │◀──────▶│ (TypeScript/Node) │ │ -│ │ (grammY) │ │ │ │ -│ └────────────────┘ │ ┌─────────────────────────────────┐ │ │ -│ │ │ Session Manager │ │ │ -│ │ │ userId → agentId (persisted) │ │ │ -│ │ └─────────────────────────────────┘ │ │ -│ │ │ │ -│ │ ┌─────────────────────────────────┐ │ │ -│ │ │ Letta Code SDK │ │ │ -│ │ │ createSession/resumeSession │ │ │ -│ │ └──────────────┬──────────────────┘ │ │ -│ └─────────────────┼─────────────────────┘ │ -│ │ spawn subprocess │ -│ ▼ │ -│ ┌─────────────────────────────────────┐ │ -│ │ Letta Code CLI │ │ -│ │ (--input-format stream-json) │ │ -│ │ │ │ -│ │ Local Tool Execution: │ │ -│ │ • Read/Glob/Grep - file ops │ │ -│ │ • Task - spawn subagents │ │ -│ │ • web_search - internet queries │ │ -│ └──────────────────┬───────────────────┘ │ -└───────────────────────────────────────────────┼───────────────────────┘ - │ Letta API - ▼ - ┌──────────────────────────────────┐ - │ Letta Server │ - │ (api.letta.com or self-hosted) │ - │ │ - │ • Agent Memory (persistent) │ - │ • LLM Inference │ - │ • Conversation History │ - └──────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────┐ +│ Your Server / Machine │ +│ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ ┌───────┐ │ +│ │ Telegram │ │ Slack │ │ Discord │ │ WhatsApp │ │ Signal│ │ +│ │ (grammY) │ │ (Socket) │ │ (Gateway) │ │(Baileys) │ │ (CLI) │ │ +│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └────┬─────┘ └───┬───┘ │ +│ │ │ │ │ │ │ +│ └───────────────┴───────────────┴──────────────┴────────────┘ │ +│ │ │ +│ ┌─────────▼─────────┐ │ +│ │ LettaBot Core │ │ +│ │ (TypeScript) │ │ +│ │ │ │ +│ │ • Message Router │ │ +│ │ • Session Mgmt │ │ +│ │ • Heartbeat/Cron │ │ +│ └─────────┬─────────┘ │ +│ │ │ +│ ┌─────────▼─────────┐ │ +│ │ Letta Code SDK │ │ +│ │ (subprocess) │ │ +│ │ │ │ +│ │ Local Tools: │ │ +│ │ • Read/Glob/Grep │ │ +│ │ • Bash │ │ +│ │ • web_search │ │ +│ └─────────┬─────────┘ │ +└────────────────────────────────────┼──────────────────────────────────────┘ + │ Letta API + ▼ + ┌──────────────────────────┐ + │ Letta Server │ + │ (api.letta.com or │ + │ self-hosted Docker) │ + │ │ + │ • Agent Memory │ + │ • LLM Inference │ + │ • Conversation History │ + └──────────────────────────┘ ``` ## Key Features -- **Persistent Memory** - Your agent remembers conversations across days/weeks/months +- **Multi-Channel** - Chat across Telegram, Slack, Discord, WhatsApp, and Signal +- **Unified Memory** - Single agent remembers everything from all channels +- **Persistent Memory** - Conversations persist across days/weeks/months - **Local Tool Execution** - Agent can search files, run commands on your machine -- **Multi-user Support** - Each Telegram user gets their own persistent agent +- **Voice Messages** - Automatic transcription via OpenAI Whisper - **Streaming Responses** - Real-time message updates as the agent thinks -- **Gmail Integration** - Get email summaries delivered to Telegram +- **Background Tasks** - Heartbeats and cron jobs for proactive actions diff --git a/docs/commands.md b/docs/commands.md index f32f38f..5e83708 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,6 +1,6 @@ # Commands Reference -LettaBot responds to these slash commands in Telegram. +LettaBot responds to these slash commands in chat channels. ## Available Commands @@ -9,56 +9,40 @@ LettaBot responds to these slash commands in Telegram. Shows the welcome message and list of available commands. ``` -🤖 LettaBot - AI assistant with persistent memory +LettaBot - AI assistant with persistent memory Commands: -/new - Start a new conversation (keeps memory) -/reset - Create a new agent (fresh memory) -/status - Show current agent ID +/status - Show current status /help - Show this message Just send me a message to get started! ``` -### `/new` - -Starts a new conversation while keeping the same agent and memory. - -Use this when you want to change topics but keep your agent's memory of who you are and past interactions. - -**Example:** -``` -You: /new -Bot: Started a new conversation. Your agent still remembers you! -You: Let's talk about something different now. -``` - -### `/reset` - -Creates a completely fresh agent with no memory. - -Use this if you want to start over from scratch, as if you've never talked to the bot before. - -**Warning:** This permanently deletes your agent's memory of past conversations. - -**Example:** -``` -You: /reset -Bot: Created a fresh agent with no memory. Send a message to begin! -``` - ### `/status` -Shows your current agent ID. +Shows your current agent ID and connection status. Useful for debugging or if you need to reference your agent in other tools. **Example:** ``` You: /status -Bot: Current agent: agent-a1b2c3d4-e5f6-7890-abcd-ef1234567890 +Bot: Agent: agent-a1b2c3d4-... + Model: claude-sonnet-4 + Channels: telegram, slack ``` +### `/heartbeat` + +Manually triggers a heartbeat check-in. + +Heartbeats are background tasks where the agent can: +- Review pending tasks +- Check reminders +- Perform proactive actions + +**Note:** This command runs silently - the agent won't automatically reply. If the agent wants to message you during a heartbeat, it will use the `lettabot-message` CLI. + ## Sending Messages Just type any message to chat with your agent. The agent has: @@ -82,10 +66,8 @@ The bot supports markdown formatting in responses: - ```Code blocks``` - [Links](https://example.com) -## Future Commands +Note: Available formatting varies by channel. WhatsApp and Signal have limited markdown support. -These commands are planned for future releases: +## Cross-Channel Commands -- `/model ` - Switch the LLM model -- `/verbose` - Toggle tool output visibility -- `/context` - Show memory summary +Commands work the same across all channels (Telegram, Slack, Discord, WhatsApp, Signal). The agent maintains a single conversation across all channels. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..781ba11 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,217 @@ +# Configuration Reference + +Complete reference for LettaBot configuration options. + +## Config File Locations + +LettaBot checks these locations in order: + +1. `./lettabot.yaml` - Project-local (recommended) +2. `./lettabot.yml` - Project-local alternate +3. `~/.lettabot/config.yaml` - User global +4. `~/.lettabot/config.yml` - User global alternate + +## Example Configuration + +```yaml +# Server connection +server: + mode: cloud # 'cloud' or 'selfhosted' + apiKey: letta_... # Required for cloud mode + +# Agent settings +agent: + name: LettaBot + model: claude-sonnet-4 + # id: agent-... # Optional: use existing agent + +# Channel configurations +channels: + telegram: + enabled: true + token: "123456:ABC-DEF..." + dmPolicy: pairing + + slack: + enabled: true + botToken: xoxb-... + appToken: xapp-... + dmPolicy: pairing + + discord: + enabled: true + token: "..." + dmPolicy: pairing + + whatsapp: + enabled: true + selfChat: true # IMPORTANT: true for personal numbers + dmPolicy: pairing + + signal: + enabled: true + phone: "+1234567890" + selfChat: true + dmPolicy: pairing + +# Features +features: + cron: true + heartbeat: + enabled: true + intervalMin: 60 + +# Voice transcription +transcription: + provider: openai + apiKey: sk-... # Optional: falls back to OPENAI_API_KEY + model: whisper-1 + +# Attachment handling +attachments: + maxMB: 20 + maxAgeDays: 14 +``` + +## Server Configuration + +| Option | Type | Description | +|--------|------|-------------| +| `server.mode` | `'cloud'` \| `'selfhosted'` | Connection mode | +| `server.apiKey` | string | API key for Letta Cloud | +| `server.baseUrl` | string | URL for self-hosted server (e.g., `http://localhost:8283`) | + +### Self-Hosted Mode + +```yaml +server: + mode: selfhosted + baseUrl: http://localhost:8283 +``` + +Run Letta server with Docker: +```bash +docker run -v ~/.letta/.persist/pgdata:/var/lib/postgresql/data \ + -p 8283:8283 \ + -e OPENAI_API_KEY="..." \ + letta/letta:latest +``` + +## Agent Configuration + +| Option | Type | Description | +|--------|------|-------------| +| `agent.id` | string | Use existing agent (skips creation) | +| `agent.name` | string | Name for new agent | +| `agent.model` | string | Model ID (e.g., `claude-sonnet-4`) | + +## Channel Configuration + +All channels share these common options: + +| Option | Type | Description | +|--------|------|-------------| +| `enabled` | boolean | Enable this channel | +| `dmPolicy` | `'pairing'` \| `'allowlist'` \| `'open'` | Access control mode | +| `allowedUsers` | string[] | User IDs/numbers for allowlist mode | + +### DM Policies + +- **`pairing`** (recommended): New users get a code, approve with `lettabot pairing approve` +- **`allowlist`**: Only specified user IDs can message +- **`open`**: Anyone can message (not recommended) + +### Channel-Specific Options + +#### Telegram +| Option | Type | Description | +|--------|------|-------------| +| `token` | string | Bot token from @BotFather | + +#### Slack +| Option | Type | Description | +|--------|------|-------------| +| `botToken` | string | Bot User OAuth Token (xoxb-...) | +| `appToken` | string | App-Level Token (xapp-...) for Socket Mode | + +#### Discord +| Option | Type | Description | +|--------|------|-------------| +| `token` | string | Bot token from Discord Developer Portal | + +#### WhatsApp +| Option | Type | Description | +|--------|------|-------------| +| `selfChat` | boolean | **Critical:** `true` = only "Message Yourself" works | + +#### Signal +| Option | Type | Description | +|--------|------|-------------| +| `phone` | string | Phone number with + prefix | +| `selfChat` | boolean | `true` = only "Note to Self" works | + +## Features Configuration + +### Heartbeat + +```yaml +features: + heartbeat: + enabled: true + intervalMin: 60 # Check every 60 minutes +``` + +Heartbeats are background tasks where the agent can review pending work. + +### Cron Jobs + +```yaml +features: + cron: true +``` + +Enable scheduled tasks. See [Cron Setup](./cron-setup.md). + +## Transcription Configuration + +Voice message transcription via OpenAI Whisper: + +```yaml +transcription: + provider: openai + apiKey: sk-... # Optional: uses OPENAI_API_KEY env var + model: whisper-1 # Default +``` + +## Attachments Configuration + +```yaml +attachments: + maxMB: 20 # Max file size to download (default: 20) + maxAgeDays: 14 # Auto-delete after N days (default: 14) +``` + +Attachments are stored in `/tmp/lettabot/attachments/`. + +## Environment Variables + +Environment variables override config file values: + +| Env Variable | Config Equivalent | +|--------------|-------------------| +| `LETTA_API_KEY` | `server.apiKey` | +| `LETTA_BASE_URL` | `server.baseUrl` | +| `LETTA_AGENT_ID` | `agent.id` | +| `LETTA_AGENT_NAME` | `agent.name` | +| `LETTA_MODEL` | `agent.model` | +| `TELEGRAM_BOT_TOKEN` | `channels.telegram.token` | +| `TELEGRAM_DM_POLICY` | `channels.telegram.dmPolicy` | +| `SLACK_BOT_TOKEN` | `channels.slack.botToken` | +| `SLACK_APP_TOKEN` | `channels.slack.appToken` | +| `DISCORD_BOT_TOKEN` | `channels.discord.token` | +| `WHATSAPP_ENABLED` | `channels.whatsapp.enabled` | +| `WHATSAPP_SELF_CHAT_MODE` | `channels.whatsapp.selfChat` | +| `SIGNAL_PHONE_NUMBER` | `channels.signal.phone` | +| `OPENAI_API_KEY` | `transcription.apiKey` | + +See [SKILL.md](../SKILL.md) for complete environment variable reference. diff --git a/docs/cron-setup.md b/docs/cron-setup.md new file mode 100644 index 0000000..22d3d23 --- /dev/null +++ b/docs/cron-setup.md @@ -0,0 +1,181 @@ +# Scheduling Tasks (Cron & Heartbeat) + +LettaBot supports two types of background tasks: +- **Cron jobs**: Send scheduled messages at specific times +- **Heartbeats**: Periodic agent check-ins + +## Enabling Background Tasks + +Add to your `lettabot.yaml`: + +```yaml +features: + cron: true + heartbeat: + enabled: true + intervalMin: 60 # Every 60 minutes +``` + +Or via environment variables: + +```bash +CRON_ENABLED=true +HEARTBEAT_ENABLED=true +HEARTBEAT_INTERVAL_MIN=60 +``` + +## Cron Jobs + +Schedule tasks that send you messages at specific times. + +### Creating a Job + +```bash +lettabot-cron create \ + --name "Morning Briefing" \ + --schedule "0 8 * * *" \ + --message "Good morning! Review tasks for today." \ + --deliver telegram:123456789 +``` + +**Options:** +- `--name` - Job name (required) +- `--schedule` - Cron expression (required) +- `--message` - Message sent when job runs (required) +- `--deliver` - Where to send: `channel:chatId` (defaults to last messaged chat) + +### Managing Jobs + +```bash +lettabot-cron list # Show all jobs +lettabot-cron delete # Delete a job +lettabot-cron enable # Enable a job +lettabot-cron disable # Disable a job +``` + +### Cron Expression Syntax + +``` +┌───────── minute (0-59) +│ ┌─────── hour (0-23) +│ │ ┌───── day of month (1-31) +│ │ │ ┌─── month (1-12) +│ │ │ │ ┌─ day of week (0-6, Sun=0) +* * * * * +``` + +**Examples:** + +| Expression | When | +|------------|------| +| `0 8 * * *` | Daily at 8:00 AM | +| `0 9 * * 1-5` | Weekdays at 9:00 AM | +| `0 */2 * * *` | Every 2 hours | +| `30 17 * * 5` | Fridays at 5:30 PM | +| `0 0 1 * *` | First of month at midnight | + +### Example Jobs + +**Daily morning check-in:** +```bash +lettabot-cron create \ + -n "Morning" \ + -s "0 8 * * *" \ + -m "Good morning! What's on today's agenda?" +``` + +**Weekly review:** +```bash +lettabot-cron create \ + -n "Weekly Review" \ + -s "0 17 * * 5" \ + -m "Friday wrap-up: What did we accomplish this week?" +``` + +**Hourly reminder:** +```bash +lettabot-cron create \ + -n "Hydration" \ + -s "0 * * * *" \ + -m "Time to drink water!" +``` + +## Heartbeats + +Heartbeats are periodic check-ins where the agent can: +- Review pending tasks +- Check reminders +- Perform proactive actions + +### Configuration + +```yaml +features: + heartbeat: + enabled: true + intervalMin: 60 # Default: 60 minutes +``` + +### Manual Trigger + +You can trigger a heartbeat manually via the `/heartbeat` command in any channel. + +### How It Works + +1. At each interval (or when `/heartbeat` is called), the agent receives a heartbeat message +2. The agent runs in **Silent Mode** - responses are not automatically delivered +3. If the agent wants to message you, it must use `lettabot-message send` + +This prevents unwanted messages while allowing proactive behavior when needed. + +## Silent Mode + +Both cron jobs and heartbeats run in **Silent Mode**: + +- The agent's text output is NOT automatically sent to users +- The agent sees a `[SILENT MODE]` banner with instructions +- To send messages, the agent must explicitly run: + +```bash +lettabot-message send --text "Your message here" +``` + +**Requirements for background messaging:** +- Bash tool must be enabled for the agent +- A user must have messaged the bot at least once (establishes delivery target) + +## Monitoring & Logs + +### Check Job Status + +```bash +lettabot-cron list +``` + +Shows: +- Job ID, name, schedule +- Next run time +- Last run status + +### Log Files + +- `cron-jobs.json` - Job configurations +- `cron-log.jsonl` - Execution logs + +## Troubleshooting + +### Cron jobs not running + +1. Check `features.cron: true` in config +2. Verify schedule expression is valid +3. Check `lettabot-cron list` for next run time + +### Agent not sending messages during heartbeat + +1. Check if Bash tool is enabled (agent needs to run CLI) +2. Verify a user has messaged the bot at least once +3. Check the [ADE](https://app.letta.com) to see agent activity + +### Jobs running but no messages received + +The agent runs in Silent Mode - it must actively choose to send messages. Check the agent's behavior in the ADE to see what it's doing during background tasks. diff --git a/docs/getting-started.md b/docs/getting-started.md index e9c6692..24d367e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -4,7 +4,7 @@ Get LettaBot running in 5 minutes. ## Prerequisites -- Node.js 18+ +- Node.js 20+ - npm or yarn - A Telegram account - A Letta account ([app.letta.com](https://app.letta.com)) @@ -14,7 +14,7 @@ Get LettaBot running in 5 minutes. ### 1. Clone and Install ```bash -git clone https://github.com/yourusername/lettabot.git +git clone https://github.com/letta-ai/lettabot.git cd lettabot npm install ``` @@ -37,7 +37,9 @@ npm install **Option A: Interactive Setup (Recommended)** ```bash -npm run setup +npm run build +npm link +lettabot onboard ``` This will walk you through configuration interactively. @@ -97,4 +99,5 @@ ALLOWED_USERS=123456789,987654321 - [Commands Reference](./commands.md) - Learn all bot commands - [Gmail Integration](./gmail-pubsub.md) - Set up email notifications -- Check out [PLAN.md](../PLAN.md) for the full roadmap +- [Slack Setup](./slack-setup.md) - Add Slack channel +- [Discord Setup](./discord-setup.md) - Add Discord channel diff --git a/docs/slack-setup.md b/docs/slack-setup.md index 142ed2b..e046f2d 100644 --- a/docs/slack-setup.md +++ b/docs/slack-setup.md @@ -211,5 +211,5 @@ First responses may take longer as the agent "wakes up". ## Next Steps - [WhatsApp Setup](./whatsapp-setup.md) -- [Cron Jobs](./cron-setup.md) -- [Configuration Reference](./configuration.md) +- [Discord Setup](./discord-setup.md) +- [Signal Setup](./signal-setup.md) diff --git a/docs/whatsapp-setup.md b/docs/whatsapp-setup.md index 191e4e7..5aaf61e 100644 --- a/docs/whatsapp-setup.md +++ b/docs/whatsapp-setup.md @@ -17,19 +17,43 @@ LettaBot connects to WhatsApp using **Baileys**, which uses the WhatsApp Web pro ## Step 1: Enable WhatsApp in Configuration -Add to your `.env` file: +Add to your `lettabot.yaml` or set environment variables: + +```yaml +# lettabot.yaml +channels: + whatsapp: + enabled: true + selfChatMode: true # IMPORTANT: See below + dmPolicy: pairing # or 'allowlist' or 'open' +``` + +Or via environment variables: ```bash # WhatsApp Configuration WHATSAPP_ENABLED=true +WHATSAPP_SELF_CHAT_MODE=true # CRITICAL - see below +WHATSAPP_DM_POLICY=pairing -# Optional: Custom session storage path (default: ./data/whatsapp-session) -# WHATSAPP_SESSION_PATH=./data/whatsapp-session - -# Optional: Restrict to specific phone numbers +# Optional: Restrict to specific phone numbers (if dmPolicy=allowlist) # WHATSAPP_ALLOWED_USERS=+15551234567,+15559876543 ``` +### Self-Chat Mode (Critical Safety Setting) + +**`selfChatMode: true`** (default, recommended for personal numbers): +- Bot ONLY responds to "Message Yourself" chat +- Bot will NOT message your contacts +- Safe to use with your personal WhatsApp number + +**`selfChatMode: false`** (for dedicated bot numbers): +- Bot responds to ALL incoming messages +- Use ONLY with a dedicated phone number +- Risk of bot messaging your contacts if misconfigured + +> **Warning:** If using your personal WhatsApp number, ALWAYS keep `selfChatMode: true` to prevent the bot from accidentally messaging your contacts. + ## Step 2: Start LettaBot ```bash @@ -108,12 +132,31 @@ This uses your personal WhatsApp account: - Messages appear as coming from your number - Consider using a dedicated phone number for the bot - Your contacts will see the bot as "you" +- **Use `selfChatMode: true`** to prevent bot from messaging your contacts ### Multi-Device Limitations - WhatsApp allows up to 4 linked devices - The bot counts as one linked device - If you unlink the bot, you'll need to scan the QR code again +## Media Support + +LettaBot supports receiving images, documents, and voice messages: + +- **Images**: Downloaded and shown to the agent (agent can view using Read tool) +- **Voice messages**: Automatically transcribed via OpenAI Whisper +- **Documents**: Downloaded with metadata shown to agent + +Configure attachment handling in `lettabot.yaml`: + +```yaml +attachments: + maxMB: 20 # Max file size to download (default: 20MB) + maxAgeDays: 14 # Auto-delete after N days (default: 14) +``` + +Attachments are stored in `/tmp/lettabot/attachments/` by default. + ## Running in Production For production deployments: @@ -150,9 +193,10 @@ If you see "logged out" in the logs: ### Messages Not Being Received -1. Make sure the sender's number is in `WHATSAPP_ALLOWED_USERS` (if configured) -2. Check that the message is a text message (media not fully supported yet) -3. Look for errors in the console +1. Check `selfChatMode` setting - if `true`, only "Message Yourself" works +2. Make sure the sender's number is allowed by `dmPolicy` setting +3. If `dmPolicy: allowlist`, check `allowedUsers` list +4. Look for errors in the console ### Bot Responding to Old Messages @@ -178,5 +222,5 @@ data/ ## Next Steps - [Slack Setup](./slack-setup.md) -- [Cron Jobs](./cron-setup.md) -- [Configuration Reference](./configuration.md) +- [Discord Setup](./discord-setup.md) +- [Signal Setup](./signal-setup.md)