> ## Documentation Index
> Fetch the complete documentation index at: https://docs.echos.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Interfaces

> Configure and use Telegram, Web API, CLI, and MCP Server interfaces.

# Interface Guide

EchOS supports four interfaces for interacting with your personal knowledge base. You can enable/disable each interface in your `.env` file.

## Configuration

Edit `.env` to control which interfaces are active:

```bash theme={null}
# Enable/disable interfaces
ENABLE_TELEGRAM=true   # Telegram bot (stable, recommended)
ENABLE_WEB=false       # Web API server (experimental, disabled by default)
ENABLE_MCP=false       # MCP server for AI agent integrations (disabled by default)

# Web server port (only used when ENABLE_WEB=true)
WEB_PORT=3000

# Web API authentication key — mandatory when ENABLE_WEB=true (server exits without it)
# Generate with: openssl rand -hex 32
WEB_API_KEY=your_secret_key_here

# MCP server port (only used when ENABLE_MCP=true)
MCP_PORT=3939

# MCP API key — optional authentication for the MCP server
# The daemon binds to 127.0.0.1 by default; setting this key does not by itself enable or secure remote access
# If you expose MCP via a proxy or port-forward, set this key and add appropriate network/TLS protections
# Generate with: openssl rand -hex 32
MCP_API_KEY=your_secret_key_here
```

<Note>The Web UI is experimental. The CLI (`pnpm echos`) is stable and requires no daemon. Telegram is the recommended interface for daily use. The setup wizard (`pnpm wizard`) generates a `WEB_API_KEY` automatically when you enable the web interface.</Note>

**Start the application:**

```bash theme={null}
pnpm start
```

The application will start all enabled interfaces simultaneously.

***

## 1. Telegram Bot Interface

**Default**: Enabled

### Setup

1. Create a bot with [@BotFather](https://t.me/BotFather) on Telegram
2. Get your bot token
3. Get your Telegram user ID from [@userinfobot](https://t.me/userinfobot)
4. Configure in `.env`:
   ```bash theme={null}
   TELEGRAM_BOT_TOKEN=your_token_here
   ALLOWED_USER_IDS=123456789,987654321
   ```

### Usage

1. Start EchOS: `pnpm start`
2. Open Telegram and message your bot
3. Send natural language commands:
   * `"Save this article: https://example.com/post"`
   * `"What notes do I have about TypeScript?"`
   * `"Create a note called 'Project Ideas'"`
   * `"Remind me to review the proposal tomorrow"`

### Commands

| Command                         | Description                                            |
| ------------------------------- | ------------------------------------------------------ |
| `/start`                        | Welcome message                                        |
| `/reset`                        | Clear conversation history and start fresh             |
| `/usage`                        | Show token usage and cost for the current session      |
| `/followup <message>`           | Queue a message to run after the current task finishes |
| `/model [fast\|balanced\|deep]` | Show or switch the model for the current session       |

### Steering (mid-run interruption)

If the agent is currently processing a task and you send a new text message, EchOS steers the agent rather than queuing a second turn:

1. The agent finishes its current tool call
2. Remaining tool calls in the turn are skipped
3. The new message is injected and the agent responds to it

The agent replies "↩️ Redirecting..." immediately to acknowledge receipt. The final response (for the original turn, now steered) is updated in the original message thread.

Use `/followup` instead when you want to *chain* work after the current task — e.g. "save this article" then `/followup summarise the key points`.

### Exporting Notes

Ask the agent to export notes and it will send the file as a document attachment:

```
"Export my Redis note as a markdown file"
→ bot sends redis-note.md as a document

"Export all my programming notes as a zip"
→ bot sends export-{timestamp}.zip as a document
```

Supported formats: `markdown`, `text`, `json`, `zip`. All exports are sent as document attachments. Single-note markdown/text exports are delivered directly as small document files; multi-note exports or explicit `zip`/`json` format requests write a temporary file to `data/exports/` before sending. The temporary file is deleted automatically after the bot sends it.

### Inline Keyboards

Certain tool results include inline keyboard buttons for quick actions without typing:

* **Reading queue** — "Mark Read" buttons per item, plus link buttons for items with URLs
* **List notes** — "Mark Read" and "Archive" buttons for non-archived items
* **Reminders** — "Complete" buttons for pending reminders

When you press a button, the action executes immediately (no AI round-trip) and the button is removed from the keyboard. These actions work the same as asking the agent to mark content as read, archive notes, or complete reminders.

### Features

* ✅ Streaming responses (live updates as AI thinks)
* ✅ Multi-user support (each user has isolated sessions)
* ✅ Rate limiting per user
* ✅ Authentication via `ALLOWED_USER_IDS`
* ✅ Voice message support (with Whisper transcription)
* ✅ Photo/document support
* ✅ Mid-run steering and follow-up queuing
* ✅ File exports delivered as document attachments
* ✅ Inline keyboards for quick actions on list results
* ✅ Configurable emoji reactions (can be disabled)

### Configuration

| Variable             | Default | Description                                                                    |
| -------------------- | ------- | ------------------------------------------------------------------------------ |
| `TELEGRAM_BOT_TOKEN` | —       | Bot token from @BotFather (required)                                           |
| `ALLOWED_USER_IDS`   | —       | Comma-separated Telegram user IDs (required)                                   |
| `TELEGRAM_REACTIONS` | `true`  | Send emoji reactions (👀, 🤗) on incoming messages. Set to `false` to disable. |

### Security

Only users listed in `ALLOWED_USER_IDS` can interact with the bot. All requests are validated and sanitized.

***

## 2. Web API Interface

**Default**: Disabled — **experimental**

### Setup

Configure in `.env`:

```bash theme={null}
ENABLE_WEB=true
WEB_PORT=3000
WEB_API_KEY=your_secret_key_here   # required — server refuses to start without it
```

Generate a key:

```bash theme={null}
openssl rand -hex 32
```

Or run `pnpm wizard` — it generates and stores the key automatically.

<Warning>If `ENABLE_WEB=true` and `WEB_API_KEY` is not set, the web server throws at startup and the process exits. There is no unauthenticated fallback.</Warning>

### Usage

Start EchOS and access the API at `http://localhost:3000`

### Endpoints

#### Health Check

```bash theme={null}
GET /health
```

**Response:**

```json theme={null}
{
  "status": "ok",
  "timestamp": "2026-02-15T23:45:00.000Z"
}
```

All API routes except `/health` require an `Authorization` header:

```
Authorization: Bearer <WEB_API_KEY>
```

The `userId` in every request must be one of the values in `ALLOWED_USER_IDS` — requests with unknown user IDs are rejected with `403`.

#### Send Chat Message

```bash theme={null}
POST /api/chat
Content-Type: application/json
Authorization: Bearer <WEB_API_KEY>

{
  "userId": 123,
  "message": "What notes do I have?"
}
```

**Response:**

```json theme={null}
{
  "response": "You have 15 notes covering topics like...",
  "toolCalls": [
    { "name": "list_notes", "result": "success" }
  ]
}
```

#### Steer Running Agent

Interrupt the agent mid-turn. Only valid while a `/api/chat` request is in flight for the same `userId`. Skips remaining tool calls and injects the new message.

```bash theme={null}
POST /api/chat/steer
Content-Type: application/json
Authorization: Bearer <WEB_API_KEY>

{ "userId": 123, "message": "Actually focus on X instead" }
```

Returns `409` if the agent is not currently running.

#### Switch Model Preset

```bash theme={null}
POST /api/chat/model
Content-Type: application/json
Authorization: Bearer <WEB_API_KEY>

{ "userId": 123, "preset": "balanced" }
```

Valid presets: `fast` | `balanced` | `deep`. Returns `{ ok: true, model: "<model-id>" }`.

#### Queue Follow-up

Queue a message to run after the current agent turn completes. Safe to call at any time — the message is held until the agent is idle.

```bash theme={null}
POST /api/chat/followup
Content-Type: application/json
Authorization: Bearer <WEB_API_KEY>

{ "userId": 123, "message": "Now summarise what you just saved" }
```

#### Reset Chat Session

```bash theme={null}
POST /api/chat/reset
Content-Type: application/json
Authorization: Bearer <WEB_API_KEY>

{
  "userId": 123
}
```

**Response:**

```json theme={null}
{
  "ok": true
}
```

### Examples

**Create a note:**

```bash theme={null}
curl -X POST http://localhost:3000/api/chat \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $WEB_API_KEY" \
  -d '{"userId": 123, "message": "Create a note about quantum computing basics"}'
```

**Search knowledge:**

```bash theme={null}
curl -X POST http://localhost:3000/api/chat \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $WEB_API_KEY" \
  -d '{"userId": 123, "message": "Find notes about machine learning"}'
```

**Save an article:**

```bash theme={null}
curl -X POST http://localhost:3000/api/chat \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $WEB_API_KEY" \
  -d '{"userId": 123, "message": "Save this article: https://example.com/ai-trends"}'
```

**Health check (no auth required):**

```bash theme={null}
curl http://localhost:3000/health
```

### Exporting Notes

Ask the agent to export notes. The agent's response will include a download link:

```
POST /api/chat  { "message": "export my redis note as json", "userId": 123 }
→ { "response": "Here is your export: /api/export/export-123.json", ... }

GET /api/export/export-123.json
Authorization: Bearer <WEB_API_KEY>
→ file download
```

The export file endpoint `GET /api/export/:fileName` requires Bearer auth and validates the filename against `^[\w-]+\.(zip|json|md|txt)$` to prevent path traversal. Files are automatically cleaned up after 1 hour by the scheduler. Example URL: `/api/export/export-1234567890.zip`.

### Features

* ✅ RESTful JSON API
* ✅ Bearer token authentication (`WEB_API_KEY`)
* ✅ `userId` validated against `ALLOWED_USER_IDS`
* ✅ CORS restricted to localhost origins
* ✅ Server binds to `127.0.0.1` (not externally reachable)
* ✅ Session management per user
* ✅ Tool execution tracking
* ✅ Non-streaming responses (full response after completion)
* ✅ Export file download via `GET /api/export/:fileName`

### Integration

Build a frontend application that calls this API:

```javascript theme={null}
const API_KEY = process.env.WEB_API_KEY; // load from env, never hardcode

async function chat(message, userId) {
  const response = await fetch('http://localhost:3000/api/chat', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`,
    },
    body: JSON.stringify({ userId, message }),
  });
  return await response.json();
}

// Usage
const result = await chat('What are my recent notes?', 123);
console.log(result.response);
```

***

## 3. CLI / Terminal Interface

The CLI is a standalone binary that runs directly against your data — no daemon required. It auto-detects how it's being used and switches modes accordingly.

### Three Modes

**One-shot** — pass a query as an argument, get an answer, exit:

```bash theme={null}
pnpm echos "find my notes on TypeScript"
pnpm echos "what reminders do I have this week?"
```

**Pipe** — pipe text in, get plain text out, exit:

```bash theme={null}
echo "summarise my last 5 notes" | pnpm echos
cat meeting-notes.md | pnpm echos "extract the action items from this"
pnpm echos "list my notes" | grep TypeScript
```

**Interactive REPL** — no args, TTY detected, persistent session:

```bash theme={null}
pnpm echos
```

### No Daemon Needed

`pnpm echos` boots its own agent in-process and connects directly to the same `./data/` directory the daemon uses. SQLite WAL mode makes concurrent access safe — run the CLI over SSH while the daemon is serving Telegram and Web without conflicts.

```
pnpm start          → Telegram bot + Web API (daemon, 24/7)
pnpm echos "query"  → one-shot, boots and exits
pnpm echos          → interactive REPL, boots and exits when done
```

### Interactive REPL Features

* Persistent readline history at `~/.echos_history` (survives between sessions, max 500 lines)
* Streaming responses with tool call indicators dimmed in grey
* `Ctrl+C` cancels the in-flight response and re-prompts (does not exit)
* `Ctrl+D` or typing `exit` / `quit` exits cleanly

### Example Session

```
$ pnpm echos
EchOS (Ctrl+C cancels response, Ctrl+D or "exit" to quit)

> What notes do I have about TypeScript?

[search_knowledge]
I found 3 notes about TypeScript:

1. "TypeScript Best Practices" (created 2026-02-10)
2. "Setting up TypeScript in Node.js" (created 2026-02-08)
3. "TypeScript vs JavaScript" (created 2026-01-15)
────────────────────────────────────────
> exit
```

### Output

* **TTY**: colored tool indicators, separator line after each response
* **Non-TTY** (pipe/redirect): plain text only, no ANSI codes — safe to pipe into other tools

### Logging

Startup logs are suppressed by default (log level `warn`). To see initialization details:

```bash theme={null}
LOG_LEVEL=info pnpm echos "query"
```

### VPS / SSH Workflow

```bash theme={null}
ssh user@vps
cd ~/echos
pnpm echos "what are my reminders for this week?"
```

### Exporting Notes

Use the `--output` / `-o` flag to save an exported file, or pipe single-note exports directly:

```bash theme={null}
# Single note — content goes to stdout (pipe-friendly)
echos "export my redis note" | pbcopy

# Single note to a file
echos "export my redis note as markdown" -o ~/Desktop/redis.md

# Multi-note zip
echos "export all my programming notes as zip" -o ~/Downloads/programming.zip

# Multi-note json
echos "export notes tagged 'recipe' as json" --output ~/Downloads/recipes.json
```

For inline (single-note markdown/text) exports, content is written to stdout or `--output` if specified. For file exports (zip/json), the file is moved to the `--output` path or saved to the current directory. The export destination path is printed to stderr so stdout remains clean for piping.

### Features

* ✅ Standalone — no daemon or Redis required
* ✅ Three auto-detected modes (one-shot, pipe, interactive)
* ✅ Streaming responses
* ✅ Persistent readline history
* ✅ Ctrl+C cancels mid-response without killing the process
* ✅ Plain output in pipe mode (scriptable)
* ✅ Safe concurrent access alongside running daemon (SQLite WAL)
* ✅ `--output` / `-o` flag for saving exported files

### Limitations

* Single user session (no multi-user support)
* No authentication (runs with local user permissions)
* Text-only (no images, voice)

***

## Choosing the Right Interface

| Feature              | Telegram              | Web API              | CLI (`pnpm echos`)    |
| -------------------- | --------------------- | -------------------- | --------------------- |
| **Status**           | ✅ Stable              | ⚠️ Experimental      | ✅ Stable              |
| **Requires daemon**  | ✅ Yes                 | ✅ Yes                | ❌ No                  |
| **Multi-user**       | ✅ Yes                 | ✅ Yes                | ❌ No                  |
| **Authentication**   | ✅ User IDs            | ✅ API key + user IDs | ❌ Local only          |
| **Network exposure** | Telegram servers      | localhost only       | None                  |
| **Streaming**        | ✅ Yes                 | ❌ No                 | ✅ Yes                 |
| **Inline actions**   | ✅ Keyboards           | ❌ No                 | ❌ No                  |
| **Mobile Access**    | ✅ Yes                 | ✅ Yes (localhost)    | ❌ No                  |
| **Voice Input**      | ✅ Yes                 | ❌ No                 | ❌ No                  |
| **Scriptable**       | ❌ No                  | ✅ Yes (curl)         | ✅ Yes (pipe)          |
| **SSH-friendly**     | ❌ No                  | ❌ No                 | ✅ Yes                 |
| **Export notes**     | ✅ Document attachment | ✅ Download link      | ✅ stdout / `--output` |
| **Best For**         | Daily use             | Local integration    | Terminal / SSH        |

**Recommendations:**

* **Primary use**: Telegram (most features, best UX, stable)
* **Local automation/integration**: Web API (requires `WEB_API_KEY`)
* **Terminal / SSH / scripting**: CLI (`pnpm echos`)

***

## Multiple Interfaces

You can run Telegram and Web simultaneously:

```bash theme={null}
# .env
ENABLE_TELEGRAM=true
ENABLE_WEB=true
```

The CLI (`pnpm echos`) is always available independently — no `.env` flag needed, just run it.

**Note**: Each interface maintains its own session state, so conversations don't carry across interfaces.

***

## Security Considerations

### Telegram

* ✅ Built-in authentication via `ALLOWED_USER_IDS`
* ✅ Rate limiting per user
* ✅ Input validation and sanitization
* ⚠️ Only one instance can run per bot token

### Web API

* ✅ Bearer token auth via `WEB_API_KEY` (set in `.env`)
* ✅ `userId` validated against `ALLOWED_USER_IDS` on every request
* ✅ Binds to `127.0.0.1` — not reachable from the network
* ✅ CORS restricted to `localhost` / `127.0.0.1` origins
* ⚠️ Experimental — not recommended as the primary interface
* ⚠️ If `WEB_API_KEY` is missing, the server **refuses to start** (exits with an error)

### CLI (`pnpm echos`)

* ⚠️ **No authentication** — runs with local user permissions
* ✅ No network exposure (stdin/stdout only)
* ✅ Safe for local/SSH use

***

## Quick Start Examples

### Test Web API

```bash theme={null}
# Start web only
pnpm start:web-only

# In another terminal, test it:
curl http://localhost:3000/health   # no auth required

# Load key from .env
export WEB_API_KEY=$(grep ^WEB_API_KEY .env | cut -d= -f2)

curl -X POST http://localhost:3000/api/chat \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $WEB_API_KEY" \
  -d '{"userId": 123, "message": "Hello!"}'
```

### Use the CLI

```bash theme={null}
# One-shot
pnpm echos "list my notes"

# Interactive REPL (no daemon needed)
pnpm echos
# > list my notes
# > exit

# Pipe
echo "summarise my recent notes" | pnpm echos
```

### Test Telegram + Web

```bash theme={null}
# Configure .env
ENABLE_TELEGRAM=true
ENABLE_WEB=true

# Start daemon
pnpm start

# Access:
# - Telegram: Message your bot
# - Web: curl http://localhost:3000/health
# - CLI: pnpm echos "query"  (no daemon config needed)
```

***

## 4. MCP Server

**Default**: Disabled (`ENABLE_MCP=false`)

The MCP (Model Context Protocol) server exposes your EchOS knowledge base to any MCP-compatible AI client — Claude Code, Cursor, Windsurf, and others. It runs as part of the daemon (not a separate process) and listens on a configurable port.

### Setup

Add to your `.env`:

```bash theme={null}
ENABLE_MCP=true
MCP_PORT=3939       # default

# Optional: protect with a bearer token (recommended for non-localhost)
MCP_API_KEY=$(openssl rand -hex 32)
```

Restart the daemon (`pnpm start`). You should see:

```
MCP server started (localhost only) port=3939
```

### Exposed Tools

| Tool               | Description                                     |
| ------------------ | ----------------------------------------------- |
| `search_knowledge` | Hybrid/semantic/keyword search across all notes |
| `create_note`      | Create a new note                               |
| `get_note`         | Retrieve a note by ID                           |
| `list_notes`       | List notes with optional filters                |
| `find_similar`     | Find semantically similar notes                 |
| `knowledge_stats`  | Knowledge base overview and statistics          |
| `recall_knowledge` | Recall personal memories by topic               |

### Exposed Resources

MCP resources let clients browse and read notes, tags, and categories without calling a tool. Each resource type uses a URI template:

| URI pattern                   | Description                                                   |
| ----------------------------- | ------------------------------------------------------------- |
| `notes://{noteId}`            | Individual note — full markdown content with YAML frontmatter |
| `tags://{tagName}`            | Notes with a given tag (URL-encoded)                          |
| `categories://{categoryName}` | Notes in a given category (URL-encoded)                       |

Listing resources (e.g. `resources/list`) returns all notes, tags, or categories. Reading a resource (e.g. `resources/read` with `notes://abc-123`) returns the full content as `text/markdown`.

### Connect from Claude Code

Add to your Claude Code configuration (`~/.claude.json` or project `.claude.json`):

```json theme={null}
{
  "mcpServers": {
    "echos": {
      "type": "http",
      "url": "http://localhost:3939/mcp",
      "headers": {
        "Authorization": "Bearer <your MCP_API_KEY>"
      }
    }
  }
}
```

If `MCP_API_KEY` is not set, omit the `headers` block.

### Security

* The MCP server only binds to `127.0.0.1` (localhost) — not accessible from other machines by default
* Set `MCP_API_KEY` to require bearer token authentication on all requests
* If `MCP_API_KEY` is unset, the server accepts all requests (convenient for localhost-only use)

***

## Troubleshooting

### Telegram: 409 Conflict Error

See [Troubleshooting — Telegram bot conflicts](/troubleshooting#telegram-bot-conflicts)

### Web API: Port Already in Use

```bash theme={null}
# Change port in .env
WEB_PORT=3001

# Or find and kill the process using port 3000
lsof -ti:3000 | xargs kill
```

### CLI: No output / empty response

* The model may have changed. Check `DEFAULT_MODEL` in `.env` — ensure it's not a deprecated model
* Run with `LOG_LEVEL=info pnpm echos "query"` to see startup and agent creation logs
* Ensure at least one LLM API key is set: `ANTHROPIC_API_KEY` (Anthropic) or `LLM_API_KEY` (other providers)

### CLI: Startup logs cluttering output

* By default the CLI runs at log level `warn` — you should only see agent responses
* If you're seeing logs, check if `LOG_LEVEL` is set in your shell environment

### Interface Not Starting

Check the startup logs:

```bash theme={null}
pnpm start | pnpm exec pino-pretty
```

Look for messages like:

* `Telegram bot started`
* `Web server started` (with port number)

***

## Next Steps

* **Telegram**: See examples in the [main README](../README.md)
* **Web API**: Build a frontend with React/Vue/etc.
* **CLI**: Terminal queries, SSH access, scripting
* **MCP Server**: Connect Claude Code, Cursor, and other AI agents to your knowledge base
* **Deploy**: See [Deployment](/deployment) for production setup
