Plugin Development Guide
This guide explains how to build a Ravel adapter for a new editor, IDE, or AI tool. The Claude Code adapter is the reference implementation.
Contents
- What Adapters Do
- What the CLI Provides
- The Graceful Degradation Pattern
- Shelling Out to the CLI
- Context Loading Integration
- Output Format Parsing
- Installing an Adapter
- Building a Minimal Adapter
- Adapter File Structure
What Adapters Do
An adapter integrates Ravel's global knowledge capabilities into a specific tool's plugin or extension system. The adapter's responsibilities are:
- Check availability — determine whether
ravelis installed before invoking it - Query on context load — run
ravel query --contextwhen the user begins a session, injecting results into the tool's context - Offer promotion pathways — when the user records a significant learning, offer to run
ravel promoteto promote it globally - Surface curation and exploration — provide user-facing commands that delegate to
ravel curateandravel explore - Degrade gracefully — if
ravelis not installed, continue working without it; no errors, no warnings to the user
What adapters do NOT do:
- Manage the knowledge store directly (that is the CLI's job)
- Implement their own knowledge format parsers
- Call the Ravel library (Rust crate) directly — only the CLI binary
All adapter-to-Ravel communication is through the CLI's stdout.
What the CLI Provides
The CLI is the sole interface between adapters and the knowledge store. Relevant commands for adapters:
| Command | Output | Use case |
|---|---|---|
ravel query [terms] [--context] [--format F] [--max-tokens N] |
Formatted knowledge | Context loading at session start |
ravel promote [--tags T] [--origin P] |
Interactive session | Global promotion after per-project promotion |
ravel status |
Summary text | Dashboard display or health check |
ravel curate |
Interactive session | User-initiated curation |
ravel explore |
Interactive session | User-initiated exploration |
The CLI writes results to stdout. Interactive sessions (promote, curate, explore) read from stdin and write prompts to stdout — they require a connected terminal.
The Graceful Degradation Pattern
Every adapter must implement graceful degradation: all Ravel-dependent features must be silently skipped if the CLI is not installed.
Availability Check
Check for the CLI binary before any invocation:
Shell (bash/zsh):
if command -v ravel > /dev/null 2>&1; then
# ravel is available
fi
In a Claude Code skill (Markdown):
Check whether `ravel` is installed:
```bash
which ravel
If found, run:
ravel query --context --format markdown
If the command fails or produces no output, skip silently.
**In a JavaScript/TypeScript plugin:**
```typescript
import { execSync } from 'child_process';
function isRavelAvailable(): boolean {
try {
execSync('which ravel', { stdio: 'pipe' });
return true;
} catch {
return false;
}
}
In a Python plugin:
import shutil
def is_ravel_available() -> bool:
return shutil.which('ravel') is not None
What "Graceful" Means
- Do not show error messages, warnings, or "Ravel not found" notices to the user
- Do not change the layout of your plugin's output based on Ravel's absence
- Simply omit the sections that Ravel would have populated
- Do not retry, poll, or cache the availability check for the session (re-check on each invocation — the user may install Ravel mid-session)
The rationale: users who have not installed Ravel should get a seamless experience with the per-project features, not constant reminders about a tool they may not want.
Shelling Out to the CLI
Working Directory
The CLI uses the working directory for context detection (query --context). Always invoke the CLI with the working directory set to the project root.
Shell:
cd /path/to/project && ravel query --context --format markdown
Node.js:
const result = execSync('ravel query --context --format markdown', {
cwd: projectRoot,
encoding: 'utf8'
});
Python:
import subprocess
result = subprocess.run(
['ravel', 'query', '--context', '--format', 'markdown'],
cwd=project_root,
capture_output=True,
text=True
)
Error Handling
CLI invocations may fail for various reasons (misconfigured store, empty knowledge base, parse errors). Handle failures by silently discarding the output and continuing without global knowledge:
function queryGlobalKnowledge(projectRoot: string): string | null {
if (!isRavelAvailable()) return null;
try {
const output = execSync(
'ravel query --context --format markdown',
{ cwd: projectRoot, encoding: 'utf8', timeout: 5000 }
);
return output.trim() || null;
} catch {
return null; // silently degrade
}
}
Timeout
Apply a reasonable timeout (5 seconds is a good default) to prevent CLI invocations from blocking the editor or tool. The CLI scans the filesystem during context detection and may be slow on very large projects or network-mounted filesystems.
Context Loading Integration
Context loading is the primary use case for adapters: injecting relevant global knowledge into the LLM's context window at the start of a work session.
When to Load Context
Load global knowledge:
- When the user begins a session or opens a project (
/begin-workequivalent) - When the user explicitly requests it
- Not on every message (too expensive in tokens and latency)
Token Budget Management
Use --max-tokens to keep the injection size within the LLM's context window budget:
ravel query --context --format markdown --max-tokens 4000
The CLI approximates 500 tokens per result and limits accordingly. Adjust the budget based on:
- The LLM's context window size
- How much other context you're injecting (per-project knowledge, conversation history)
- The user's preferences
For a 32K context window with significant per-project knowledge, --max-tokens 4000 is a reasonable starting point.
Presenting the Results
When injecting into an LLM context, wrap the output in a clear section heading so the model understands its provenance:
### Global knowledge loaded
<ravel query output here>
If the query returns no results or fails, omit the section entirely.
Output Format Parsing
Markdown
The default format. Use it when injecting into LLM context. Each result is an H2 section:
## <title> (<confidence>)
Tags: <comma-separated tags>
<body>
---
No parsing required — inject directly into context.
JSON
Use JSON when you need to process results programmatically (display in a UI, filter, deduplicate against per-project knowledge):
[
{
"title": "Rust Async Channel Patterns",
"tags": ["rust", "async", "tokio", "channels"],
"confidence": "high",
"body": "## Bounded channels prevent backpressure\n\n..."
}
]
Parse with any JSON library. The body field contains the full Markdown body of the entry.
Plain
One line per result. Use for quick scanning in terminal contexts:
title: Rust Async Channel Patterns | confidence: high | tags: rust, async, tokio, channels
Installing an Adapter
When a user runs ravel install <adapter-name>, the CLI copies the adapter's files from the repository's adapters/<adapter-name>/ directory to the tool-specific location.
Registering Your Adapter
To register a new adapter with the CLI's install command, add a match arm in src/main.rs:
"your-tool" => {
let plugin_target = dirs::home_dir()
.expect("Could not determine home directory")
.join(".your-tool/plugins/ravel");
let exe_dir = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()));
let source_candidates = [
exe_dir.as_ref().map(|d| d.join("../adapters/your-tool")),
Some(std::path::PathBuf::from("adapters/your-tool")),
];
let source = source_candidates
.iter()
.flatten()
.find(|p| p.exists())
.ok_or_else(|| anyhow::anyhow!(
"Could not find adapter files. Run from the Ravel repo directory."
))?;
commands::install::run_install_claude_code(source, &plugin_target)?;
println!("✓ Ravel adapter installed to {}", plugin_target.display());
}
The run_install_claude_code function performs a recursive directory copy; it works for any adapter.
Building a Minimal Adapter
Here is a complete example of a minimal adapter for a hypothetical tool that supports Markdown-based skills.
Skill: ravel-context.md
This skill queries global knowledge and injects it into the session:
---
name: ravel-context
description: Load relevant global knowledge from Ravel. Usage: /ravel-context
---
Load global knowledge from Ravel if available.
## Check availability
```bash
which ravel
If ravel is not installed, skip this skill entirely — do not report any errors.
Query global knowledge
Run in the current project directory:
ravel query --context --format markdown --max-tokens 4000
Present results
If the command succeeds and produces output, display it under the heading: