Skip to main content

Creating a Plugin

Plugins add content processors and agent tools to EchOS without modifying core code. Each plugin is a separate workspace package in plugins/.

Steps

1

Scaffold the package

Create plugins/my-plugin/package.json:
Create plugins/my-plugin/tsconfig.json:
2

Write the processor

Create plugins/my-plugin/src/processor.ts — the logic that fetches/transforms external content:
Security rules (non-negotiable):
  • Always use validateUrl() before fetching any URL
  • Always use sanitizeHtml() on external content
  • Never use eval(), Function(), or vm
  • Never log secrets
3

Create the agent tool

Create plugins/my-plugin/src/tool.ts — defines the tool the LLM agent can call:
New in this example: AI-powered auto-categorization support using the categorizeContent function from @echos/core. When autoCategorize=true, the plugin will automatically extract category, tags, and optionally gist/summary/key points from the content.
4

Export the plugin

Create plugins/my-plugin/src/index.ts:
5

Auto-discovery

Plugins are auto-discovered at runtime — src/plugin-loader.ts scans the plugins/ directory and dynamically imports any @echos/plugin-<dirname> package. No manual import or registration in src/index.ts is needed.
6

Wire up the workspace

Add the path mapping to root tsconfig.json:
Install dependencies:
Build and verify:

PluginContext API

Every plugin receives a PluginContext with:

AI Categorization

Plugins can use the built-in categorization service from @echos/core:
The categorization service:
  • Uses streamSimple + parseStreamingJson to stream the LLM response progressively
  • Fires onProgress as fields resolve: category → tags → gist (full mode)
  • Handles errors with safe defaults (fallback to ‘uncategorized’)
  • Respects content length limits (5000 chars for lightweight, 10000 for full)
  • Is safe to call without onProgress — callers that don’t need streaming omit the last argument
See Categorization for detailed documentation.

Existing plugins

Twitter Plugin

The Twitter plugin (@echos/plugin-twitter) provides the save_tweet tool for saving tweets and threads from Twitter/X. Features:
  • Save individual tweets with full metadata (text, author, engagement stats, media URLs)
  • Automatic thread unrolling — reply chains by the same author are merged into a clean article
  • Quote tweet extraction
  • Media URLs referenced as markdown links (images and videos)
  • Optional AI categorization for automatic tagging
  • No API key required — uses the free FxTwitter API (api.fxtwitter.com)
Supported URL formats:
  • twitter.com/<user>/status/<id>
  • x.com/<user>/status/<id>
  • mobile.twitter.com/<user>/status/<id>
  • fxtwitter.com/<user>/status/<id>
  • vxtwitter.com/<user>/status/<id>
  • All formats support query parameters (?s=20, ?t=...)
Tool: save_tweet
Thread unrolling:
  • Walks up the reply chain via replying_to_status to find earlier tweets by the same author
  • Stops when a different author is reached or the chain exceeds 25 tweets
  • Merges thread tweets into a clean article format, stripping self-reply @mentions
  • Single tweets (no thread) are saved in blockquote format with engagement stats

Image Plugin

The image plugin (@echos/plugin-image) provides the save_image tool for storing and organizing images in the knowledge base. Features:
  • Download images from URLs or accept base64 data
  • Extract metadata: dimensions, format, file size, EXIF
  • Store original files in knowledge/image/{category}/
  • Create searchable markdown notes with image references
  • Optional AI categorization for automatic tagging
Tool: save_image
Supported formats:
  • JPEG, PNG, GIF, WebP, AVIF, TIFF, BMP
  • Maximum size: 20MB
Processor (processImage):
  • Validates image format and size
  • Extracts metadata using Sharp library
  • Generates content-based filename hash
  • Returns structured metadata and buffer
Storage:
  • Original file: knowledge/image/{category}/{hash}.{ext}
  • Markdown note: knowledge/note/{category}/{date}-{slug}.md
  • Embedded reference: ![title](../../image/{category}/{filename})
Telegram Integration:
  • Automatic photo handler via bot.on('message:photo')
  • Downloads from Telegram API
  • Passes URL to save_image tool
  • Supports captions for context
See Images for complete documentation on image handling.

Resurface Plugin

The resurface plugin (@echos/plugin-resurface) brings forgotten knowledge back to the surface through spaced repetition and serendipitous discovery. It provides a get_resurfaced agent tool and a daily scheduled job that broadcasts notes via Telegram. Features:
  • Three resurfacing strategies:
    • forgotten — notes you haven’t seen in 7+ days, oldest first (classic spaced repetition)
    • on_this_day — notes created on the same calendar date in a prior year
    • mix (default) — blend of both for maximum serendipity
    • random — random sampling of un-recently-surfaced notes (supported by both the get_resurfaced tool and scheduler config)
  • Tracks a last_surfaced timestamp per note in SQLite — never resurfaces the same note twice within 7 days
  • Daily broadcast job sends 2–3 notes to Telegram with emoji labels (🔮 Resurfaced, 📅 On this day, 🎲 Discovery)
  • On-demand access via the get_resurfaced tool
Tool: get_resurfaced
Trigger phrases the agent recognises:
  • “surprise me”
  • “what did I save before?”
  • “on this day”
  • “rediscover something”
  • “show me something old”
  • “random note”
Setting up the daily job: Tell the agent:
“Schedule a daily knowledge resurfacing at 9am.”
The agent will create a resurface schedule with cron 0 9 * * *. You can customize it:
Scheduler config options:

PDF Plugin

The PDF plugin (@echos/plugin-pdf) provides the save_pdf tool for extracting and saving text from PDF documents. Features:
  • Download PDFs from public http(s) URLs and extract text via pdf-parse (pure JS, no native deps)
  • Preserves page count in the note header/body; stores author and source URL in frontmatter metadata (when available)
  • Enforces a 10 MiB PDF download size limit; larger binaries are rejected with a clear error
  • Truncates oversized content gracefully (max 500 000 chars), appending [content truncated due to size limit] and marking the “Extracted characters” field as (truncated)
  • Fails clearly on password-protected or corrupt PDFs
  • Optional AI categorization for automatic tagging
  • URL validation via validateUrl() (SSRF protection: only public http(s) URLs; private/localhost/internal hosts are blocked)
Tool: save_pdf
Metadata in saved note:
  • **Source:** — source URL
  • **Pages:** — page count
  • **Extracted characters:** — character count (with truncation notice if applicable)
  • **Author:** — if present in PDF metadata

Audio / Podcast Plugin

The audio plugin (@echos/plugin-audio) provides the save_audio tool for transcribing podcast episodes and audio files via OpenAI Whisper and saving the transcript as a searchable knowledge note. Requires OPENAI_API_KEY — Whisper is an OpenAI API service. The plugin fails gracefully with a clear message if the key is absent. Features:
  • Downloads audio from public http(s) URLs and transcribes via whisper-1
  • Supports: .mp3, .wav, .m4a, .ogg, .webm, .mp4, .flac
  • Files > 25 MB are split into 24 MB byte-range chunks and transcribed sequentially — no ffmpeg required
  • Probes file size with a HEAD request, then falls back to a Range: bytes=0-0 probe if Content-Length is absent
  • Streams downloads with a hard 25 MB cap to prevent unbounded memory usage
  • Saves notes with inputSource: 'voice' and type: 'note'
  • Optional AI categorization via Anthropic
  • Respects WHISPER_LANGUAGE config for language hints
Tool: save_audio
Metadata in saved note:
  • **Source:** — source URL
  • **Format:** — file extension (e.g. MP3)
  • **File size:** — human-readable size (KB/MB)
  • **Duration estimate:** — estimated from file size and format bitrate
  • **Transcript length:** — character count
Chunking for large files: Files exceeding the Whisper 25 MB limit are automatically chunked:
  1. A HEAD request probes Content-Length
  2. If absent, a Range: bytes=0-0 request reads the Content-Range total size
  3. If the total exceeds 25 MB, the file is fetched in 24 MB byte-range slices
  4. Each slice is transcribed separately; results are joined with \n\n
  5. If the server does not honour Range headers (returns 200 instead of 206), an informative error is returned
Example prompts:
  • “Save this podcast episode: [URL]”
  • “Transcribe and save this interview recording”
  • “Save the audio from this conference talk: [URL]“

Journal Plugin

The journal plugin (@echos/plugin-journal) provides a dedicated journaling experience with two agent tools and an optional daily prompt job. Features:
  • Dedicated journal tool for creating journal/diary entries (replaces create_note(type="journal"))
  • AI-powered reflect tool that synthesizes journal entries over a time period
  • Optional journal_prompt scheduled job for daily journaling nudges via Telegram
Tool: journal
All journal entries are stored with type: 'journal' and status: 'read'. After creating, the agent always calls categorize_note for automatic tagging. Tool: reflect
The reflect tool:
  • Fetches journal entries within the date range (up to 50 entries)
  • Spawns a sub-agent to synthesize patterns, mood trends, key themes, and insights
  • Returns a warm, structured reflection with actionable suggestions
  • Validates date ranges (max 365 days lookback)
Trigger phrases the agent recognises:
  • “reflect on my journal”
  • “weekly journal review”
  • “how has my week been?”
  • “mood summary”
  • “look back at my journaling”
Setting up the daily prompt: Tell the agent:
“Schedule a daily journal prompt at 9pm.”
The agent will create a journal_prompt schedule with cron 0 21 * * *. Scheduler config options:

RSS Feed Plugin

The RSS plugin (@echos/plugin-rss) provides the manage_feeds tool for subscribing to RSS and Atom feeds, with automatic background polling every 4 hours. Features:
  • Subscribe to any RSS 2.0 or Atom feed via URL
  • Automatic deduplication — each article is saved at most once, even if a manual refresh and a scheduled poll run concurrently (atomic guid claim before processing)
  • Full article extraction via @echos/plugin-article (Readability) — not just the feed summary
  • AI categorization applied automatically when ANTHROPIC_API_KEY or LLM_API_KEY is configured
  • Per-feed tags: all articles from a feed inherit its configured tags plus the rss tag
  • Background polling every 4 hours via a self-registering BullMQ schedule (rss-poll)
  • Plugin-specific SQLite database at {DB_PATH}/rss.db — separate from echos.db
Tool: manage_feeds
Actions: Example prompts: Automatic polling: On first startup, the plugin registers a default schedule (rss-poll) with cron 0 */4 * * * (every 4 hours). The schedule is stored in SQLite and picked up by the ScheduleManager — no manual setup required. To change the poll frequency, update the schedule via the agent:
“Change the RSS poll schedule to run every 2 hours.”
Storage:
  • Each feed entry is stored as a note with type: article, inputSource: url, and sourceUrl pointing to the original article
  • Feed-specific data (subscriptions, seen guids) lives in {DB_PATH}/rss.db and is managed entirely by the plugin
  • Deleting a feed subscription cascades to its entry records in rss.db — no orphan rows