Skip to main content

Interface Guide

EchOS supports three 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:
# Enable/disable interfaces
ENABLE_TELEGRAM=true   # Telegram bot (stable, recommended)
ENABLE_WEB=false       # Web API server (experimental, 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
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.
Start the application:
pnpm start
The application will start all enabled interfaces simultaneously.

1. Telegram Bot Interface

Default: Enabled

Setup

  1. Create a bot with @BotFather on Telegram
  2. Get your bot token
  3. Get your Telegram user ID from @userinfobot
  4. Configure in .env:
    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

CommandDescription
/startWelcome message
/resetClear conversation history and start fresh
/usageShow 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.

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

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:
ENABLE_WEB=true
WEB_PORT=3000
WEB_API_KEY=your_secret_key_here   # required — server refuses to start without it
Generate a key:
openssl rand -hex 32
Or run pnpm wizard — it generates and stores the key automatically.
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.

Usage

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

Endpoints

Health Check

GET /health
Response:
{
  "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

POST /api/chat
Content-Type: application/json
Authorization: Bearer <WEB_API_KEY>

{
  "userId": 123,
  "message": "What notes do I have?"
}
Response:
{
  "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.
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

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.
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

POST /api/chat/reset
Content-Type: application/json
Authorization: Bearer <WEB_API_KEY>

{
  "userId": 123
}
Response:
{
  "ok": true
}

Examples

Create a note:
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:
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:
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):
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:
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:
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:
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:
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:
LOG_LEVEL=info pnpm echos "query"

VPS / SSH Workflow

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:
# 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)
  • Plugin tools (YouTube, article) not loaded — core knowledge tools only

Choosing the Right Interface

FeatureTelegramWeb APICLI (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 exposureTelegram serverslocalhost onlyNone
Streaming✅ Yes❌ No✅ Yes
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 ForDaily useLocal integrationTerminal / 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:
# .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

# 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

# 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

# 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)

Troubleshooting

Telegram: 409 Conflict Error

See Troubleshooting — Telegram bot conflicts

Web API: Port Already in Use

# 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 ANTHROPIC_API_KEY is set in .env

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:
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
  • Web API: Build a frontend with React/Vue/etc.
  • CLI: Terminal queries, SSH access, scripting
  • Deploy: See Deployment for production setup