bm2
v1.0.33
Published
A blazing-fast, full-featured process manager built entirely on Bun native APIs. The modern PM2 replacement — zero Node.js dependencies, pure Bun performance.
Maintainers
Readme
⚡ BM2
A blazing-fast, full-featured process manager built entirely on Bun native APIs. The modern PM2 replacement — zero Node.js dependencies, pure Bun performance.
Created by the MaxxPainn Team 🌐 https://maxxpainn.com 📧 Support: [email protected]
Table of Contents
- Why BM2?
- Features
- Requirements
- Installation
- Quick Start
- CLI Reference
- Configuration Reference
- Web Dashboard
- Prometheus and Grafana Integration
- Programmatic API
- Architecture
- Comparison with PM2
- Recipes and Examples
- Troubleshooting
- File Structure
- Contributing
- License
Why BM2?
PM2 is the de facto process manager for Node.js, but it carries years of legacy baggage, a heavy dependency tree, and is fundamentally built for the Node.js runtime. BM2 is a ground-up reimagining of production process management designed exclusively for the Bun runtime.
BM2 replaces PM2's Node.js internals with Bun-native APIs. It uses Bun.spawn for process management, Bun.serve for the dashboard and IPC, native WebSocket for daemon communication, Bun.file for high-performance I/O, and Bun.gzipSync for log compression. The result is a process manager that starts faster, uses less memory, and leverages Bun's superior performance across the board.
Features
Core Process Management — Start, stop, restart, reload, delete, and scale processes with automatic restart on crash, configurable restart strategies, memory-limit restarts, and tree killing.
Cluster Mode — Run multiple instances of your application with per-worker environment injection, automatic port assignment, and round-robin-ready configuration using NODE_APP_INSTANCE conventions.
Zero-Downtime Reload — Graceful reload cycles through instances sequentially, starting the new process before stopping the old one, ensuring your application never drops a request.
Real-Time Web Dashboard — A built-in dark-themed web dashboard with live WebSocket updates, CPU/memory charts, process control buttons, and a log viewer. No external dependencies.
Prometheus Metrics — A dedicated metrics endpoint exports process and system telemetry in Prometheus exposition format, ready for scraping by Prometheus and visualization in Grafana.
Log Management — Automatic log capture with buffered writes, size-based rotation, configurable retention, optional gzip compression, log flushing, and real-time tailing.
Health Checks — HTTP health check probes with configurable intervals, timeouts, and failure thresholds that automatically restart unhealthy processes.
Cron Restarts — Schedule periodic restarts using standard cron expressions for applications that benefit from regular recycling.
File Watching — Automatic restart on file changes with configurable watch paths and ignore patterns. Ideal for development workflows.
Ecosystem Files — Declare your entire application topology in a single JSON or TypeScript configuration file and start everything with one command.
Process Persistence — Save the current process list and resurrect it after a daemon restart or system reboot. Combined with startup script generation, your applications survive server reboots.
Startup Script Generation — Automatically generate and install systemd (Linux) or launchd (macOS) service configurations so the BM2 daemon starts at boot.
Remote Deployment — A built-in deploy system that handles SSH-based deployment with git pull, release directory management, symlink rotation, and pre/post-deploy hooks.
Module/Plugin System — Extend BM2 with custom modules that hook into the process manager lifecycle.
Environment Management — Store, retrieve, and inject environment variables per process with .env file loading support.
Full IPC Architecture — A daemonized architecture where the CLI communicates with a long-running daemon process over a Unix domain socket using WebSocket protocol.
Requirements
Bun version 1.0 or higher is required. BM2 is built exclusively for the Bun runtime.
Install Bun if you haven't already:
curl -fsSL https://bun.sh/install | bashInstallation
From Source
git clone https://github.com/aspect-dev/bm2.git
cd bm2
bun install
bun linkGlobal Install
bun add -g bm2Verify Installation
bm2 --versionQuick Start
Start a process
bm2 start app.tsStart with a name and options
bm2 start app.ts --name my-api --instances 4 --port 3000List all processes
bm2 listList processes with live updates
bm2 list --liveOutput:
┌────┬──────────┬──────────┬──────┬───────┬──────────┬──────────┬──────────┐
│ ID │ Name │ Status │ PID │ CPU │ Memory │ Restarts │ Uptime │
├────┼──────────┼──────────┼──────┼───────┼──────────┼──────────┼──────────┤
│ 0 │ my-api-0 │ online │ 4521 │ 0.3% │ 42.1 MB │ 0 │ 5m 23s │
│ 1 │ my-api-1 │ online │ 4522 │ 0.2% │ 39.8 MB │ 0 │ 5m 23s │
│ 2 │ my-api-2 │ online │ 4523 │ 0.4% │ 41.3 MB │ 0 │ 5m 23s │
│ 3 │ my-api-3 │ online │ 4524 │ 0.1% │ 40.5 MB │ 0 │ 5m 23s │
└────┴──────────┴──────────┴──────┴───────┴──────────┴──────────┴──────────┘Open the dashboard
bm2 dashboardOutput:
⚡ Dashboard running at http://localhost:9615
📊 Prometheus metrics at http://localhost:9616/metricsSave and auto-resurrect on reboot
bm2 save
bm2 startupCLI Reference
Process Management
bm2 start
Start a new process or processes.
bm2 start server.tsbm2 start server.ts --name api -- --port 8080 --host 0.0.0.0bm2 start server.ts --name api --env NODE_ENV=production --env API_KEY=xxxbm2 start server.ts --name api --max-memory-restart 512Mbm2 start script.py --interpreter python3bm2 start server.ts --name api --wait-ready --listen-timeout 10000Options:
| Flag | Description | Default |
|---|---|---|
| --name <name> | Process name | Script filename |
| --instances <n> | Number of instances. Use max for all CPUs | 1 |
| --exec-mode <mode> | fork or cluster | fork |
| --cwd <path> | Working directory | Current directory |
| --env <KEY=VAL> | Environment variable (repeatable) | — |
| --interpreter <bin> | Custom interpreter binary | Auto-detected |
| --interpreter-args <args> | Arguments for the interpreter | — |
| --node-args <args> | Additional runtime arguments | — |
| --max-memory-restart <size> | Restart when memory exceeds limit | — |
| --max-restarts <n> | Maximum consecutive restarts | 16 |
| --min-uptime <ms> | Minimum uptime before a restart is considered stable | 1000 |
| --restart-delay <ms> | Delay between restarts | 0 |
| --kill-timeout <ms> | Grace period before SIGKILL | 5000 |
| --no-autorestart | Disable automatic restart | false |
| --cron <expression> | Cron expression for scheduled restarts | — |
| --watch | Enable file watching | false |
| --ignore-watch <dirs> | Directories to ignore | node_modules,.git |
| --port <n> | Base port (auto-incremented in cluster mode) | — |
| --namespace <ns> | Process namespace for grouping | — |
| --wait-ready | Wait for process ready signal | false |
| --listen-timeout <ms> | Timeout waiting for ready signal | 3000 |
| --source-map-support | Enable source map support | false |
| --merge-logs | Merge all instance logs into one file | false |
| --log-date-format <fmt> | Date format prefix for log lines | — |
| --output <file> | Custom stdout log path | ~/.bm2/logs/<name>-<id>-out.log |
| --error <file> | Custom stderr log path | ~/.bm2/logs/<name>-<id>-error.log |
| --log-max-size <size> | Max log file size before rotation | 10M |
| --log-retain <n> | Number of rotated log files to keep | 5 |
| --log-compress | Gzip rotated log files | false |
| --health-check-url <url> | HTTP endpoint for health probes | — |
| --health-check-interval <ms> | Probe interval | 30000 |
| --health-check-timeout <ms> | Probe timeout | 5000 |
| --health-check-max-fails <n> | Failures before restart | 3 |
bm2 stop
Stop a process, all processes with a name, or all processes.
bm2 stop 0
bm2 stop my-api
bm2 stop my-namespace
bm2 stop allbm2 restart
Stop and restart a process. The process is fully stopped and then re-spawned.
bm2 restart my-api
bm2 restart allbm2 reload
Graceful zero-downtime reload. New instances start before old ones are killed, ensuring your application always has live workers handling requests.
bm2 reload my-api
bm2 reload allThe reload process works as follows for each instance. First, a new process is spawned. Then BM2 waits for the new process to become stable or emit a ready signal if --wait-ready is enabled. Next, the old process receives SIGTERM and is given the kill timeout to shut down gracefully. Finally, the cycle moves to the next instance.
bm2 delete
Stop and remove a process from BM2's management.
bm2 delete 0
bm2 delete my-api
bm2 delete allbm2 scale
Dynamically scale a process group up or down.
bm2 scale my-api 8
bm2 scale my-api 2When scaling up, new instances inherit the configuration of the existing instances. When scaling down, the highest-numbered instances are stopped and removed first.
bm2 describe
Show detailed information about a process.
bm2 describe my-apiOutput:
┌─────────────────────┬──────────────────────────────────────────┐
│ Name │ my-api-0 │
│ ID │ 0 │
│ Status │ online │
│ PID │ 4521 │
│ Exec Mode │ cluster │
│ Instances │ 4 │
│ Uptime │ 2h 15m │
│ Restarts │ 0 │
│ Unstable Restarts │ 0 │
│ CPU │ 0.3% │
│ Memory │ 42.1 MB │
│ File Handles │ 24 │
│ Script │ /home/user/app/server.ts │
│ CWD │ /home/user/app │
│ Interpreter │ bun │
│ Watch │ disabled │
│ Max Memory Restart │ 512 MB │
│ Health Check │ http://localhost:3000/health (healthy) │
│ Cron Restart │ disabled │
│ Namespace │ production │
│ Created │ 2025-02-11T10:30:00.000Z │
│ Out Log │ /home/user/.bm2/logs/my-api-0-out.log │
│ Error Log │ /home/user/.bm2/logs/my-api-0-error.log │
└─────────────────────┴──────────────────────────────────────────┘bm2 list
List all managed processes with their status, resource usage, and uptime.
Supports a live mode with auto-refresh and interactive keyboard shortcuts.
bm2 listLive Mode Keyboard Shortcuts
R : Reload table manually
M : Sort by Memory usage
C : Sort by CPU usage
U : Sort by Uptime
Q : Quit live modeExamples
# List all processes once
bm2 list
# List processes with live updates
bm2 list --live
Notes
- Live mode automatically refreshes the table every second (default interval).
- Sorting can be changed on the fly using the keyboard shortcuts.
- Press
Rto reload manually,Qto quit live mode.
bm2 signal
Send an OS signal to a process.
bm2 signal my-api SIGUSR2bm2 reset
Reset the restart counter for a process.
bm2 reset my-api
bm2 reset allCluster Mode
Cluster mode spawns multiple instances of your application, each running in its own process. This is ideal for CPU-bound workloads and for taking full advantage of multi-core servers.
bm2 start server.ts --name api --instances maxbm2 start server.ts --name api --instances 4bm2 start server.ts --name api --instances 4 --port 3000Each cluster worker receives the following environment variables:
| Variable | Description |
|---|---|
| BM2_CLUSTER | Set to "true" in cluster mode |
| BM2_WORKER_ID | Zero-indexed worker ID |
| BM2_INSTANCES | Total number of instances |
| NODE_APP_INSTANCE | Same as BM2_WORKER_ID (PM2 compatibility) |
| PORT | basePort + workerIndex (if --port is specified) |
Example application using cluster-aware port binding:
// server.ts
const workerId = parseInt(process.env.BM2_WORKER_ID || "0");
const port = parseInt(process.env.PORT || "3000");
Bun.serve({
port,
fetch(req) {
return new Response(`Hello from worker ${workerId} on port ${port}`);
},
});
console.log(`Worker ${workerId} listening on :${port}`);Log Management
bm2 logs
Display recent logs for a process.
bm2 logsbm2 logs my-api --lines 100bm2 logs my-api --errbm2 logs my-api --followbm2 flush
Clear log files.
bm2 flush my-api
bm2 flushLog Rotation
Log rotation runs automatically in the background. It checks log file sizes once per minute and rotates when the configured threshold is exceeded.
bm2 start server.ts --log-max-size 50M --log-retain 10 --log-compressRotation behavior: When a log file exceeds --log-max-size, it is renamed with a numeric suffix. Existing rotated files are shifted up by one number. Files beyond the --log-retain count are deleted. If --log-compress is enabled, rotated files are gzip-compressed using Bun's native Bun.gzipSync.
Default values:
| Setting | Default |
|---|---|
| log-max-size | 10 MB |
| log-retain | 5 |
| log-compress | false |
Monitoring and Metrics
bm2 monit
Open an interactive terminal monitor showing real-time CPU, memory, and event loop data for all processes.
bm2 monitbm2 metrics
Dump a current metrics snapshot as JSON.
bm2 metricsOutput:
{
"timestamp": 1707650400000,
"processes": [
{
"id": 0,
"name": "my-api-0",
"pid": 4521,
"cpu": 0.3,
"memory": 44150784,
"handles": 24,
"status": "online",
"restarts": 0,
"uptime": 8100000
}
],
"system": {
"totalMemory": 17179869184,
"freeMemory": 8589934592,
"cpuCount": 8,
"loadAvg": [1.23, 1.45, 1.67],
"platform": "linux"
}
}bm2 metrics --history
Retrieve historical metrics. BM2 retains up to 1 hour of per-second snapshots in memory.
bm2 metrics --history 600bm2 prometheus
Output current metrics in Prometheus exposition format.
bm2 prometheusOutput:
# HELP bm2_process_cpu CPU usage percentage
# TYPE bm2_process_cpu gauge
bm2_process_cpu{name="my-api-0",id="0"} 0.3
# HELP bm2_process_memory_bytes Memory usage in bytes
# TYPE bm2_process_memory_bytes gauge
bm2_process_memory_bytes{name="my-api-0",id="0"} 44150784
# HELP bm2_process_restarts_total Total restart count
# TYPE bm2_process_restarts_total counter
bm2_process_restarts_total{name="my-api-0",id="0"} 0
# HELP bm2_process_uptime_seconds Process uptime in seconds
# TYPE bm2_process_uptime_seconds gauge
bm2_process_uptime_seconds{name="my-api-0",id="0"} 8100
# HELP bm2_process_status Process status (1=online)
# TYPE bm2_process_status gauge
bm2_process_status{name="my-api-0",id="0",status="online"} 1
# HELP bm2_system_memory_total_bytes Total system memory
# TYPE bm2_system_memory_total_bytes gauge
bm2_system_memory_total_bytes 17179869184
# HELP bm2_system_memory_free_bytes Free system memory
# TYPE bm2_system_memory_free_bytes gauge
bm2_system_memory_free_bytes 8589934592
# HELP bm2_system_load_average System load average
# TYPE bm2_system_load_average gauge
bm2_system_load_average{period="1m"} 1.23
bm2_system_load_average{period="5m"} 1.45
bm2_system_load_average{period="15m"} 1.67Dashboard
bm2 dashboard
Launch the built-in web dashboard.
bm2 dashboardbm2 dashboard --port 8080 --metrics-port 8081bm2 dashboard stop
Stop the web dashboard.
bm2 dashboard stopSee the Web Dashboard section below for a detailed description of dashboard capabilities.
Ecosystem Files
An ecosystem file defines your entire application topology in a single configuration. BM2 supports JSON and TypeScript ecosystem files.
bm2 start ecosystem.config.jsonbm2 start ecosystem.config.tsExample ecosystem.config.json:
{
"apps": [
{
"name": "api",
"script": "./src/api/server.ts",
"instances": 4,
"execMode": "cluster",
"port": 3000,
"env": {
"NODE_ENV": "production",
"DATABASE_URL": "postgres://localhost/mydb"
},
"maxMemoryRestart": "512M",
"healthCheckUrl": "http://localhost:3000/health",
"healthCheckInterval": 15000,
"logMaxSize": "50M",
"logRetain": 10,
"logCompress": true
},
{
"name": "worker",
"script": "./src/worker/index.ts",
"instances": 2,
"env": {
"NODE_ENV": "production",
"REDIS_URL": "redis://localhost:6379"
},
"cron": "0 */6 * * *",
"maxRestarts": 50
},
{
"name": "scheduler",
"script": "./src/scheduler/cron.ts",
"instances": 1,
"autorestart": true,
"watch": ["./src/scheduler"]
}
],
"deploy": {
"production": {
"user": "deploy",
"host": ["web1.example.com", "web2.example.com"],
"ref": "origin/main",
"repo": "[email protected]:your-org/your-app.git",
"path": "/var/www/app",
"preDeploy": "bun test",
"postDeploy": "bun install && bm2 reload ecosystem.config.json --env production"
}
}
}Example ecosystem.config.ts:
// ecosystem.config.ts
import type { EcosystemConfig } from "bm2/types";
const config: EcosystemConfig = {
apps: [
{
name: "api",
script: "./src/server.ts",
instances: "max",
execMode: "cluster",
port: 3000,
env: {
NODE_ENV: "production",
},
maxMemoryRestart: "1G",
healthCheckUrl: "http://localhost:3000/health",
},
],
};
export default config;Environment Management
bm2 env set
Set an environment variable for a process.
bm2 env set my-api DATABASE_URL postgres://localhost/mydbbm2 env get
List all stored environment variables for a process.
bm2 env get my-apibm2 env delete
Remove an environment variable or all environment variables.
bm2 env delete my-api DATABASE_URL
bm2 env delete my-api.env File Support
BM2 can load environment variables from .env files:
bm2 start server.ts --env-file .env.productionDeployment
BM2 includes a built-in deployment system for SSH-based deployments with release management.
bm2 deploy setup
Initial setup of the remote server. Creates the directory structure and clones the repository.
bm2 deploy ecosystem.config.json production setupThis creates the following remote directory structure:
/var/www/app/
├── source/
├── releases/
│ ├── 2025-02-11T10-30-00-000Z/
│ └── 2025-02-10T15-45-00-000Z/
├── current -> releases/2025-02-11T10-30-00-000Z/
└── shared/bm2 deploy
Deploy a new release.
bm2 deploy ecosystem.config.json productionThe deploy process works as follows. It runs the preDeploy hook locally such as running tests. It connects via SSH to each configured host. It pulls the latest code from the configured ref. It creates a new timestamped release directory. It updates the current symlink to the new release. It runs the postDeploy hook remotely such as installing dependencies and reloading processes. It cleans up old releases, keeping only the 5 most recent.
Multi-host deployment is supported. Specify an array of hosts to deploy to all of them sequentially:
{
"host": ["web1.example.com", "web2.example.com", "web3.example.com"]
}Startup Scripts
bm2 startup
Generate and display a startup script for your operating system. On Linux, this generates a systemd unit file. On macOS, this generates a launchd plist.
bm2 startupbm2 startup install
Automatically install the startup script so the BM2 daemon starts at boot.
bm2 startup installbm2 startup uninstall
Remove the startup script.
bm2 startup uninstallbm2 save
Save the current process list so it can be restored on daemon startup.
bm2 savebm2 resurrect
Restore previously saved processes.
bm2 resurrectRecommended boot setup:
bm2 start ecosystem.config.json
bm2 save
bm2 startup installOn reboot, systemd or launchd starts the BM2 daemon, and the daemon automatically runs resurrect to restore your processes.
Modules
BM2 supports a plugin system for extending functionality.
bm2 module install
Install a module from a git URL, local path, or npm package name.
bm2 module install https://github.com/user/bm2-logrotate.git
bm2 module install ./my-bm2-module
bm2 module install bm2-prometheus-pushgatewaybm2 module list
List installed modules.
bm2 module listbm2 module uninstall
Remove an installed module.
bm2 module uninstall bm2-prometheus-pushgatewayWriting a BM2 Module
A BM2 module is a package with a default export implementing the BM2Module interface:
// my-module/index.ts
import type { ProcessManager } from "bm2/process-manager";
export default {
name: "my-module",
version: "1.0.0",
init(pm: ProcessManager) {
console.log("[my-module] Initialized with", pm.list().length, "processes");
},
destroy() {
console.log("[my-module] Destroyed");
},
};Daemon Control
bm2 ping
Check if the daemon is running.
bm2 pingbm2 kill
Stop all processes and kill the daemon.
bm2 killConfiguration Reference
Ecosystem File Format
The ecosystem file is a JSON or TypeScript file with the following top-level structure:
interface EcosystemConfig {
apps: StartOptions[];
deploy?: Record<string, DeployConfig>;
}Process Options
The complete set of options available for each entry in the apps array:
| Option | Type | Default | Description |
|---|---|---|---|
| name | string | Filename | Process name |
| script | string | required | Path to the script to execute |
| args | string[] | [] | Arguments passed to the script |
| cwd | string | process.cwd() | Working directory |
| env | Record<string, string> | {} | Environment variables |
| instances | number or "max" | 1 | Number of instances |
| execMode | "fork" or "cluster" | "fork" | Execution mode |
| autorestart | boolean | true | Restart on crash |
| maxRestarts | number | 16 | Maximum restart attempts before giving up |
| minUptime | number | 1000 | Minimum ms a process must be up to be considered stable |
| maxMemoryRestart | string or number | — | Memory threshold for restart |
| restartDelay | number | 0 | Delay in ms between restart attempts |
| killTimeout | number | 5000 | Grace period in ms before SIGKILL |
| interpreter | string | Auto | Custom interpreter |
| interpreterArgs | string[] | — | Arguments for the interpreter |
| nodeArgs | string[] | — | Additional runtime arguments |
| namespace | string | — | Namespace for grouping processes |
| sourceMapSupport | boolean | false | Enable source map support |
| waitReady | boolean | false | Wait for process to emit ready signal |
| listenTimeout | number | 3000 | Timeout when waiting for ready signal |
Cluster Options
| Option | Type | Default | Description |
|---|---|---|---|
| instances | number or "max" | 1 | Worker count |
| execMode | "cluster" | "fork" | Set to cluster for multi-instance mode |
| port | number | — | Base port. Worker i gets port + i |
Log Options
| Option | Type | Default | Description |
|---|---|---|---|
| outFile | string | ~/.bm2/logs/<name>-<id>-out.log | Custom stdout log path |
| errorFile | string | ~/.bm2/logs/<name>-<id>-error.log | Custom stderr log path |
| mergeLogs | boolean | false | Merge all instance logs into one file |
| logDateFormat | string | — | Date format for log line prefixes |
| logMaxSize | string or number | "10M" | Max log file size before rotation |
| logRetain | number | 5 | Number of rotated files to keep |
| logCompress | boolean | false | Gzip-compress rotated log files |
Health Check Options
| Option | Type | Default | Description |
|---|---|---|---|
| healthCheckUrl | string | — | URL to probe |
| healthCheckInterval | number | 30000 | Probe interval in ms |
| healthCheckTimeout | number | 5000 | Probe timeout in ms |
| healthCheckMaxFails | number | 3 | Consecutive failures before restart |
Watch Options
| Option | Type | Default | Description |
|---|---|---|---|
| watch | boolean or string[] | false | Enable file watching |
| ignoreWatch | string[] | ["node_modules", ".git", ".bm2"] | Patterns to ignore |
Deploy Configuration
| Option | Type | Description |
|---|---|---|
| user | string | SSH user |
| host | string or string[] | Remote host(s) |
| ref | string | Git ref to deploy |
| repo | string | Git repository URL |
| path | string | Remote deployment path |
| preDeploy | string | Command to run locally before deploy |
| postDeploy | string | Command to run remotely after deploy |
| preSetup | string | Command to run remotely during setup |
| postSetup | string | Command to run remotely after setup |
| ssh_options | string | Additional SSH options |
| env | Record<string, string> | Environment variables for remote commands |
Web Dashboard
The BM2 dashboard is a self-contained web application served directly by the daemon. It requires no external dependencies. The HTML, CSS, JavaScript, and WebSocket server are all built in.
Dashboard Features
Process Overview — Four summary cards showing counts of online and errored processes, total CPU usage, and aggregate memory consumption.
System Information — Platform, CPU count, load average, and memory usage with a visual progress bar.
CPU and Memory Chart — A real-time canvas-rendered chart showing aggregate CPU percentage and memory usage over the last 60 data points, updating every 2 seconds.
Process Table — A detailed table showing every managed process with columns for ID, name, status with color-coded badges, PID, CPU, memory, restart count, uptime, and action buttons for restart, stop, and log viewing.
Log Viewer — A tabbed log panel that streams stdout and stderr from any selected process, with syntax highlighting for timestamps and error output. Logs auto-scroll to the latest entry.
Live Updates — All data is streamed over WebSocket with a visual pulse indicator confirming the live connection. If the connection drops, the dashboard automatically reconnects within 2 seconds.
REST API
The dashboard exposes a REST API on the same port:
| Method | Endpoint | Description |
|---|---|---|
| GET | / | Dashboard HTML |
| GET | /api/processes | List all processes as JSON |
| GET | /api/metrics | Current metrics snapshot |
| GET | /api/metrics/history?seconds=300 | Historical metrics |
| GET | /api/prometheus or /metrics | Prometheus text format |
| POST | /api/restart | Restart process |
| POST | /api/stop | Stop process |
| POST | /api/reload | Graceful reload |
| POST | /api/delete | Delete process |
| POST | /api/scale | Scale process |
| POST | /api/flush | Flush logs |
POST endpoints accept JSON body with target field for process identification and additional fields where applicable such as count for scaling.
Example using curl:
curl http://localhost:9615/api/processescurl -X POST http://localhost:9615/api/restart \
-H "Content-Type: application/json" \
-d '{"target": "my-api"}'curl -X POST http://localhost:9615/api/scale \
-H "Content-Type: application/json" \
-d '{"target": "my-api", "count": 8}'curl http://localhost:9615/metricsWebSocket API
Connect to ws://localhost:9615/ws for real-time bidirectional communication.
Client to server messages:
{ "type": "getState", "data": {} }{ "type": "getLogs", "data": { "target": 0, "lines": 50 } }{ "type": "restart", "data": { "target": "my-api" } }{ "type": "stop", "data": { "target": 0 } }{ "type": "reload", "data": { "target": "all" } }{ "type": "scale", "data": { "target": "my-api", "count": 4 } }Server to client messages:
{
"type": "state",
"data": {
"processes": [],
"metrics": {
"timestamp": 1707650400000,
"processes": [],
"system": {}
}
}
}{
"type": "logs",
"data": [
{ "name": "my-api-0", "id": 0, "out": "...", "err": "..." }
]
}Prometheus and Grafana Integration
BM2 runs a dedicated Prometheus metrics server on default port 9616 separately from the dashboard, following best practices for metrics collection.
Prometheus Configuration
Add the following to your prometheus.yml:
scrape_configs:
- job_name: "bm2"
scrape_interval: 5s
static_configs:
- targets: ["localhost:9616"]Available Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
| bm2_process_cpu | gauge | name, id | CPU usage percentage |
| bm2_process_memory_bytes | gauge | name, id | Memory usage in bytes |
| bm2_process_restarts_total | counter | name, id | Total restart count |
| bm2_process_uptime_seconds | gauge | name, id | Uptime in seconds |
| bm2_process_status | gauge | name, id, status | 1 if online, 0 otherwise |
| bm2_system_memory_total_bytes | gauge | — | Total system memory |
| bm2_system_memory_free_bytes | gauge | — | Free system memory |
| bm2_system_load_average | gauge | period | Load average (1m, 5m, 15m) |
Grafana Dashboard
Import a dashboard with the following panels for comprehensive monitoring: Process Status Overview as a stat panel colored by status, CPU Usage per Process as a time series with bm2_process_cpu grouped by name, Memory Usage per Process as a time series with bm2_process_memory_bytes grouped by name, Restart Rate as a graph of rate(bm2_process_restarts_total[5m]) to detect instability, System Load as a time series of bm2_system_load_average across all periods, and Memory Pressure as a gauge computing 1 - (bm2_system_memory_free_bytes / bm2_system_memory_total_bytes).
Alert Rules Example
groups:
- name: bm2
rules:
- alert: ProcessDown
expr: bm2_process_status == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Process {{ \$labels.name }} is down"
- alert: HighRestartRate
expr: rate(bm2_process_restarts_total[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "Process {{ \$labels.name }} is restarting frequently"
- alert: HighMemoryUsage
expr: bm2_process_memory_bytes > 1e9
for: 5m
labels:
severity: warning
annotations:
summary: "Process {{ \$labels.name }} using > 1GB memory"Programmatic API
BM2 exposes two levels of programmatic access. The BM2 client class communicates with the daemon over its Unix socket, giving you the same capabilities as the CLI from within any Bun application. For in-process usage without a daemon, you can use the ProcessManager class directly.
Programmatic Quick Start
import BM2 from "bm2";
const bm2 = new BM2();
await bm2.connect();
// Start a clustered application
await bm2.start({
script: "./server.ts",
name: "api",
instances: 4,
execMode: "cluster",
port: 3000,
env: { NODE_ENV: "production" },
});
// List all processes
const processes = await bm2.list();
console.log(processes);
// Stream metrics every 2 seconds
bm2.on("metrics", (snapshot) => {
console.log(`CPU: ${snapshot.system.cpu}% Memory: ${snapshot.system.memory}%`);
});
bm2.startPolling(2000);
// Graceful shutdown
bm2.stopPolling();
await bm2.disconnect();Connection Lifecycle
bm2.connect(): Promise<BM2>
Connect to the BM2 daemon. If the daemon is not running, it is spawned automatically and the method waits up to 5 seconds for it to become responsive. Returns the BM2 instance for chaining.
const bm2 = new BM2();
await bm2.connect();
console.log(`Connected to daemon PID ${bm2.daemonPid}`);bm2.disconnect(): Promise<void>
Disconnect from the daemon. This stops any internal polling timers but does not kill the daemon — all managed processes continue running.
await bm2.disconnect();
console.log(bm2.connected); // falsebm2.connected: boolean
Read-only property indicating whether the client believes the daemon is reachable.
bm2.daemonPid: number | null
Read-only property containing the PID of the daemon process, or null if unknown.
Programmatic Process Management
bm2.start(options: StartOptions): Promise<ProcessState[]>
Start a new process or process group. The script path is automatically resolved to an absolute path. Returns the array of ProcessState objects for the started instances.
const procs = await bm2.start({
script: "./worker.ts",
name: "worker",
instances: 2,
env: { QUEUE: "emails" },
maxMemoryRestart: "256M",
});
console.log(`Started ${procs.length} instances`);The StartOptions object accepts all the same fields documented in the Process Options configuration reference.
bm2.startEcosystem(config: EcosystemConfig): Promise<ProcessState[]>
Start an entire ecosystem configuration. All script paths within the config are resolved to absolute paths before being sent to the daemon.
const procs = await bm2.startEcosystem({
apps: [
{ script: "./api.ts", name: "api", instances: 4, port: 3000 },
{ script: "./worker.ts", name: "worker", instances: 2 },
],
});bm2.stop(target?: string | number): Promise<ProcessState[]>
Stop one or more processes. The target can be a process name, numeric ID, namespace, or "all". Defaults to "all" when omitted.
await bm2.stop("api"); // Stop by name
await bm2.stop(0); // Stop by ID
await bm2.stop(); // Stop allbm2.restart(target?: string | number): Promise<ProcessState[]>
Hard restart one or more processes. The process is fully stopped and then re-spawned.
await bm2.restart("api");
await bm2.restart(); // Restart allbm2.reload(target?: string | number): Promise<ProcessState[]>
Graceful zero-downtime reload. New instances are started before old ones are stopped, ensuring no dropped requests. Ideal for deploying new code.
await bm2.reload("api");
await bm2.reload(); // Reload allbm2.delete(target?: string | number): Promise<ProcessState[]>
Stop and remove one or more processes from BM2's management entirely.
await bm2.delete("api");
await bm2.delete(); // Delete allbm2.scale(target: string | number, count: number): Promise<ProcessState[]>
Scale a process group to the specified number of instances. When scaling up, new instances inherit the configuration of existing ones. When scaling down, the highest-numbered instances are removed first.
await bm2.scale("api", 8); // Scale up to 8 instances
await bm2.scale("api", 2); // Scale down to 2 instancesbm2.sendSignal(target: string | number, signal: string): Promise<void>
Send an OS signal to a managed process.
await bm2.sendSignal("api", "SIGUSR2");
await bm2.sendSignal(0, "SIGHUP");bm2.reset(target?: string | number): Promise<ProcessState[]>
Reset the restart counter for one or more processes. Defaults to "all".
await bm2.reset("api");
await bm2.reset(); // Reset allIntrospection
bm2.list(): Promise<ProcessState[]>
List all managed processes with their current state.
const processes = await bm2.list();
for (const proc of processes) {
console.log(`${proc.name} [${proc.status}] PID=${proc.pid} CPU=${proc.cpu}%`);
}bm2.describe(target: string | number): Promise<ProcessState[]>
Get detailed information about a specific process or process group.
const details = await bm2.describe("api");
console.log(details[0]);Logs
bm2.logs(target?: string | number, lines?: number): Promise<Array<{ name: string; id: number; out: string; err: string }>>
Retrieve recent log lines for one or all processes. Defaults to "all" with 20 lines.
const logs = await bm2.logs("api", 100);
for (const entry of logs) {
console.log(`[${entry.name}] stdout:\n${entry.out}`);
if (entry.err) console.error(`[${entry.name}] stderr:\n${entry.err}`);
}bm2.flush(target?: string | number): Promise<void>
Truncate log files for one or all processes.
await bm2.flush("api"); // Flush logs for "api"
await bm2.flush(); // Flush all logsProgrammatic Monitoring and Metrics
bm2.metrics(): Promise<MetricSnapshot>
Take a single metrics snapshot containing process-level and system-level telemetry.
const snapshot = await bm2.metrics();
console.log(`System CPU: ${snapshot.system.cpu}%`);
for (const proc of snapshot.processes) {
console.log(` ${proc.name}: ${proc.memory} bytes, ${proc.cpu}% CPU`);
}bm2.metricsHistory(seconds?: number): Promise<MetricSnapshot[]>
Retrieve historical metric snapshots from the daemon's in-memory ring buffer. The seconds parameter controls the look-back window and defaults to 300 (5 minutes). The daemon retains up to 1 hour of per-second snapshots.
const history = await bm2.metricsHistory(600); // Last 10 minutes
console.log(`Got ${history.length} snapshots`);bm2.prometheus(): Promise<string>
Get the current metrics formatted as a Prometheus exposition text string.
const text = await bm2.prometheus();
console.log(text);
// # HELP bm2_process_cpu CPU usage percentage
// # TYPE bm2_process_cpu gauge
// bm2_process_cpu{name="api-0",id="0"} 1.2
// ...bm2.startPolling(intervalMs?: number): void
Start polling the daemon for metrics at a fixed interval and emitting "metrics" events. Defaults to 2000 ms. Calling this again replaces the existing polling timer.
bm2.on("metrics", (snapshot) => {
console.log(`${snapshot.processes.length} processes running`);
});
bm2.startPolling(1000); // Poll every secondbm2.stopPolling(): void
Stop the metrics polling loop.
bm2.stopPolling();Persistence
bm2.save(): Promise<void>
Persist the current process list to ~/.bm2/dump.json so it can be restored later.
await bm2.save();bm2.resurrect(): Promise<ProcessState[]>
Restore previously saved processes from ~/.bm2/dump.json.
const restored = await bm2.resurrect();
console.log(`Restored ${restored.length} processes`);Dashboard Control
bm2.dashboard(port?: number, metricsPort?: number): Promise<{ port: number; metricsPort: number }>
Start the web dashboard. Defaults to port 9615 for the dashboard and 9616 for the Prometheus metrics endpoint.
const { port, metricsPort } = await bm2.dashboard(8080, 8081);
console.log(`Dashboard: http://localhost:${port}`);
console.log(`Metrics: http://localhost:${metricsPort}/metrics`);bm2.dashboardStop(): Promise<void>
Stop the web dashboard.
await bm2.dashboardStop();Module Management
bm2.moduleInstall(nameOrPath: string): Promise<{ path: string }>
Install a BM2 module from a git URL, local path, or npm package name.
const result = await bm2.moduleInstall("bm2-prometheus-pushgateway");
console.log(`Installed to ${result.path}`);bm2.moduleUninstall(name: string): Promise<void>
Uninstall a BM2 module.
await bm2.moduleUninstall("bm2-prometheus-pushgateway");bm2.moduleList(): Promise<Array<{ name: string; version: string }>>
List all installed modules.
const modules = await bm2.moduleList();
for (const mod of modules) {
console.log(`${mod.name}@${mod.version}`);
}Daemon Lifecycle
bm2.ping(): Promise<{ pid: number; uptime: number }>
Ping the daemon and return its PID and uptime in milliseconds.
const info = await bm2.ping();
console.log(`Daemon PID ${info.pid}, up for ${Math.round(info.uptime / 1000)}s`);bm2.kill(): Promise<void>
Kill the daemon and all managed processes. Cleans up the socket and PID files. The daemon connection will not respond after this call, which is expected.
await bm2.kill();
console.log(bm2.connected); // falsebm2.daemonReload(): Promise<string>
Reload the daemon server itself without killing managed processes.
const result = await bm2.daemonReload();
console.log(result);Low-Level Transport
bm2.send(message: DaemonMessage): Promise<DaemonResponse>
Send an arbitrary message to the daemon over the Unix socket and return the raw response. This is useful for custom command types, future extensions, or direct daemon interaction.
const response = await bm2.send({ type: "ping" });
console.log(response);
// { success: true, data: { pid: 12345, uptime: 60000 }, id: "abc123" }Messages are JSON objects with a type field for routing and an optional id field for request-response correlation (auto-generated if omitted). The data field carries command-specific payload.
Events
The BM2 class extends EventEmitter and emits the following typed events:
| Event | Payload | Description |
|---|---|---|
| daemon:connected | — | Daemon connection established |
| daemon:disconnected | — | Client disconnected from daemon |
| daemon:launched | pid: number | Daemon was spawned by this client |
| daemon:killed | — | Daemon was killed via kill() |
| error | error: Error | Transport or polling error |
| process:start | processes: ProcessState[] | Process(es) started |
| process:stop | processes: ProcessState[] | Process(es) stopped |
| process:restart | processes: ProcessState[] | Process(es) restarted |
| process:reload | processes: ProcessState[] | Process(es) reloaded |
| process:delete | processes: ProcessState[] | Process(es) deleted |
| process:scale | processes: ProcessState[] | Process group scaled |
| metrics | snapshot: MetricSnapshot | Metrics snapshot received |
| log:data | logs: Array<{ name, id, out, err }> | Log data retrieved |
const bm2 = new BM2();
bm2.on("daemon:connected", () => console.log("Connected!"));
bm2.on("daemon:disconnected", () => console.log("Disconnected"));
bm2.on("process:start", (procs) => {
console.log("Started:", procs.map((p) => p.name).join(", "));
});
bm2.on("process:stop", (procs) => {
console.log("Stopped:", procs.map((p) => p.name).join(", "));
});
bm2.on("error", (err) => console.error("BM2 error:", err.message));
bm2.on("metrics", (snapshot) => {
console.log(`${snapshot.processes.length} processes, system CPU ${snapshot.system.cpu}%`);
});
await bm2.connect();Error Handling
All methods that communicate with the daemon throw a BM2Error when the daemon returns a failure response. The error includes the command that failed and the full daemon response for inspection.
import { BM2Error } from "bm2";
try {
await bm2.describe("nonexistent");
} catch (err) {
if (err instanceof BM2Error) {
console.error(`Command "${err.command}" failed: ${err.message}`);
console.error("Full response:", err.response);
}
}Transport-level errors (daemon unreachable, socket closed) throw standard Error instances.
BM2Error Properties
| Property | Type | Description |
|---|---|---|
| message | string | Human-readable error message |
| command | string | The daemon command type that failed |
| response | DaemonResponse | The full response object from the daemon |
Direct ProcessManager Usage
For in-process usage without a running daemon, you can use the ProcessManager class directly. This is useful for embedding BM2 into your own application or for custom tooling.
import { ProcessManager } from "bm2/process-manager";
import { Dashboard } from "bm2/dashboard";
const pm = new ProcessManager();
// Start a process
const states = await pm.start({
name: "my-api",
script: "./server.ts",
instances: 4,
execMode: "cluster",
port: 3000,
env: { NODE_ENV: "production" },
maxMemoryRestart: "512M",
healthCheckUrl: "http://localhost:3000/health",
});
console.log("Started:", states.map((s) => `${s.name} (pid: ${s.pid})`));
// List processes
const list = pm.list();
// Get metrics
const metrics = await pm.getMetrics();
// Scale
await pm.scale("my-api", 8);
// Graceful reload
await pm.reload("my-api");
// Start the web dashboard
const dashboard = new Dashboard(pm);
dashboard.start(9615, 9616);
// Get Prometheus-format metrics
const promText = pm.getPrometheusMetrics();
// Save and restore
await pm.save();
await pm.resurrect();
// Stop everything
await pm.stopAll();The ProcessManager provides the same process management capabilities but runs in-process rather than communicating with a daemon. Use the BM2 client class for the standard daemon-based workflow, and ProcessManager when you need direct, embedded control.
Architecture
┌─────────────────────────────────────────────────────────┐
│ BM2 CLI │
│ (bm2 start, bm2 list, bm2 restart, bm2 dashboard) │
└────────────────────────┬────────────────────────────────┘
│ Unix Socket (WebSocket)
│ ~/.bm2/daemon.sock
▼
┌─────────────────────────────────────────────────────────┐
│ BM2 Daemon │
│ │
│ ┌─────────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ Process Manager │ │ Dashboard │ │ Modules │ │
│ │ │ │ (Bun.serve) │ │ (Plugins)│ │
│ │ ┌───────────┐ │ │ HTTP + WS │ └───────────┘ │
│ │ │ Container │ │ │ REST API │ │
│ │ │ (Bun.spawn)│ │ └──────────────┘ │
│ │ └───────────┘ │ │
│ │ ┌───────────┐ │ ┌──────────────┐ ┌───────────┐ │
│ │ │ Container │ │ │ Monitor │ │ Metrics │ │
│ │ │ (Bun.spawn)│ │ │ CPU/Memory │ │ Prometheus│ │
│ │ └───────────┘ │ └──────────────┘ │ :9616 │ │
│ │ ┌───────────┐ │ └───────────┘ │
│ │ │ Container │ │ ┌──────────────┐ │
│ │ │ (Bun.spawn)│ │ │ Health Check │ │
│ │ └───────────┘ │ │ HTTP Probes │ │
│ └─────────────────┘ └──────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌─────────────┐ │
│ │ Cluster │ │ Logs │ │ Cron │ │ Deploy │ │
│ │ Manager │ │ Manager │ │Manager │ │ Manager │ │
│ └──────────┘ └──────────┘ └────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘Daemon Process — The daemon is a long-running Bun process that manages all child processes. It listens on a Unix domain socket at ~/.bm2/daemon.sock for commands from the CLI. The daemon is automatically started when you first run a BM2 command and can be explicitly killed with bm2 kill.
Process Container — Each managed process is wrapped in a ProcessContainer that handles spawning via Bun.spawn, log piping, monitoring, restart logic, health checking, watch mode, and signal handling.
IPC Protocol — The CLI and daemon communicate over WebSocket on a Unix socket. Messages are JSON-encoded with a type field for routing and an id field for request-response correlation.
Dashboard — The dashboard is served by a Bun.serve instance with WebSocket upgrade support. A single HTTP server handles the dashboard UI, REST API, and WebSocket connections.
Metrics Server — A separate Bun.serve instance on port 9616 serves Prometheus metrics, keeping the scrape endpoint isolated from dashboard traffic.
Comparison with PM2
| Feature | PM2 | BM2 |
|---|---|---|
| Runtime | Node.js | Bun |
| Language | JavaScript | TypeScript |
| Dependencies | ~40+ packages | Zero (Bun built-ins only) |
| Process Spawning | child_process.fork | Bun.spawn |
| IPC | Custom protocol over pipes | WebSocket over Unix socket |
| HTTP Server | Express/http | Bun.serve |
| Log Compression | External pm2-logrotate module | Built-in Bun.gzipSync |
| Dashboard | PM2 Plus (paid) or pm2-monit | Built-in web dashboard (free) |
| Prometheus Metrics | pm2-prometheus-exporter module | Built-in native export |
| Startup Time | ~500ms | ~50ms |
| Memory Overhead | ~40MB (daemon) | ~12MB (daemon) |
| Cluster Mode | cluster module | Bun.spawn with env-based routing |
| Ecosystem Files | JSON, JS, YAML | JSON, TypeScript |
| Deploy System | Built-in | Built-in |
| Module System | pm2 install | bm2 module install |
| TypeScript | Requires compilation | Native support |
| File Watching | chokidar | Native fs.watch |
Recipes and Examples
Basic HTTP Server
bm2 start server.ts --name apiProduction API with Clustering and Health Checks
bm2 start server.ts \
--name api \
--instances max \
--port 3000 \
--max-memory-restart 512M \
--health-check-url http://localhost:3000/health \
--health-check-interval 15000 \
--log-max-size 50M \
--log-retain 10 \
--log-compressDevelopment Mode with Watch
bm2 start server.ts --name dev-api --watch --ignore-watch node_modules,.git,distPython Script
bm2 start worker.py --name py-worker --interpreter python3Scheduled Restart (Daily at 3 AM)
bm2 start server.ts --name api --cron "0 3 * * *"Multiple Environments via Ecosystem
{
"apps": [
{
"name": "api-staging",
"script": "./server.ts",
"env": { "NODE_ENV": "staging", "PORT": "3000" }
},
{
"name": "api-production",
"script": "./server.ts",
"env": { "NODE_ENV": "production", "PORT": "8080" }
}
]
}Full Production Setup
bm2 start ecosystem.config.json
bm2 save
bm2 startup install
bm2 dashboard
bm2 listMonitoring with Prometheus and Grafana
bm2 dashboard --metrics-port 9616
curl http://localhost:9616/metricsThen add the target to your prometheus.yml and import the Grafana dashboard.
Zero-Downtime Deploy
bm2 deploy ecosystem.config.json productionOr manually:
git pull origin main
bun install
bm2 reload allProgrammatic Monitoring Service
import BM2 from "bm2";
const bm2 = new BM2();
await bm2.connect();
// Alert when any process uses more than 512 MB
bm2.on("metrics", (snapshot) => {
for (const proc of snapshot.processes) {
if (proc.memory > 512 * 1024 * 1024) {
console.warn(`⚠️ ${proc.name} using ${Math.round(proc.memory / 1024 / 1024)} MB`);
}
}
});
bm2.startPolling(5000);
// Keep running
process.on("SIGINT", async () => {
bm2.stopPolling();
await bm2.disconnect();
process.exit(0);
});Programmatic Deploy Pipeline
import BM2 from "bm2";
const bm2 = new BM2();
await bm2.connect();
// Deploy new code, then reload
console.log("Reloading all processes...");
const reloaded = await bm2.reload("all");
console.log(`Reloaded ${reloaded.length} processes`);
// Verify everything is healthy
const processes = await bm2.list();
const allOnline = processes.every((p) => p.status === "online");
if (allOnline) {
console.log("✅ All processes online");
await bm2.save();
} else {
console.error("❌ Some processes failed to come online");
const failed = processes.filter((p) => p.status !== "online");
for (const p of failed) {
console.error(` ${p.name}: ${p.status}`);
}
}
await bm2.disconnect();Troubleshooting
Daemon won't start
If BM2 commands hang or return connection errors, the daemon may have died without cleanup.
rm -f ~/.bm2/daemon.sock ~/.bm2/daemon.pid
bm2 listProcess keeps restarting
Check the error logs for crash information:
bm2 logs my-app --err --lines 100If the process exits too quickly, it may hit the max restart limit. Check minUptime and maxRestarts settings:
bm2 describe my-appReset the counter if needed:
bm2 reset my-appHigh memory usage
If a process is using excessive memory and you have maxMemoryRestart configured, BM2 will restart it automatically. You can also check the metrics history:
bm2 metrics --history 3600Port conflicts
In cluster mode, each instance uses basePort + instanceIndex. Ensure no other services are using those ports:
lsof -i :3000-3007Log files growing too large
Enable log rotation:
bm2 start server.ts --log-max-size 50M --log-retain 5 --log-compressOr flush existing logs:
bm2 flush my-appDashboard not accessible
Ensure the dashboard is started and check the port:
bm2 dashboard --port 9615
curl http://localhost:9615If running behind a firewall, ensure port 9615 (dashboard) and 9616 (metrics) are open.
Checking daemon health
bm2 pingThis returns the daemon PID and uptime. If it doesn't respond, the daemon needs to be restarted.
File Structure
BM2 stores all data in ~/.bm2/:
~/.bm2/
├── daemon.sock # Unix domain socket for IPC
├── daemon.pid # Daemon process ID
├── dump.json # Saved process list (bm2 save)
├── config.json # Global configuration
├── env-registry.json # Stored environment variables
├── logs/ # Process log files
│ ├── my-api-0-out.log
│ ├── my-api-0-error.log
│ ├── my-api-0-out.log.1.gz
│ └── daemon-out.log
├── pids/ # PID files
│ └── my-api-0.pid
├── metrics/ # Persisted metric snapshots
└── modules/ # Installed BM2 modulesContributing
Contributions are welcome. Please follow these guidelines:
- Fork the repository and create a feature branch.
- Write tests for new functionality.
- Follow the existing code style — TypeScript strict mode, no
anywhere avoidable. - Run the test suite before submitting:
bun test. - Submit a pull request with a clear description of the change.
Development Setup
git clone https://github.com/aspect-dev/bm2.git
cd bm2
bun install
bun run src/index.ts list
bun testLicense
GPL-3.0-only
Copyright (c) 2025 MaxxPainn Team
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Built with ❤️ by the MaxxPainn Team 📧 Support: [email protected]
