Skip to content

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"
                }
            }
        }
    }
}