feat: add non-interactive onboarding and SKILL.md (#45)

* feat: add non-interactive onboarding and SKILL.md

Add agent-friendly setup flow:
- lettabot onboard --non-interactive flag
- Reads all config from environment variables
- SKILL.md documents env-based setup for agents
- Supports all channels (Telegram, Slack, Discord, WhatsApp, Signal)
- No prompts - ideal for coding agents automating setup

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: address non-interactive setup issues

- Add SLACK_APP_NAME for customizable app name (defaults to LETTA_AGENT_NAME or LettaBot)
- Clarify WhatsApp requires WHATSAPP_ENABLED and WHATSAPP_SELF_CHAT to be explicit
- Document all 5 channels supported (Telegram, Slack, Discord, WhatsApp, Signal)
- Fix WhatsApp selfChat default to be explicit false

* docs: recommend non-interactive setup as primary method

Update README per review feedback to show env-based setup first.
This is simpler for most users and ideal for automation.

* docs: rewrite setup to be AI-first per feedback

Make recommended setup AI-focused:
- Show prompt to paste into AI coding assistants
- AI handles clone/install/config autonomously
- Manual wizard becomes Option 2 for human users

---------

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
cthomas
2026-01-30 16:14:29 -08:00
committed by GitHub
parent 76ea0da8fd
commit 1d66f42dad
5 changed files with 478 additions and 5 deletions

View File

@@ -50,9 +50,29 @@ See the [documentation](https://docs.letta.com/guides/docker/) for more details
### Setup
Run the interactive onboarding wizard:
**Option 1: AI-Assisted Setup (Recommended)**
Paste this into Letta Code, Claude Code, Codex CLI, or any AI coding assistant:
```
Clone https://github.com/letta-ai/lettabot, read the SKILL.md
for setup instructions, and help me configure Telegram.
```
You'll need:
- A Letta API key from [app.letta.com](https://app.letta.com) (or a [Letta Docker server](https://docs.letta.com/guides/docker/))
- A Telegram bot token from [@BotFather](https://t.me/BotFather)
The AI will handle the rest: cloning, installing dependencies, reading setup docs, and configuring your bot.
**Option 2: Interactive Wizard**
For manual step-by-step setup:
```bash
git clone https://github.com/letta-ai/lettabot.git
cd lettabot
npm install && npm run build && npm link
lettabot onboard
```
@@ -64,6 +84,8 @@ lettabot server
That's it! Message your bot on Telegram.
> **Note:** For detailed environment variable reference and multi-channel setup, see [SKILL.md](./SKILL.md)
## Skills
LettaBot is compatible with [skills.sh](https://skills.sh) and [Clawdhub](https://clawdhub.com/).

295
SKILL.md Normal file
View File

@@ -0,0 +1,295 @@
---
name: lettabot
description: Set up and run LettaBot - a multi-channel AI assistant for Telegram, Slack, Discord, WhatsApp, and Signal. Supports both interactive wizard and non-interactive (agent-friendly) configuration.
---
# LettaBot Setup
Multi-channel AI assistant with persistent memory across Telegram, Slack, Discord, WhatsApp, and Signal.
## Quick Setup (Agent-Friendly)
For non-interactive setup (ideal for coding agents):
```bash
# 1. Clone and install
git clone https://github.com/letta-ai/lettabot.git
cd lettabot
npm install
npm run build
npm link
# 2. Configure via environment variables
export LETTA_API_KEY="letta_..." # From app.letta.com
export LETTA_BASE_URL="https://api.letta.com" # Or self-hosted
export LETTA_AGENT_ID="agent-..." # Optional: use existing agent
# 3. Configure channel (example: Telegram)
export TELEGRAM_BOT_TOKEN="123456:ABC-DEF..." # From @BotFather
export TELEGRAM_DM_POLICY="pairing" # Optional: pairing | allowlist | open
# 4. Run non-interactive setup
lettabot onboard --non-interactive
# 5. Start the bot
lettabot server
```
## Interactive Setup
For human-friendly setup with wizard:
```bash
lettabot onboard
```
The wizard will guide you through:
- Letta API authentication (OAuth or API key)
- Agent selection/creation
- Channel configuration (Telegram, Slack, Discord, WhatsApp, Signal)
## Environment Variables
### Required
| Variable | Description |
|----------|-------------|
| `LETTA_API_KEY` | API key from app.letta.com (or skip for OAuth) |
| `LETTA_BASE_URL` | API endpoint (default: https://api.letta.com) |
### Agent Selection
| Variable | Description |
|----------|-------------|
| `LETTA_AGENT_ID` | Use existing agent (skip agent creation) |
| `LETTA_AGENT_NAME` | Name for new agent (default: "lettabot") |
| `LETTA_MODEL` | Model for new agent (default: "claude-sonnet-4") |
### Telegram
| Variable | Description | Required |
|----------|-------------|----------|
| `TELEGRAM_BOT_TOKEN` | Bot token from @BotFather | ✅ |
| `TELEGRAM_DM_POLICY` | Access control: `pairing` \| `allowlist` \| `open` | ❌ (default: pairing) |
| `TELEGRAM_ALLOWED_USERS` | Comma-separated user IDs (if dmPolicy=allowlist) | ❌ |
### Slack (Socket Mode)
| Variable | Description | Required |
|----------|-------------|----------|
| `SLACK_BOT_TOKEN` | Bot User OAuth Token (xoxb-...) | ✅ |
| `SLACK_APP_TOKEN` | App-Level Token (xapp-...) for Socket Mode | ✅ |
| `SLACK_APP_NAME` | Custom app name (default: LETTA_AGENT_NAME or "LettaBot") | ❌ |
| `SLACK_DM_POLICY` | Access control: `pairing` \| `allowlist` \| `open` | ❌ (default: pairing) |
| `SLACK_ALLOWED_USERS` | Comma-separated Slack user IDs (if dmPolicy=allowlist) | ❌ |
**Setup Slack app:** See [Slack Setup Wizard](./src/setup/slack-wizard.ts) or run `lettabot onboard` for guided setup.
### Discord
| Variable | Description | Required |
|----------|-------------|----------|
| `DISCORD_BOT_TOKEN` | Bot token from discord.com/developers/applications | ✅ |
| `DISCORD_DM_POLICY` | Access control: `pairing` \| `allowlist` \| `open` | ❌ (default: pairing) |
| `DISCORD_ALLOWED_USERS` | Comma-separated Discord user IDs (if dmPolicy=allowlist) | ❌ |
**Setup Discord bot:** See [docs/discord-setup.md](./docs/discord-setup.md)
### WhatsApp
| Variable | Description | Required |
|----------|-------------|----------|
| `WHATSAPP_ENABLED` | Enable WhatsApp: `true` \| `false` | ✅ Must be explicit |
| `WHATSAPP_SELF_CHAT` | Self-chat mode: `true` (personal number) \| `false` (dedicated bot number) | ✅ Must be explicit |
| `WHATSAPP_DM_POLICY` | Access control: `pairing` \| `allowlist` \| `open` | ❌ (default: pairing) |
| `WHATSAPP_ALLOWED_USERS` | Comma-separated phone numbers with + (if dmPolicy=allowlist) | ❌ |
**Important:**
- `WHATSAPP_SELF_CHAT=false` (dedicated bot number): Responds to ALL incoming messages
- `WHATSAPP_SELF_CHAT=true` (personal number): Only responds to "Message Yourself" chat
- QR code appears on first run - scan with WhatsApp app
### Signal
| Variable | Description | Required |
|----------|-------------|----------|
| `SIGNAL_PHONE_NUMBER` | Your phone number (with +) | ✅ |
| `SIGNAL_DM_POLICY` | Access control: `pairing` \| `allowlist` \| `open` | ❌ (default: pairing) |
| `SIGNAL_ALLOWED_USERS` | Comma-separated phone numbers with + (if dmPolicy=allowlist) | ❌ |
**Setup:** Requires Signal CLI - see [signal-cli documentation](https://github.com/AsamK/signal-cli).
## Channel-Specific Setup
### Telegram Bot Setup
1. Message [@BotFather](https://t.me/BotFather) on Telegram
2. Send `/newbot` and follow prompts
3. Copy the token (format: `123456:ABC-DEF...`)
4. Set `TELEGRAM_BOT_TOKEN` environment variable
### Slack App Setup (Interactive)
For Socket Mode (required for real-time messages):
```bash
lettabot onboard
# Select "Slack" → "Guided setup"
```
This uses a manifest to pre-configure:
- Socket Mode
- 5 bot scopes (app_mentions:read, chat:write, im:*)
- 2 event subscriptions (app_mention, message.im)
### Slack App Setup (Manual)
1. Go to [api.slack.com/apps](https://api.slack.com/apps)
2. Create app from manifest (see `src/setup/slack-wizard.ts` for manifest YAML)
3. Install to workspace → copy Bot Token (`xoxb-...`)
4. Enable Socket Mode → generate App Token (`xapp-...`)
5. Set both tokens in environment
## Access Control
Each channel supports three DM policies:
- **`pairing`** (recommended): Users get a code, you approve via `lettabot pairing approve <channel> <code>`
- **`allowlist`**: Only specified user IDs can message
- **`open`**: Anyone can message (not recommended)
## Configuration File
After onboarding, config is saved to `~/.config/lettabot/config.yaml`:
```yaml
server:
baseUrl: https://api.letta.com
apiKey: letta_...
agentId: agent-...
telegram:
enabled: true
botToken: 123456:ABC-DEF...
dmPolicy: pairing
slack:
enabled: true
botToken: xoxb-...
appToken: xapp-...
dmPolicy: pairing
```
Edit this file directly or re-run `lettabot onboard` to reconfigure.
## Commands
```bash
# Setup
lettabot onboard # Interactive wizard
lettabot onboard --non-interactive # Env-based setup (agent-friendly)
# Run
lettabot server # Start bot server
# Manage
lettabot pairing list # List pending pairing requests
lettabot pairing approve <channel> <code> # Approve user
lettabot skills # Enable/disable skills
# Scheduling
lettabot cron list # List scheduled tasks
lettabot cron add "Daily standup at 9am" "0 9 * * *" # Add cron job
```
## Troubleshooting
### "Module not found" errors
Make sure you've run `npm run build` after installing or pulling updates.
### Telegram bot not responding
1. Check token is correct: `curl https://api.telegram.org/bot<TOKEN>/getMe`
2. Ensure bot is started: `lettabot server` should show "Connected to Telegram"
3. Check access control: User may need pairing approval
### Slack not receiving messages
1. Verify Socket Mode is enabled in Slack app settings
2. Check both tokens are set: `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN`
3. Ensure event subscriptions are configured (app_mention, message.im)
### WhatsApp QR code not appearing
1. Make sure Signal Desktop is closed (conflicts with baileys)
2. Delete `~/.wwebjs_auth` if previously used different library
3. Check no other WhatsApp Web sessions are active
## Example: Agent Setup Flow
For coding agents helping users set up LettaBot:
```bash
# 1. Clone and build
git clone https://github.com/letta-ai/lettabot.git
cd lettabot
npm install && npm run build && npm link
# 2. Get Letta API key
# Guide user to app.letta.com → API Keys → Create Key
# 3. Get Telegram bot token
# Guide user to @BotFather → /newbot → follow prompts
# 4. Set environment variables
export LETTA_API_KEY="letta_..."
export TELEGRAM_BOT_TOKEN="123456:ABC-DEF..."
# 5. Run non-interactive setup
lettabot onboard --non-interactive
# 6. Start server
lettabot server
```
The agent can verify success by checking:
- `lettabot server` output shows "Connected to Telegram"
- Config file exists at `~/.config/lettabot/config.yaml`
- User can message bot on Telegram
## Self-Hosted Letta
To use a self-hosted Letta server:
```bash
# Run Letta Docker
docker run -v ~/.letta/.persist/pgdata:/var/lib/postgresql/data \
-p 8283:8283 \
-e OPENAI_API_KEY="your_openai_api_key" \
letta/letta:latest
# Configure LettaBot
export LETTA_BASE_URL="http://localhost:8283"
export LETTA_API_KEY="sk-..." # From Letta admin panel
lettabot onboard --non-interactive
```
## Skills Integration
LettaBot supports loading skills from:
- **Clawdhub** ([clawdhub.com](https://clawdhub.com))
- **skills.sh** repositories
- Local `.skills/` directory
```bash
# Install skill from Clawdhub
npx molthub@latest install sonoscli
# Connect to LettaBot
lettabot skills
# Space to toggle, Enter to confirm
# Skills will be available to agent
```

View File

@@ -206,7 +206,8 @@ async function main() {
case 'onboard':
case 'setup':
case 'init':
await onboard();
const nonInteractive = args.includes('--non-interactive') || args.includes('-n');
await onboard({ nonInteractive });
break;
case 'server':

View File

@@ -9,6 +9,119 @@ import * as p from '@clack/prompts';
import { saveConfig, syncProviders } from './config/index.js';
import type { LettaBotConfig, ProviderConfig } from './config/types.js';
// ============================================================================
// Non-Interactive Helpers
// ============================================================================
function readConfigFromEnv(existingConfig: any): any {
return {
baseUrl: process.env.LETTA_BASE_URL || existingConfig.server?.baseUrl || 'https://api.letta.com',
apiKey: process.env.LETTA_API_KEY || existingConfig.server?.apiKey,
agentId: process.env.LETTA_AGENT_ID || existingConfig.agent?.id,
agentName: process.env.LETTA_AGENT_NAME || existingConfig.agent?.name || 'lettabot',
model: process.env.LETTA_MODEL || existingConfig.agent?.model || 'claude-sonnet-4',
telegram: {
enabled: !!process.env.TELEGRAM_BOT_TOKEN,
botToken: process.env.TELEGRAM_BOT_TOKEN || existingConfig.channels?.telegram?.token,
dmPolicy: process.env.TELEGRAM_DM_POLICY || existingConfig.channels?.telegram?.dmPolicy || 'pairing',
allowedUsers: process.env.TELEGRAM_ALLOWED_USERS?.split(',').map(s => s.trim()) || existingConfig.channels?.telegram?.allowedUsers,
},
slack: {
enabled: !!(process.env.SLACK_BOT_TOKEN && process.env.SLACK_APP_TOKEN),
botToken: process.env.SLACK_BOT_TOKEN || existingConfig.channels?.slack?.botToken,
appToken: process.env.SLACK_APP_TOKEN || existingConfig.channels?.slack?.appToken,
dmPolicy: process.env.SLACK_DM_POLICY || existingConfig.channels?.slack?.dmPolicy || 'pairing',
allowedUsers: process.env.SLACK_ALLOWED_USERS?.split(',').map(s => s.trim()) || existingConfig.channels?.slack?.allowedUsers,
},
discord: {
enabled: !!process.env.DISCORD_BOT_TOKEN,
botToken: process.env.DISCORD_BOT_TOKEN || existingConfig.channels?.discord?.token,
dmPolicy: process.env.DISCORD_DM_POLICY || existingConfig.channels?.discord?.dmPolicy || 'pairing',
allowedUsers: process.env.DISCORD_ALLOWED_USERS?.split(',').map(s => s.trim()) || existingConfig.channels?.discord?.allowedUsers,
},
whatsapp: {
enabled: process.env.WHATSAPP_ENABLED === 'true' || !!existingConfig.channels?.whatsapp?.enabled,
selfChat: process.env.WHATSAPP_SELF_CHAT === 'true' || !!existingConfig.channels?.whatsapp?.selfChat || false,
dmPolicy: process.env.WHATSAPP_DM_POLICY || existingConfig.channels?.whatsapp?.dmPolicy || 'pairing',
allowedUsers: process.env.WHATSAPP_ALLOWED_USERS?.split(',').map(s => s.trim()) || existingConfig.channels?.whatsapp?.allowedUsers,
},
signal: {
enabled: !!process.env.SIGNAL_PHONE_NUMBER,
phoneNumber: process.env.SIGNAL_PHONE_NUMBER || existingConfig.channels?.signal?.phoneNumber,
dmPolicy: process.env.SIGNAL_DM_POLICY || existingConfig.channels?.signal?.dmPolicy || 'pairing',
allowedUsers: process.env.SIGNAL_ALLOWED_USERS?.split(',').map(s => s.trim()) || existingConfig.channels?.signal?.allowedUsers,
},
};
}
async function saveConfigFromEnv(config: any, configPath: string): Promise<void> {
const { saveConfig } = await import('./config/index.js');
const lettabotConfig: LettaBotConfig = {
server: {
mode: config.baseUrl?.includes('localhost') ? 'selfhosted' : 'cloud',
baseUrl: config.baseUrl,
apiKey: config.apiKey,
},
agent: {
id: config.agentId,
name: config.agentName,
model: config.model,
},
channels: {
telegram: config.telegram.enabled ? {
enabled: true,
token: config.telegram.botToken,
dmPolicy: config.telegram.dmPolicy,
allowedUsers: config.telegram.allowedUsers,
} : { enabled: false },
slack: config.slack.enabled ? {
enabled: true,
botToken: config.slack.botToken,
appToken: config.slack.appToken,
allowedUsers: config.slack.allowedUsers,
} : { enabled: false },
discord: config.discord.enabled ? {
enabled: true,
token: config.discord.botToken,
dmPolicy: config.discord.dmPolicy,
allowedUsers: config.discord.allowedUsers,
} : { enabled: false },
whatsapp: config.whatsapp.enabled ? {
enabled: true,
selfChat: config.whatsapp.selfChat,
dmPolicy: config.whatsapp.dmPolicy,
allowedUsers: config.whatsapp.allowedUsers,
} : { enabled: false },
signal: config.signal.enabled ? {
enabled: true,
phone: config.signal.phoneNumber,
selfChat: config.signal.selfChat,
dmPolicy: config.signal.dmPolicy,
allowedUsers: config.signal.allowedUsers,
} : { enabled: false },
},
features: {
cron: false,
heartbeat: {
enabled: false,
intervalMin: 60,
},
},
};
saveConfig(lettabotConfig);
}
// ============================================================================
// Config Types
// ============================================================================
@@ -1173,7 +1286,8 @@ async function reviewLoop(config: OnboardConfig, env: Record<string, string>): P
// Main Onboard Function
// ============================================================================
export async function onboard(): Promise<void> {
export async function onboard(options?: { nonInteractive?: boolean }): Promise<void> {
const nonInteractive = options?.nonInteractive || false;
// Temporary storage for wizard values
const env: Record<string, string> = {};
@@ -1183,6 +1297,46 @@ export async function onboard(): Promise<void> {
const configPath = resolveConfigPath();
const hasExistingConfig = existsSync(configPath);
// Non-interactive mode: read all config from env vars
if (nonInteractive) {
console.log('🤖 LettaBot Non-Interactive Setup\n');
console.log('Reading configuration from environment variables...\n');
const config = readConfigFromEnv(existingConfig);
// Validate required fields
if (!config.baseUrl) {
console.error('❌ Error: LETTA_BASE_URL is required');
process.exit(1);
}
if (!config.apiKey && !config.baseUrl?.includes('localhost')) {
console.error('❌ Error: LETTA_API_KEY is required (or use self-hosted with LETTA_BASE_URL)');
process.exit(1);
}
// Test server connection
console.log(`Connecting to ${config.baseUrl}...`);
try {
const res = await fetch(`${config.baseUrl}/v1/health`, { signal: AbortSignal.timeout(5000) });
if (res.ok) {
console.log('✅ Connected to server\n');
} else {
console.error(`❌ Server returned status ${res.status}`);
process.exit(1);
}
} catch (e) {
console.error(`❌ Could not connect to ${config.baseUrl}`);
process.exit(1);
}
// Save config and exit
await saveConfigFromEnv(config, configPath);
console.log(`✅ Configuration saved to ${configPath}\n`);
console.log('Run "lettabot server" to start the bot.');
return;
}
p.intro('🤖 LettaBot Setup');
if (hasExistingConfig) {

View File

@@ -85,13 +85,14 @@ async function stepCreateApp(): Promise<boolean> {
p.log.step('Step 1/3: Create Slack App from Manifest');
// Inline manifest for Socket Mode configuration
const appName = process.env.SLACK_APP_NAME || process.env.LETTA_AGENT_NAME || 'LettaBot';
const manifest = `display_information:
name: LettaBot
name: ${appName}
description: AI assistant with Socket Mode for real-time conversations
background_color: "#2c2d30"
features:
bot_user:
display_name: LettaBot
display_name: ${appName}
always_online: false
oauth_config:
scopes: