Plugin Examples
Practical examples of Alma plugins demonstrating various features and patterns.
Example 1: Token Counter (Status Bar Plugin)
A plugin that displays real-time token usage and cost in the status bar.
Features
- Tracks tokens per-thread
- Calculates cost using real pricing data
- Updates in real-time as messages are sent/received
- Shows detailed breakdown on hover
Code
typescript
import type { PluginContext, PluginActivation } from 'alma-plugin-api';
interface Pricing {
input?: number;
output?: number;
cacheRead?: number;
}
interface ThreadStats {
promptTokens: number;
completionTokens: number;
cachedTokens: number;
totalTokens: number;
totalCost: number;
pricing?: Pricing;
}
export async function activate(context: PluginContext): Promise<PluginActivation> {
const { logger, events, ui, settings } = context;
logger.info('Token Counter plugin activated!');
// Create status bar item
const statusBarItem = ui.createStatusBarItem({
id: 'token-counter',
alignment: 'right',
priority: 100,
});
// Track stats per thread
const threadStats = new Map<string, ThreadStats>();
let currentThreadId: string | null = null;
// Calculate cost
const calculateCost = (stats: ThreadStats): number => {
const { pricing } = stats;
if (!pricing) {
// Fallback: $3 per million tokens
return (stats.totalTokens / 1_000_000) * 3;
}
const inputCost = (stats.promptTokens / 1_000_000) * (pricing.input ?? 0);
const outputCost = (stats.completionTokens / 1_000_000) * (pricing.output ?? 0);
const cacheCost = (stats.cachedTokens / 1_000_000) * (pricing.cacheRead ?? 0);
return inputCost + outputCost + cacheCost;
};
// Update display
const updateStatusBar = () => {
const stats = currentThreadId ? threadStats.get(currentThreadId) : null;
if (!stats || stats.totalTokens === 0) {
statusBarItem.text = 'Tokens: 0';
statusBarItem.tooltip = 'No tokens used yet';
return;
}
const cost = calculateCost(stats);
statusBarItem.text = `Tokens: ${stats.totalTokens.toLocaleString()} ($${cost.toFixed(4)})`;
statusBarItem.tooltip = [
`Input: ${stats.promptTokens.toLocaleString()}`,
`Output: ${stats.completionTokens.toLocaleString()}`,
stats.cachedTokens > 0 ? `Cached: ${stats.cachedTokens.toLocaleString()}` : '',
`Total: ${stats.totalTokens.toLocaleString()}`,
'',
`Cost: $${cost.toFixed(4)}`,
].filter(Boolean).join('\n');
};
// Track message usage
const messageDisposable = events.on('chat.message.didReceive', (input) => {
const { threadId, pricing, response } = input;
currentThreadId = threadId;
if (response.usage) {
const existing = threadStats.get(threadId) || {
promptTokens: 0,
completionTokens: 0,
cachedTokens: 0,
totalTokens: 0,
totalCost: 0,
};
const updated: ThreadStats = {
promptTokens: existing.promptTokens + response.usage.promptTokens,
completionTokens: existing.completionTokens + response.usage.completionTokens,
cachedTokens: existing.cachedTokens + (response.usage.cachedInputTokens ?? 0),
totalTokens: existing.totalTokens + response.usage.totalTokens,
totalCost: 0,
pricing: pricing || existing.pricing,
};
updated.totalCost = calculateCost(updated);
threadStats.set(threadId, updated);
updateStatusBar();
}
});
// Handle thread switching
const threadDisposable = events.on('thread.activated', (input) => {
const { threadId, usage, pricing } = input;
currentThreadId = threadId;
if (usage && !threadStats.has(threadId)) {
const stats: ThreadStats = {
promptTokens: usage.promptTokens,
completionTokens: usage.completionTokens,
cachedTokens: usage.cachedInputTokens ?? 0,
totalTokens: usage.totalTokens,
totalCost: 0,
pricing,
};
stats.totalCost = calculateCost(stats);
threadStats.set(threadId, stats);
}
updateStatusBar();
});
// Initialize
updateStatusBar();
statusBarItem.show();
return {
dispose: () => {
messageDisposable.dispose();
threadDisposable.dispose();
statusBarItem.dispose();
},
};
}Example 2: Hello World Tool
A simple tool plugin that the AI can use to greet users.
Features
- Registers a tool that the AI can invoke
- Supports multiple languages
- Demonstrates Zod schema for parameters
Code
typescript
import { z } from 'zod';
import type { PluginContext, PluginActivation } from 'alma-plugin-api';
const greetings: Record<string, string> = {
en: 'Hello',
zh: '你好',
ja: 'こんにちは',
es: 'Hola',
fr: 'Bonjour',
};
export async function activate(context: PluginContext): Promise<PluginActivation> {
const { logger, tools, commands, ui } = context;
logger.info('Hello World plugin activated!');
// Register a tool
const toolDisposable = tools.register('greet', {
description: 'Greet someone in their preferred language',
parameters: z.object({
name: z.string().describe('Name of the person to greet'),
language: z.enum(['en', 'zh', 'ja', 'es', 'fr'])
.default('en')
.describe('Language for the greeting'),
}),
execute: async (params) => {
const greeting = greetings[params.language] || greetings.en;
const message = `${greeting}, ${params.name}!`;
logger.info(`Greeting: ${message}`);
return {
success: true,
message,
language: params.language,
};
},
});
// Register a command to test the greeting
const commandDisposable = commands.register('helloWorld.greet', async () => {
const name = await ui.showInputBox({
title: 'Greeting',
prompt: 'Enter your name',
placeholder: 'Name',
});
if (name) {
const language = await ui.showQuickPick([
{ label: 'English', value: 'en' },
{ label: '中文', value: 'zh' },
{ label: '日本語', value: 'ja' },
{ label: 'Español', value: 'es' },
{ label: 'Français', value: 'fr' },
], { title: 'Select Language' });
if (language) {
const greeting = greetings[language];
ui.showNotification(`${greeting}, ${name}!`, { type: 'success' });
}
}
});
return {
dispose: () => {
logger.info('Hello World plugin deactivated');
toolDisposable.dispose();
commandDisposable.dispose();
},
};
}Example 3: Prompt Enhancer
A transform plugin that enhances user prompts with additional context.
Features
- Intercepts messages before sending
- Adds system context and instructions
- Configurable enhancement levels
- Toggle command for quick enable/disable
Code
typescript
import type { PluginContext, PluginActivation } from 'alma-plugin-api';
type EnhancementMode = 'minimal' | 'standard' | 'detailed';
const enhancements: Record<EnhancementMode, string> = {
minimal: 'Be concise.',
standard: 'Be helpful and concise. Provide examples when useful.',
detailed: `Be thorough and educational. Include:
- Step-by-step explanations
- Code examples with comments
- Best practices and potential pitfalls
- Links to relevant documentation`,
};
export async function activate(context: PluginContext): Promise<PluginActivation> {
const { logger, events, commands, settings, ui } = context;
logger.info('Prompt Enhancer plugin activated!');
// Get settings
const getEnabled = () => settings.get<boolean>('promptEnhancer.enabled', true);
const getMode = () => settings.get<EnhancementMode>('promptEnhancer.mode', 'standard');
const getCustomInstructions = () => settings.get<string>('promptEnhancer.customInstructions', '');
// Transform messages
const eventDisposable = events.on('chat.message.willSend', (input, output) => {
if (!getEnabled()) return;
const mode = getMode();
const enhancement = enhancements[mode];
const custom = getCustomInstructions();
// Build enhanced prompt
const parts = [input.content];
if (enhancement) {
parts.push(`\n\n[Instructions: ${enhancement}]`);
}
if (custom) {
parts.push(`\n\n[Additional context: ${custom}]`);
}
output.content = parts.join('');
logger.debug(`Enhanced prompt with ${mode} mode`);
});
// Toggle command
const toggleDisposable = commands.register('promptEnhancer.toggle', async () => {
const current = getEnabled();
await settings.update('promptEnhancer.enabled', !current);
ui.showNotification(
`Prompt Enhancer ${!current ? 'enabled' : 'disabled'}`,
{ type: 'info' }
);
});
// Mode selection command
const modeDisposable = commands.register('promptEnhancer.selectMode', async () => {
const mode = await ui.showQuickPick([
{ label: 'Minimal', description: 'Brief responses', value: 'minimal' as EnhancementMode },
{ label: 'Standard', description: 'Balanced (default)', value: 'standard' as EnhancementMode },
{ label: 'Detailed', description: 'Thorough explanations', value: 'detailed' as EnhancementMode },
], { title: 'Select Enhancement Mode' });
if (mode) {
await settings.update('promptEnhancer.mode', mode);
ui.showNotification(`Enhancement mode: ${mode}`, { type: 'success' });
}
});
return {
dispose: () => {
logger.info('Prompt Enhancer plugin deactivated');
eventDisposable.dispose();
toggleDisposable.dispose();
modeDisposable.dispose();
},
};
}Example 4: Tool Monitor
A plugin that tracks and logs all tool executions.
Features
- Logs all tool calls with arguments
- Tracks execution time and success/failure
- Shows statistics in status bar
- Provides error recovery for specific tools
Code
typescript
import type { PluginContext, PluginActivation } from 'alma-plugin-api';
interface ToolStats {
calls: number;
successes: number;
failures: number;
totalTime: number;
}
export async function activate(context: PluginContext): Promise<PluginActivation> {
const { logger, events, ui, commands } = context;
logger.info('Tool Monitor plugin activated!');
// Statistics
const stats = new Map<string, ToolStats>();
// Status bar
const statusItem = ui.createStatusBarItem({
id: 'tool-monitor',
alignment: 'right',
priority: 50,
});
const updateStatusBar = () => {
let totalCalls = 0;
let totalFailures = 0;
for (const s of stats.values()) {
totalCalls += s.calls;
totalFailures += s.failures;
}
statusItem.text = `Tools: ${totalCalls}`;
statusItem.tooltip = `${totalCalls} calls, ${totalFailures} failures\nClick for details`;
};
// Track tool execution start
const willExecuteDisposable = events.on('tool.willExecute', (input) => {
logger.info(`[Tool] Executing: ${input.tool}`, {
args: input.args,
thread: input.context.threadId,
});
});
// Track tool execution completion
const didExecuteDisposable = events.on('tool.didExecute', (input) => {
const toolStats = stats.get(input.tool) || {
calls: 0,
successes: 0,
failures: 0,
totalTime: 0,
};
toolStats.calls++;
toolStats.successes++;
toolStats.totalTime += input.duration;
stats.set(input.tool, toolStats);
logger.info(`[Tool] Completed: ${input.tool} (${input.duration.toFixed(0)}ms)`);
updateStatusBar();
});
// Track tool errors
const onErrorDisposable = events.on('tool.onError', (input, output) => {
const toolStats = stats.get(input.tool) || {
calls: 0,
successes: 0,
failures: 0,
totalTime: 0,
};
toolStats.calls++;
toolStats.failures++;
toolStats.totalTime += input.duration;
stats.set(input.tool, toolStats);
logger.error(`[Tool] Failed: ${input.tool}`, {
error: input.error.message,
duration: input.duration,
});
// Provide fallback for network tools
if (input.tool === 'fetchUrl' || input.tool === 'webSearch') {
output.rethrow = false;
output.result = {
error: true,
message: `Tool failed: ${input.error.message}`,
suggestion: 'Please try again or check your network connection.',
};
}
updateStatusBar();
});
// Command to show detailed stats
const commandDisposable = commands.register('toolMonitor.showStats', () => {
const lines = ['Tool Execution Statistics:', ''];
for (const [tool, s] of stats.entries()) {
const avgTime = s.calls > 0 ? s.totalTime / s.calls : 0;
const successRate = s.calls > 0 ? (s.successes / s.calls * 100).toFixed(1) : '0.0';
lines.push(`${tool}:`);
lines.push(` Calls: ${s.calls}`);
lines.push(` Success Rate: ${successRate}%`);
lines.push(` Avg Time: ${avgTime.toFixed(0)}ms`);
lines.push('');
}
if (stats.size === 0) {
lines.push('No tools have been executed yet.');
}
ui.showNotification(lines.join('\n'), { duration: 0 });
});
statusItem.command = 'toolMonitor.showStats';
updateStatusBar();
statusItem.show();
return {
dispose: () => {
logger.info('Tool Monitor plugin deactivated');
willExecuteDisposable.dispose();
didExecuteDisposable.dispose();
onErrorDisposable.dispose();
commandDisposable.dispose();
statusItem.dispose();
},
};
}Example 5: Auto-Save Settings
A plugin that persists user preferences across sessions.
Features
- Saves settings to local storage
- Syncs across app restarts
- Provides import/export functionality
Code
typescript
import type { PluginContext, PluginActivation } from 'alma-plugin-api';
interface UserPreferences {
theme: string;
fontSize: number;
autoSave: boolean;
lastModel: string;
}
const defaultPreferences: UserPreferences = {
theme: 'system',
fontSize: 14,
autoSave: true,
lastModel: '',
};
export async function activate(context: PluginContext): Promise<PluginActivation> {
const { logger, storage, settings, commands, ui, events } = context;
logger.info('Auto-Save Settings plugin activated!');
// Load preferences
const loadPreferences = async (): Promise<UserPreferences> => {
const saved = await storage.local.get<UserPreferences>('preferences');
return { ...defaultPreferences, ...saved };
};
// Save preferences
const savePreferences = async (prefs: Partial<UserPreferences>) => {
const current = await loadPreferences();
const updated = { ...current, ...prefs };
await storage.local.set('preferences', updated);
logger.debug('Preferences saved:', updated);
};
// Track model usage
const messageDisposable = events.on('chat.message.didReceive', async (input) => {
await savePreferences({ lastModel: input.model });
});
// Track settings changes
const settingsDisposable = settings.onDidChange(async (event) => {
if (event.key.startsWith('appearance.')) {
const prefs: Partial<UserPreferences> = {};
if (event.key === 'appearance.theme') {
prefs.theme = event.newValue as string;
}
if (event.key === 'appearance.fontSize') {
prefs.fontSize = event.newValue as number;
}
await savePreferences(prefs);
}
});
// Export command
const exportDisposable = commands.register('autoSave.export', async () => {
const prefs = await loadPreferences();
const json = JSON.stringify(prefs, null, 2);
// In a real plugin, you'd save to a file
logger.info('Exported preferences:', json);
ui.showNotification('Preferences exported to clipboard', { type: 'success' });
});
// Import command
const importDisposable = commands.register('autoSave.import', async () => {
const json = await ui.showInputBox({
title: 'Import Preferences',
prompt: 'Paste your preferences JSON',
validateInput: (value) => {
try {
JSON.parse(value);
return undefined;
} catch {
return 'Invalid JSON';
}
},
});
if (json) {
try {
const prefs = JSON.parse(json) as Partial<UserPreferences>;
await savePreferences(prefs);
ui.showNotification('Preferences imported!', { type: 'success' });
} catch (error) {
ui.showError('Failed to import preferences');
}
}
});
// Reset command
const resetDisposable = commands.register('autoSave.reset', async () => {
const confirmed = await ui.showConfirmDialog(
'Reset all preferences to defaults?',
{ type: 'warning' }
);
if (confirmed) {
await storage.local.set('preferences', defaultPreferences);
ui.showNotification('Preferences reset to defaults', { type: 'success' });
}
});
// Restore preferences on startup
events.once('app.ready', async () => {
const prefs = await loadPreferences();
logger.info('Restored preferences:', prefs);
if (prefs.lastModel) {
logger.info(`Last used model: ${prefs.lastModel}`);
}
});
return {
dispose: () => {
logger.info('Auto-Save Settings plugin deactivated');
messageDisposable.dispose();
settingsDisposable.dispose();
exportDisposable.dispose();
importDisposable.dispose();
resetDisposable.dispose();
},
};
}Manifest Examples
Tool Plugin
json
{
"id": "hello-world",
"name": "Hello World",
"version": "1.0.0",
"description": "A greeting tool for the AI",
"author": { "name": "Your Name" },
"main": "main.js",
"engines": { "alma": "^0.1.0" },
"type": "tool",
"permissions": ["tools:register"],
"activationEvents": ["onStartup"],
"contributes": {
"tools": [
{
"id": "greet",
"description": "Greet someone in their language"
}
]
}
}UI Plugin
json
{
"id": "token-counter",
"name": "Token Counter",
"version": "1.0.0",
"description": "Display token usage in status bar",
"author": { "name": "Your Name" },
"main": "main.js",
"engines": { "alma": "^0.1.0" },
"type": "ui",
"activationEvents": ["onStartup"],
"contributes": {
"configuration": {
"title": "Token Counter",
"properties": {
"tokenCounter.showCost": {
"type": "boolean",
"default": true,
"description": "Show estimated cost"
}
}
}
}
}Transform Plugin
json
{
"id": "prompt-enhancer",
"name": "Prompt Enhancer",
"version": "1.0.0",
"description": "Enhance prompts with context",
"author": { "name": "Your Name" },
"main": "main.js",
"engines": { "alma": "^0.1.0" },
"type": "transform",
"activationEvents": ["onStartup"],
"contributes": {
"commands": [
{
"id": "promptEnhancer.toggle",
"title": "Toggle Prompt Enhancer"
}
],
"configuration": {
"title": "Prompt Enhancer",
"properties": {
"promptEnhancer.enabled": {
"type": "boolean",
"default": true
},
"promptEnhancer.mode": {
"type": "string",
"enum": ["minimal", "standard", "detailed"],
"default": "standard"
}
}
}
}
}