npm package: @majkapp/majk-chat-basic-tools
Description: Basic tools package for Magic Chat with filesystem, process, and utility operations
Author: Jules White
Latest version: 1.0.32

# @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

```typescript
// 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

```typescript
// 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:
```typescript
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:
```typescript
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

```bash
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:

```bash
# 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

```typescript
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`:

```typescript
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

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
// 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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
// 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.

```typescript
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.

```typescript
// 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.

```typescript
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.

```typescript
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:

```typescript
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

```typescript
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

```typescript
// 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:

```typescript
// 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

```typescript
// 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:

```bash
# 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

```typescript
// 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

```typescript
// 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

```typescript
// 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:

```typescript
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)

```typescript
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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
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:

```typescript
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

```typescript
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

```typescript
#!/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:

```typescript
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:

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

Example error handling:

```typescript
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