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:
The application will start all enabled interfaces simultaneously.
1. Telegram Bot Interface
Default: Enabled
Setup
- Create a bot with @BotFather on Telegram
- Get your bot token
- Get your Telegram user ID from @userinfobot
- Configure in
.env:
TELEGRAM_BOT_TOKEN=your_token_here
ALLOWED_USER_IDS=123456789,987654321
Usage
- Start EchOS:
pnpm start
- Open Telegram and message your bot
- 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:
- The agent finishes its current tool call
- Remaining tool calls in the turn are skipped
- 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:
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
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:
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:
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
| 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 |
| 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:
# .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