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

@majkapp/majk-chat-basic-tools

v1.0.28

Published

Basic tools package for Magic Chat with filesystem, process, and utility operations

Readme

@majkapp/majk-chat-basic-tools

A comprehensive set of basic tools for Magic Chat that provides essential filesystem operations, process execution, and utility functions. Built using Node.js built-in modules for maximum compatibility and minimal dependencies.

Features

  • Bash: Execute bash commands with optional background execution
  • KillBash: Terminate background bash processes with different signals
  • BashOutput: Monitor and retrieve output from background processes
  • LS: List files and directories with detailed information
  • Read: Read file contents with configurable limits and offsets
  • Glob: Fast file pattern matching using glob patterns
  • Grep: Search for text patterns in files using regular expressions
  • Edit: Perform exact string replacements in files
  • MultiEdit: Make multiple edits to a single file in one operation
  • Write: Write content to files with encoding support
  • WebFetch: Fetch content from URLs using HTTP/HTTPS
  • TodoWrite: Manage structured task lists for session tracking
  • ReadToolResult: Read back large tool results that were truncated for LLM context limits
  • Pwd: Get the current working directory for tool operations
  • Cd: Change the current working directory for tool operations

Working Directory Management

All file and directory operations support both absolute and relative paths. Relative paths are resolved against the current working directory, which defaults to the directory where the CLI was executed.

Key Features

  • Relative Path Support: Use ./src/index.js, ../docs/readme.md, etc.
  • Current Directory Tracking: Tools maintain a shared working directory context
  • Path Resolution: Automatic resolution of relative paths to absolute paths
  • Directory Navigation: Use cd to change directory, pwd to see current directory

Basic Usage

// Get current working directory
await registry.execute('pwd', {});
// Returns: { current_working_directory: '/Users/project' }

// Change directory
await registry.execute('cd', { path: './src' });
// Now working directory is '/Users/project/src'

// Use relative paths in other tools
await registry.execute('ls', { path: '.' });        // List current directory
await registry.execute('read', { file_path: './index.js' });  // Read file in current dir
await registry.execute('write', { 
  file_path: '../docs/output.txt',   // Write to parent/docs/
  content: 'Generated content'
});

Path Examples

// Absolute paths (still work as before)
await registry.execute('read', { file_path: '/home/user/project/src/index.js' });

// Relative paths (new functionality)
await registry.execute('read', { file_path: './src/index.js' });      // Current dir/src/index.js
await registry.execute('read', { file_path: '../docs/readme.md' });   // Parent dir/docs/readme.md
await registry.execute('ls', { path: '.' });                          // Current directory
await registry.execute('ls', { path: '..' });                         // Parent directory

Working Directory Tools

Pwd Tool

Get the current working directory:

await registry.execute('pwd', {});

Returns:

  • current_working_directory: The directory used for resolving relative paths
  • process_working_directory: The actual Node.js process.cwd()
  • is_same: Whether tool and process directories match

Cd Tool

Change the current working directory:

await registry.execute('cd', { path: './src' });           // Relative change
await registry.execute('cd', { path: '/home/user/docs' }); // Absolute change  
await registry.execute('cd', { path: '..' });             // Parent directory

Parameters:

  • path (string, required): Directory to change to (absolute or relative)

Installation

npm install @majkapp/majk-chat-basic-tools @majkapp/majk-chat-core

Quick Start

CLI Integration (Recommended)

The easiest way to use these tools is through the majk-chat CLI:

# Install the CLI globally
npm install -g @majkapp/majk-chat-cli

# Enable core tools for any conversation
majk-chat chat -M "List all JavaScript files and show me package.json" --enable-core-tools

# Start interactive mode with core tools
majk-chat interactive --enable-core-tools

# Customize tool result limits and filtering
majk-chat chat -M "Analyze the codebase" --enable-core-tools --tool-result-limit 4000 --allowed-tools "ls,read,glob,grep"

Programmatic Integration

import { ToolRegistry } from '@majkapp/majk-chat-core';
import { registerBasicTools, registerReadOnlyBasicTools } from '@majkapp/majk-chat-basic-tools';

// Create a tool registry
const registry = new ToolRegistry();

// Option 1: Register all basic tools (full access)
registerBasicTools(registry);

// Option 2: Register only read-only tools (safer for untrusted environments)
registerReadOnlyBasicTools(registry);

// Now you can use the registered tools
const result = await registry.execute('read', {
  file_path: '/path/to/file.txt'
}, context);

Read-Only Tools Registration

For safer operation in untrusted environments or when you want to prevent modifications, use registerReadOnlyBasicTools:

import { registerReadOnlyBasicTools } from '@majkapp/majk-chat-basic-tools';

// Register only tools that don't modify the system
registerReadOnlyBasicTools(registry);

// Available read-only tools:
// - ls, read, glob, grep (file inspection)
// - pwd, cd (navigation)
// - bash_output (monitor processes)
// - todo_write (task management)
// - web_fetch (read web content)
// - read_tool_result (access truncated results)

// These tools are NOT available in read-only mode:
// - bash (cannot execute commands)
// - edit, multi_edit, write (cannot modify files)
// - kill_bash (cannot terminate processes)

Individual Tool Usage

import { ReadTool, BashTool } from '@majkapp/majk-chat-basic-tools';

const readTool = new ReadTool();
const bashTool = new BashTool();

// Read a file with specific limits
const readResult = await readTool.execute({
  file_path: '/absolute/path/to/file.txt',
  offset: 100,
  limit: 1000
}, context);

// Execute a bash command
const bashResult = await bashTool.execute({
  command: 'ls -la',
  working_directory: '/home/user'
}, context);

Tool Reference

Bash Tool

Execute bash commands on the local system with optional background execution.

await registry.execute('bash', {
  command: 'npm install',
  working_directory: '/project',
  timeout: 60000,
  run_in_background: false
});

Parameters:

  • command (string, required): The bash command to execute
  • working_directory (string, optional): Working directory for the command
  • timeout (number, optional): Timeout in milliseconds (default: 30000, max: 600000)
  • run_in_background (boolean, optional): Run command in background

KillBash Tool

Terminate background bash processes started by the bash tool.

await registry.execute('kill_bash', {
  process_id: 'bg_1234567890_abcdef',
  signal: 'SIGTERM',
  force: true
});

Parameters:

  • process_id (string, required): The background process ID returned by bash tool
  • signal (string, optional): Signal to send (SIGTERM, SIGKILL, SIGINT, default: SIGTERM)
  • force (boolean, optional): Use SIGKILL if SIGTERM fails (default: false)

BashOutput Tool

Monitor and retrieve output from background bash processes.

// List all background processes
await registry.execute('bash_output', {
  list_all: true,
  include_finished: true
});

// Get output from specific process
await registry.execute('bash_output', {
  process_id: 'bg_1234567890_abcdef',
  filter: 'ERROR'
});

Parameters:

  • process_id (string, optional): Process ID to get output from
  • list_all (boolean, optional): List all processes instead of getting output
  • filter (string, optional): Regex filter for output lines
  • include_finished (boolean, optional): Include finished processes in list (default: true)

LS Tool

List files and directories with optional filtering and detailed information.

await registry.execute('ls', {
  path: '/absolute/path',
  ignore: ['*.tmp', '.git'],
  show_hidden: false,
  detailed: true
});

Parameters:

  • path (string, required): Absolute path to directory
  • ignore (string[], optional): Glob patterns to ignore
  • show_hidden (boolean, optional): Show hidden files (default: false)
  • detailed (boolean, optional): Include file details (default: false)

Read Tool

Read file contents with configurable offset and character limits.

await registry.execute('read', {
  file_path: '/absolute/path/to/file.txt',
  offset: 0,
  limit: 50000,
  encoding: 'utf8'
});

Parameters:

  • file_path (string, required): Absolute path to file
  • offset (number, optional): Character offset to start reading (default: 0)
  • limit (number, optional): Maximum characters to read (default: 100000)
  • encoding (string, optional): File encoding (utf8, ascii, base64, hex)

Glob Tool

Fast file pattern matching using glob patterns.

await registry.execute('glob', {
  pattern: '**/*.js',
  path: '/project/src',
  case_sensitive: true,
  include_hidden: false
});

Parameters:

  • pattern (string, required): Glob pattern (e.g., ".js", "src/**/.ts")
  • path (string, optional): Directory to search (default: current directory)
  • ignore (string[], optional): Patterns to ignore
  • case_sensitive (boolean, optional): Case sensitive matching (default: true)
  • include_hidden (boolean, optional): Include hidden files (default: false)

Grep Tool

Search for text patterns in files using regular expressions.

await registry.execute('grep', {
  pattern: 'function\\s+\\w+',
  path: '/project',
  file_pattern: '*.js',
  case_insensitive: false,
  recursive: true,
  context_lines: 2
});

Parameters:

  • pattern (string, required): Regular expression pattern
  • path (string, optional): File or directory to search
  • file_pattern (string, optional): Glob pattern for files (default: "*")
  • case_insensitive (boolean, optional): Ignore case (default: false)
  • whole_words (boolean, optional): Match whole words only (default: false)
  • line_numbers (boolean, optional): Include line numbers (default: true)
  • context_lines (number, optional): Context lines before/after matches (default: 0)
  • max_matches (number, optional): Maximum matches to return (default: 1000)
  • recursive (boolean, optional): Search recursively (default: true)

Edit Tool

Perform exact string replacements in files.

// Using relative paths (recommended)
await registry.execute('edit', {
  file_path: './src/config.js',
  old_string: 'const oldValue = 42;',
  new_string: 'const newValue = 100;',
  replace_all: false,
  create_backup: true
});

// Absolute paths also work
await registry.execute('edit', {
  file_path: '/home/user/project/src/config.js',
  old_string: 'const oldValue = 42;',
  new_string: 'const newValue = 100;'
});

Parameters:

  • file_path (string, required): Path to file (absolute or relative)
  • old_string (string, required): Exact text to find
  • new_string (string, required): Replacement text
  • replace_all (boolean, optional): Replace all occurrences (default: false)
  • create_backup (boolean, optional): Create backup before editing (default: false)

MultiEdit Tool

Make multiple edits to a single file in one atomic operation.

await registry.execute('multi_edit', {
  file_path: '/absolute/path/to/file.js',
  edits: [
    {
      old_string: 'var x = 1;',
      new_string: 'let x = 1;'
    },
    {
      old_string: 'var y = 2;',
      new_string: 'let y = 2;',
      replace_all: true
    }
  ],
  create_backup: true
});

Parameters:

  • file_path (string, required): Absolute path to file
  • edits (EditOperation[], required): Array of edit operations
  • create_backup (boolean, optional): Create backup before editing (default: false)

Write Tool

Write content to files with encoding support.

// Using relative paths (recommended)
await registry.execute('write', {
  file_path: './output/results.txt',
  content: 'File contents here',
  encoding: 'utf8',
  create_directories: true,
  overwrite: true
});

// Absolute paths also work
await registry.execute('write', {
  file_path: '/home/user/project/output/results.txt',
  content: 'File contents here'
});

Parameters:

  • file_path (string, required): Path to file (absolute or relative)
  • content (string, required): Content to write
  • encoding (string, optional): File encoding (utf8, ascii, base64, hex)
  • create_directories (boolean, optional): Create parent directories (default: true)
  • overwrite (boolean, optional): Overwrite existing files (default: true)

WebFetch Tool

Fetch content from URLs using HTTP/HTTPS requests.

await registry.execute('web_fetch', {
  url: 'https://api.example.com/data',
  method: 'GET',
  headers: { 'Authorization': 'Bearer token' },
  timeout: 30000,
  follow_redirects: true
});

Parameters:

  • url (string, required): URL to fetch
  • method (string, optional): HTTP method (GET, POST, PUT, DELETE, PATCH)
  • headers (object, optional): HTTP headers
  • body (string, optional): Request body for POST/PUT/PATCH
  • timeout (number, optional): Request timeout (default: 30000)
  • follow_redirects (boolean, optional): Follow redirects (default: true)
  • max_response_size (number, optional): Max response size in bytes (default: 10MB)

TodoWrite Tool

Manage structured task lists for session tracking.

await registry.execute('todo_write', {
  todos: [
    {
      content: 'Implement user authentication',
      status: 'pending',
      activeForm: 'Implementing user authentication',
      priority: 'high',
      tags: ['auth', 'security']
    },
    {
      content: 'Write unit tests',
      status: 'in_progress', 
      activeForm: 'Writing unit tests'
    }
  ],
  instance_id: 'project-123',
  action: 'replace'
});

Parameters:

  • todos (TodoItem[], required): Array of todo items
  • instance_id (string, optional): Session identifier (default: "default")
  • action (string, optional): How to handle the list (replace, append, update)

Configuration

You can configure the tools when registering them:

import { registerBasicTools, BasicToolsConfig } from '@majkapp/majk-chat-basic-tools';

const config: BasicToolsConfig = {
  bashRequiresConfirmation: true,
  writeRequiresConfirmation: true,
  webFetchRequiresConfirmation: true,
  webFetchTimeout: 30000,
  maxReadSize: 100000,
  createBackups: false
};

registerBasicTools(registry, config);

🔍 Dry Run System

The dry run system provides safe operation planning and approval workflows, allowing you to preview and control tool execution:

Core Components

  • DryRunManager: Central coordinator for dry run sessions
  • DryRunStore: Storage for planned operations with session management
  • Tool Wrappers: Dry run versions of all tools that capture instead of execute
  • InteractiveReviewer: CLI interface for reviewing and approving operations
  • OperationExecutor: Executes approved operations using real tools

Basic Usage

import { DryRunManager, dryRunStore } from '@majkapp/majk-chat-basic-tools';

// Create a dry run session
const dryRunManager = new DryRunManager(toolRegistry, {
  interactive: true,
  showPreview: true
});

const sessionId = dryRunManager.startDryRun();

// Now all tool operations will be captured instead of executed
const result = await registry.execute('write', {
  file_path: '/tmp/test.txt',
  content: 'Hello World'
});

// Result contains planning information instead of actual execution
console.log('Operation planned:', result.output.operationId);

// Review and execute approved operations
await dryRunManager.reviewAndExecute(sessionId, context);

Dry Run Tool Wrappers

Each tool has a corresponding dry run wrapper:

  • WriteDryRunTool: Simulates file writes, shows content diffs
  • EditDryRunTool: Shows edit operations with match counts and previews
  • MultiEditDryRunTool: Simulates multiple edits with aggregate effects
  • BashDryRunTool: Analyzes commands for safety, shows potential effects
  • GenericDryRunTool: Fallback wrapper for any tool

Operation Planning

// Example of planned operation structure
interface PlannedOperation {
  id: string;
  toolName: string;
  args: any;
  description: string;          // Human-readable description
  preview?: string;             // Detailed preview (diffs, effects, etc.)
  timestamp: Date;
  status: 'pending' | 'approved' | 'rejected' | 'executed';
  category: 'filesystem' | 'process' | 'network' | 'other';
  riskLevel: 'low' | 'medium' | 'high';
}

Risk Assessment

The system automatically assesses operation risk:

// High risk examples
- System paths (/usr/, /bin/, /etc/)
- Destructive commands (rm -rf, sudo)
- Large operations (>50KB files, >10 edits)
- Dangerous content (eval, exec, script downloads)

// Medium risk examples  
- Config files (.env, package.json)
- Package operations (npm install, git operations)
- Network requests
- Multiple edits (5+ operations)

// Low risk examples
- Simple file reads
- Basic text edits
- Safe bash commands (ls, pwd, echo)

Session Management

// Get planned operations
const operations = dryRunStore.getOperations(sessionId);

// Get session statistics
const stats = dryRunStore.getSessionStats(sessionId);
console.log(`${stats.pending} pending, ${stats.approved} approved`);

// Apply review decisions
dryRunStore.applyReviewDecisions(sessionId, [
  { operationId: 'op1', action: 'approve' },
  { operationId: 'op2', action: 'reject' }
]);

// Execute approved operations
const executor = new OperationExecutor(toolRegistry, context);
await executor.executeApprovedOperations(sessionId);

CLI Integration

The dry run system integrates seamlessly with the CLI:

# Plan operations without executing
majk-chat chat --dry-run --enable-core-tools -M "Set up a new project"

# Interactive review and execution
majk-chat chat --dry-run-review --enable-core-tools -M "Refactor and test the codebase"

Background Process Management

The package includes comprehensive background process management capabilities:

Starting Background Processes

// Start a long-running task in the background
const result = await registry.execute('bash', {
  command: 'npm run build && npm test',
  working_directory: '/project',
  run_in_background: true
});

const processId = result.output.background_id;
console.log(`Started background process: ${processId}`);

Monitoring Process Output

// List all background processes
const listResult = await registry.execute('bash_output', {
  list_all: true
});

// Get output from a specific process
const outputResult = await registry.execute('bash_output', {
  process_id: processId,
  filter: 'ERROR' // Optional regex filter
});

console.log('Process status:', outputResult.output.status.running ? 'Running' : 'Finished');
console.log('Output:', outputResult.output.stdout);

Terminating Processes

// Graceful termination
await registry.execute('kill_bash', {
  process_id: processId,
  signal: 'SIGTERM'
});

// Force termination if needed
await registry.execute('kill_bash', {
  process_id: processId,
  signal: 'SIGTERM',
  force: true // Will use SIGKILL if SIGTERM fails
});

Process Registry

Direct access to the process registry for advanced use cases:

import { ProcessRegistry } from '@majkapp/majk-chat-basic-tools';

// Get all running processes
const runningProcesses = ProcessRegistry.getRunning();

// Get process status
const status = ProcessRegistry.getStatus(processId);

// Manual cleanup of old processes
ProcessRegistry.cleanup();

Tool Result Management

Context management and result truncation is now handled by the ContextManagementExtension in @majkapp/majk-chat-core. The basic-tools package provides the ReadToolResultTool for accessing truncated results.

Automatic Result Truncation (via Core Extension)

import { MajkChatBuilder, ContextManagementExtension } from '@majkapp/majk-chat-core';
import { registerBasicTools } from '@majkapp/majk-chat-basic-tools';

// Setup with automatic truncation via extension
const builder = new MajkChatBuilder()
  .withOpenAI()
  .withContextManagement({
    maxLength: 6000,        // Max characters before truncation
    conversationId: 'chat-123'
  });

// Or use extension directly for custom configuration
const customExtension = new ContextManagementExtension({
  maxLength: 8000,
  previewLength: 4800,
  tailLength: 2400,
  conversationId: 'custom-chat'
});

builder.withExtension(customExtension);

Reading Stored Results

// Read the full result back in chunks
const readResult = await registry.execute('read_tool_result', {
  result_id: 'conv_1234567890_abcdef',
  offset: 0,
  limit: 5000,
  format: 'formatted'  // Options: 'raw', 'formatted', 'json'
});

// Navigate through large results
if (readResult.output.reading_info.has_more) {
  const nextChunk = await registry.execute('read_tool_result', {
    result_id: 'conv_1234567890_abcdef',
    offset: readResult.output.reading_info.next_offset,
    limit: 5000
  });
}

Searching Within Results

// Search for specific patterns within stored results
const searchResult = await registry.execute('read_tool_result', {
  result_id: 'conv_1234567890_abcdef',
  search: 'ERROR|WARN',  // Regex pattern
  limit: 2000
});

console.log(`Found ${searchResult.output.matches_found} matches`);
searchResult.output.matches.forEach(match => {
  console.log(`"${match.match}" at line ${match.line_number}`);
});

Listing Stored Results

// List all stored results for the current conversation
const listResult = await registry.execute('read_tool_result', {
  result_id: 'dummy',
  list_results: true
});

console.log(`${listResult.output.stored_results.length} results available`);
listResult.output.stored_results.forEach(result => {
  console.log(`${result.result_id}: ${result.tool_name} (${result.length} chars)`);
});

Result Storage Management

Note: Truncation and storage is automatically handled by the ContextManagementExtension in core. The following utilities are available for advanced usage:

Registry Management

import { ResultStorage } from '@majkapp/majk-chat-core';

// Get statistics about stored results
const stats = ResultStorage.getStats();
console.log(`Total results: ${stats.totalResults}`);
console.log(`Total size: ${stats.totalSize} characters`);

// Manual cleanup of conversation results
ResultStorage.clearConversation('old-conversation-id');

// List results for a conversation
const results = ResultStorage.listForConversation('chat-123');

ReadToolResult Tool

The read_tool_result tool provides Read-like functionality for stored results:

await registry.execute('read_tool_result', {
  result_id: 'conv_1234567890_abcdef',  // Required: stored result ID
  offset: 1000,                         // Optional: start position
  limit: 2000,                          // Optional: max characters
  format: 'formatted',                  // Optional: output format
  search: 'pattern',                    // Optional: search within result
  list_results: false                   // Optional: list all results
});

Parameters:

  • result_id (string, required): The stored result ID from truncated output
  • offset (number, optional): Character offset to start reading (default: 0)
  • limit (number, optional): Maximum characters to read (default: 10000)
  • format (string, optional): Output format - raw, formatted, json (default: formatted)
  • search (string, optional): Regex pattern to search for within the result
  • list_results (boolean, optional): List available results instead of reading

Safety Features

All tools include built-in safety features:

  • Path Resolution: Both absolute and relative paths supported with proper resolution
  • Working Directory Context: Shared working directory context across all file operations
  • Permission Checks: Tools respect filesystem permissions and access controls
  • Size Limits: File operations have configurable size limits to prevent memory issues
  • Timeout Protection: Network and process operations have configurable timeouts
  • Confirmation Requirements: Destructive operations can require user confirmation
  • Error Handling: Comprehensive error messages with both original and resolved paths
  • Path Information: All file operations return path resolution details for transparency

Integration Examples

Express.js API

import express from 'express';
import { ToolRegistry } from '@majkapp/majk-chat-core';
import { registerBasicTools } from '@majkapp/majk-chat-basic-tools';

const app = express();
const registry = new ToolRegistry();

registerBasicTools(registry);

app.post('/api/tools/:toolName', async (req, res) => {
  try {
    const result = await registry.execute(
      req.params.toolName,
      req.body,
      { sessionId: req.headers['session-id'] }
    );
    res.json(result);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

CLI Application

#!/usr/bin/env node
import { ToolRegistry } from '@majkapp/majk-chat-core';
import { registerBasicTools } from '@majkapp/majk-chat-basic-tools';

const registry = new ToolRegistry();
registerBasicTools(registry);

async function main() {
  const [toolName, ...args] = process.argv.slice(2);
  
  const result = await registry.execute(
    toolName,
    JSON.parse(args[0] || '{}'),
    { sessionId: 'cli' }
  );
  
  console.log(JSON.stringify(result, null, 2));
}

main().catch(console.error);

TypeScript Support

The package includes full TypeScript definitions:

import { BashArgs, ReadArgs, EditArgs } from '@majkapp/majk-chat-basic-tools';

// Type-safe tool arguments
const bashArgs: BashArgs = {
  command: 'ls -la',
  working_directory: '/home'
};

const readArgs: ReadArgs = {
  file_path: '/path/to/file.txt',
  offset: 0,
  limit: 1000
};

Error Handling

All tools return structured results:

interface ToolResult {
  success: boolean;
  output?: any;
  error?: string;
  metadata?: Record<string, any>;
}

Example error handling:

const result = await registry.execute('read', { file_path: '/nonexistent' });

if (!result.success) {
  console.error('Tool failed:', result.error);
} else {
  console.log('File content:', result.output.content);
}

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass: npm test
  5. Submit a pull request

License

MIT