npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

llmail

v1.2.11

Published

Inbox and issue managementfor LLMs

Readme

llmail

An intent-based inbox and issue management system for LLM collaboration.

Core Concepts

  • Intent-Based: Metadata is the source of truth. The system helps align file locations with metadata instead of enforcing rigid rules.
  • Flexible Organization: Works with any combination of states and types through simple configuration.
  • Simple Directory Structure:
    • Active items live in state directories (issues/_state/)
    • Inactive items live in type directories (issues/type/)
    • New items start in the inbox (inbox/)
  • Extensibility: llmail provides a simple base you can use to build much more powerful systems.

Directory Structure

/
├── inbox/                    # New items needing triage
└── issues/                  # Root for all organized items
    ├── _working/           # Active items in 'working' state
    │   └── working-bug-a41d.md
    ├── _blocked/           # Active items in 'blocked' state
    │   └── blocked-feat-789.md
    ├── bug/                # Inactive bugs
    │   └── fixed-bug-456.md
    └── feat/               # Inactive features
        └── wontfix-feat-123.md

Configuration (llmail.yaml)

# Define available types and states
types_list:
  - bug
  - feat
  - spec

active_states:
  - working  # Creates _working folder
  - blocked  # Creates _blocked folder

inactive_reasons:
  - fixed
  - wontfix
  - duplicate

# Plugin configuration
plugins:
  # Format 1: Single plugin without config
  plugins: llmail-testlink

  # Format 2: Multiple plugins without config
  plugins: [llmail-testlink, llmail-github]

  # Format 3: List format with optional config
  plugins:
    - llmail-testlink  # Without config
    - name: llmail-github  # With config
      config:
        owner: 'myorg'
        repo: 'myrepo'
        labels:
          - bug
          - feature

Quick Start

npm install llmail
import { LLMail } from 'llmail';

const llmail = new LLMail();

// Initialize with default configuration
await llmail.init();

// Create a new issue with metadata
const id = await llmail.new('bug', {
  content: 'This bug sucks.\n\nWe should fix it.'
});

// Create a new issue with metadata
const id = await llmail.new('bug', {
  frontmatter: {
    title: 'Critical Login Bug',
    severity: 'high',
    assignee: 'alice'
  }
  content: 'This bug sucks.\n\nWe should fix it.'
});

// Create a new inactive issue with metadata
const id = await llmail.new('bug', {
  active: false,
  content: 'We fixed this but it could come back if we do the dumb thing again, so recording this for future ref.'
});

// Mark it as done
await llmail.done(id);

// Mark it as done and add a state with more detail
await llmail.done(id, {
  state: 'fixed'
});

// Mark it as done with additional custom metadata
await llmail.done(id, {
  state: 'fixed',
  frontmatter: {
    resolution: 'Fixed in PR #123'
  }
});

// Move it to a different type and state
await llmail.mv(id, { type: 'test', state: 'working' });

// Move it to being active and change the state
await llmail.mv(id, { active: true, state: 'failing' });

// Get frontmatter data as an object
await llmail.getFrontmatter(id);

// Get active status of a given ID
await llmail.getFrontmatter(id).active;

// Get the state of a given ID
await llmail.getFrontmatter(id).state;

// Get any frontmatter field of a given ID
await llmail.getFrontmatter(id).myfrontmatterfield;

// Get the (non-frontmatter) contents of a given file
await llmail.getContent(id);

// Set the contents of a file -- overrides current content
await llmail.setContent(id, content);

// Structured Updates
// Add timestamped pseudo-xml-wrapped content
// metadata will be added as xml attributes
await llmail.appendUpdate({
    id, 
    content: "This content is wrapped in <Update> XML tags}, 
    metadata: {
        author: "Hank",
        saying: "Say thanks"
    }
})
// Allow skipping adding timestamp 
await llmail.prependUpdate(id, content, metadata, time=false)

CLI Usage

Initialize System

# Initialize with default configuration
llmail init

# Use specific config file
llmail init --config path/to/config.yaml

Create New Items

# Create in inbox (default for 'issue' type)
llmail new issue --title "New task"

# Create in specific state
llmail new bug --state active --title "Critical bug" --content "Some stuff about this bug"

# Create with frontmatter
llmail new feat --state active --frontmatter '{"priority": 1, "assignee": "alice"} --content "Some info about this feature\n\nIt will be a great thing."'

# Create with content
llmail new doc --content "Initial documentation draft"

Move Items

# Change state
llmail mv abc1 --state blocked

# Change type
llmail mv abc1 --type feat

# Update metadata during move
llmail mv abc1 --state active --frontmatter '{"assignee": "bob"}'

Add a Timestamped XML-wrapped Update to an Item

Prepended to top of content by default

llmail update abc1 --update "Everything broke"

# Change type
llmail update abc1 -u "Everything's fixed"

# Update metadata during update
llmail update abc1 -u "Everything's fixed" --state fixed --frontmatter '{"fixed_reason": "nancy fixed it with science"}'

# Update metadata during update
llmail update abc1 -u "Everything's fixed" --state fixed --frontmatter '{"fixed_reason": "nancy fixed it with science"}'

# Append it to the bottom of the doc instead of the top
llmail update abc1 -u "Everything's fixed" --state fixed --frontmatter '{"fixed_reason": "nancy fixed it with science"}' --append

# Prepend the update to the bottom of the doc
# This is the default from cli but this is a valid option
llmail update abc1 -u "Everything's fixed" --state fixed --frontmatter '{"fixed_reason": "nancy fixed it with science"}' --prepend

Mark Done

# Mark as done with reason
llmail done abc1 --state fixed

# Include resolution details
llmail done abc1 --state fixed --frontmatter '{"resolution": "Fixed in PR #123"}'

Sync Files

# Preview changes
llmail sync --dry-run

# Apply changes
llmail sync

# Force sync without confirmation
llmail sync --force

Read files

Output contents of individual files to stdout

# output the full contents of an individual item
llmail info abc1 

# output just the frontmatter
llmail info abc1 --frontmatter

# output just updates that have been made
llmail info abc1 --updates

# output the last 3 updates that have been made
llmail info abc1 --updates 3

# output just the content
llmail info abc1 --content

# output the first 10 lines of content
llmail info abc1 --content 10

# output the updates then the frontmatter
llmail info abc1 --updates --frontmatter

# output a specific field
llmail info abc1.frontmatterfield

API Reference

Core Actions

Initialize System

await llmail.init(options?: {
  config?: string,  // Path to config file
}): Promise<void>

Create New Item

await llmail.new(type: string, options?: {
  state?: string,      // Initial state (defaults to 'inbox' for issue type)
  frontmatter?: {      // Additional frontmatter fields
    title?: string,
    [key: string]: any
  },
  content?: string,    // Initial content
}): Promise<string>    // Returns file ID

Move Item

await llmail.mv(id: string, options: {
  state?: string,
  type?: string,
  frontmatter?: Record<string, any>  // Additional frontmatter updates
}): Promise<void>

Mark Done

await llmail.done(id: string, options?: {
  state: string,     // Optional inactive state (ie 'fixed' or 'wontfix')
  frontmatter?: Record<string, any>  // Additional frontmatter updates  
}): Promise<void>

Sync Files

await llmail.sync(options?: {
  dryRun?: boolean,    // Just show what would change
  force?: boolean      // Skip confirmations
}): Promise<SyncResult>

Types

interface SyncResult {
  changes: Array<{
    id: string,
    from: string,
    to: string,
    state: string
  }>,
  errors?: Array<{
    id: string,
    error: string
  }>
}

interface FileInfo {
  id: string,
  path: string,
  type?: string,
  state?: string,
  metadata: Record<string, any>
}

Plugin System

LLMail provides a powerful plugin system that allows extending core functionality in several ways:

  1. Custom States & Types

    • Register new active states
    • Add inactive reasons
    • Define custom types
  2. Metadata Validation

    • Add custom validation rules
    • Enforce metadata requirements
    • Validate state transitions
  3. Intent Processing

    • Define custom file organization patterns
    • Control file naming and locations
    • Add metadata processing hooks
  4. Command Line Extensions

    • Add new CLI commands with lifecycle hooks
    • Create command hierarchies with subcommands
    • Access command context and plugin configuration
    • Handle command errors gracefully
  5. Plugin Configuration

    • Define default plugin configuration
    • Validate configuration changes
    • Access configuration through plugin service
  6. Command Lifecycle

    • Pre-execution hooks for argument modification
    • Post-execution hooks for result processing
    • Error handling hooks for graceful failure

TypeScript Support

LLMail is written in TypeScript and provides comprehensive type definitions for plugin development. We strongly recommend using TypeScript and our provided types when developing plugins. The following types are available:

import { 
  Plugin,              // Core plugin interface
  PluginCommand,       // Command registration
  CommandContext,      // Command execution context
  ValidationResult,    // Validation results
  FrontmatterMetadata, // File metadata
  IntentPattern,       // File organization patterns
  InterpretedIntent,   // Processed intent
  FileLocation        // File location information
} from 'llmail';

Using our types provides several benefits:

  • Compile-time type checking prevents common errors
  • Automatic error detection for interface changes
  • Rich IDE support with autocompletion
  • Self-documenting code through type information
  • Easier upgrades when APIs change
  • Ensures correct implementation of plugin interfaces

Important: While you can develop plugins in JavaScript, using TypeScript with our types will significantly improve your development experience and help prevent runtime errors. Our plugin system is designed with TypeScript in mind, and all official plugins use TypeScript.

Plugin Interface

Plugins implement the Plugin interface:

interface Plugin {
  /** Unique identifier for the plugin */
  name: string;

  /** Plugin priority - higher numbers run first (default: 0) */
  priority?: number;

  /** Default configuration for this plugin */
  defaultConfig?: Record<string, any>;

  /** Optional function to validate plugin configuration */
  validateConfig?: (config: Record<string, any>) => ValidationResult;

  /** Register new or override existing intent patterns */
  registerIntents?: (existingPatterns: IntentPattern[]) => IntentPattern[];
  
  /** Register additional valid types */
  registerTypes?: () => string[];
  
  /** Register additional valid active states */
  registerActiveStates?: () => string[];
  
  /** Register additional valid inactive reasons */
  registerInactiveReasons?: () => string[];
  
  /** Custom metadata validation hook */
  validateMetadata?: (metadata: FrontmatterMetadata) => ValidationResult;
  
  /** Post-processing hook for interpreted intents */
  postInterpretIntent?: (intent: InterpretedIntent) => InterpretedIntent;

  /** Register additional CLI commands */
  registerCommands?: () => PluginCommand[];
}

Example Plugin: Test Triage

Here's a real-world example of a plugin that adds test state tracking:

export class TestTriagePlugin implements Plugin {
  name = 'test-triage';
  priority = 10; // High priority to ensure test patterns run first

  // Default plugin configuration
  defaultConfig = {
    reportFormat: 'markdown',
    autoSync: true,
    maxRetries: 3
  };

  // Validate configuration changes
  validateConfig(config: Record<string, any>) {
    const errors: string[] = [];
    if (!['markdown', 'html', 'json'].includes(config.reportFormat)) {
      errors.push('reportFormat must be one of: markdown, html, json');
    }
    if (typeof config.autoSync !== 'boolean') {
      errors.push('autoSync must be a boolean');
    }
    if (typeof config.maxRetries !== 'number' || config.maxRetries < 1) {
      errors.push('maxRetries must be a positive number');
    }
    return { valid: errors.length === 0, errors };
  }

  // Add test-specific states
  registerActiveStates() {
    return ['pass'];
  }

  registerInactiveReasons() {
    return ['fail'];
  }

  // Add test type
  registerTypes() {
    return ['test'];
  }

  // Validate test metadata
  validateMetadata(metadata: FrontmatterMetadata) {
    if (metadata.isTest) {
      if (!metadata.testId) {
        return {
          valid: false,
          errors: ['Test files must have a testId']
        };
      }
      if (metadata.state === 'fail' && !metadata.failureReason) {
        return {
          valid: false,
          errors: ['Failed tests must have a failure reason']
        };
      }
    }
    return { valid: true };
  }

  // Register test-specific commands with lifecycle hooks
  registerCommands() {
    return [{
      name: 'test',
      description: 'Test management commands',
      subcommands: [
        {
          name: 'run',
          description: 'Run tests',
          options: [
            { name: 'suite', type: 'string', description: 'Test suite to run' }
          ],
          beforeExecute: async (args) => {
            // Validate test suite exists
            console.log('Validating test suite...');
            return args;
          },
          execute: async (args, context) => {
            // Get plugin configuration
            const config = context.pluginService.getPluginConfig('test-triage');
            console.log(`Running tests with ${config.maxRetries} retries...`);
            // Run test implementation
          },
          afterExecute: async (result) => {
            console.log('Tests completed, generating report...');
          },
          onError: async (error, args) => {
            console.error(`Test run failed: ${error.message}`);
            return new Error(`Test suite "${args.suite}" failed: ${error.message}`);
          }
        },
        {
          name: 'report',
          description: 'Generate test report',
          execute: async (args, context) => {
            const config = context.pluginService.getPluginConfig('test-triage');
            console.log(`Generating ${config.reportFormat} report...`);
            // Report generation implementation
          }
        }
      ]
    }];
  }

  // Custom file organization for tests
  registerIntents(existingPatterns: IntentPattern[]) {
    const testPatterns = [
      {
        pattern: {
          metadata: { specificState: 'pass' }
        },
        interpretation: (state) => ({
          targetLocation: {
            dirname: path.join('issues', '_pass'),
            basename: `${state.metadata.testId}-passed.md`
          },
          targetMetadata: {
            ...state.metadata,
            active: true,
            state: 'pass'
          }
        })
      },
      // ... more patterns
    ];
    return [...testPatterns, ...existingPatterns];
  }
}

Using Plugins

  1. Install Plugin Package

    npm install llmail-testlink
  2. Enable Plugin in llmail.yaml

    # Simple enable by name
    plugins:
      - llmail-testlink
    
    # Or with configuration
    plugins:
      - name: llmail-testlink
        config:
          reportFormat: 'html'
          autoSync: true
          maxRetries: 5
  3. Use Plugin Features

    // Create a test with plugin-specific metadata
    const id = await llmail.new('test', {
      frontmatter: {
        isTest: true,
        testId: 'render-test',
        title: 'UI Rendering Test'
      }
    });
    
    // Use plugin commands
    llmail test run --suite unit
    llmail test report
  4. Update Plugin Configuration

    // Get plugin configuration
    const config = llmail.getPluginService().getPluginConfig('llmail-testlink');
    console.log(config); // { reportFormat: 'html', autoSync: true, maxRetries: 5 }
    
    // Update plugin configuration programmatically
    llmail.getPluginService().setPluginConfig('llmail-testlink', {
      reportFormat: 'json',
      autoSync: true,
      maxRetries: 5
    });

Note: When a plugin is configured in llmail.yaml, its configuration will be automatically loaded during initialization. You can still update the configuration programmatically using the plugin service.

Plugin Development Guidelines

  1. Initialization

    • Register early in the application lifecycle
    • Use priority to control execution order
    • Initialize any required resources
    • Set up default configuration
  2. State Management

    • Keep plugin state isolated
    • Use metadata for persistence
    • Clean up resources when done
    • Use plugin configuration for settings
  3. Error Handling

    • Validate inputs thoroughly
    • Provide clear error messages
    • Handle failures gracefully
    • Use command lifecycle hooks for error handling
  4. Integration

    • Work with existing llmail patterns
    • Follow file naming conventions
    • Respect metadata constraints
    • Access plugin context safely
  5. Testing

    • Test all plugin functionality
    • Verify error conditions
    • Check state transitions
    • Test configuration validation

Plugin Best Practices

  1. Metadata

    • Use clear, namespaced keys
    • Document all custom fields
    • Validate required fields
    • Consider configuration impact
  2. File Organization

    • Follow llmail directory patterns
    • Use clear file naming
    • Document structure changes
    • Make paths configurable
  3. Commands

    • Use descriptive names
    • Provide helpful descriptions
    • Include usage examples
    • Implement lifecycle hooks
  4. Performance

    • Minimize filesystem operations
    • Cache when appropriate
    • Handle large datasets
    • Use configuration for tuning
  5. Configuration

    • Provide sensible defaults
    • Validate configuration changes
    • Document configuration options
    • Use type-safe configuration

For more examples, see the examples/ directory in the repository.

File Organization Rules

Active Items

  • Live in state-specific directories prefixed with underscore (e.g., issues/_working/)
  • Follow naming pattern: {state}-{type}-{id}.md
  • Must have a state when outside inbox

Inactive Items

  • Live in type directories (e.g., issues/bug/)
  • Follow naming pattern: [{state}-]{type}-{id}.md
  • State is optional but preserved when present

Inbox Items

  • Live in inbox/ directory
  • Follow naming pattern: {type}-{id}.md
  • No state in filename or metadata

Intent System

LLMail uses an intent-based system where Location Follows Metadata: Running 'sync' will move files where their metadata says they should be.

Examples

// Sync aligns locations with metadata
await llmail.sync();  // Moves files to match their metadata state

// Everything else preserved
await llmail.done('abc1', {
  state: 'fixed'   // Only changes state-related fields
});                 // All other metadata preserved

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Publishing Plugins

When publishing a plugin to npm, follow these guidelines to ensure it can be loaded via llmail.yaml:

  1. Package Naming

    • Use the prefix llmail- for your package name (e.g., llmail-github)
    • This helps users discover your plugin and ensures proper loading
  2. Package Structure

    llmail-myplugin/
    ├── package.json
    ├── README.md
    ├── dist/
    │   └── index.js      # Built plugin code
    └── src/
        └── index.ts      # Plugin source code
  3. Package.json Configuration

    {
      "name": "llmail-myplugin",
      "version": "1.0.0",
      "main": "dist/index.js",
      "types": "dist/index.d.ts",
      "keywords": ["llmail", "llmail-plugin"],
      "peerDependencies": {
        "llmail": "^1.1.0"
      }
    }
  4. Plugin Export

    // src/index.ts
    import { Plugin } from 'llmail';
    
    export class MyPlugin implements Plugin {
      name = 'llmail-myplugin';
      // ... plugin implementation
    }
    
    // Important: Export plugin class as default export
    export default MyPlugin;
  5. Documentation

    • Document all configuration options
    • Provide example usage with llmail.yaml
    • Include TypeScript types for configuration

Example plugin documentation:

# llmail-myplugin

A plugin for llmail that does amazing things.

## Installation

```bash
npm install llmail-myplugin

Configuration

Add to your llmail.yaml:

plugins:
  - llmail-myplugin  # Use default configuration

  # Or with custom configuration:
  - name: llmail-myplugin
    config:
      option1: value1
      option2: value2

Configuration Options

| Option | Type | Default | Description | |---------|---------|---------|----------------------| | option1 | string | 'def' | Controls feature X | | option2 | boolean | true | Enables feature Y |