Turn any CLI into a REST API in under 2 minutes.
@munesoft/cli-to-api is the "Zapier for CLI tools (developer edition)." Define a JSON config, run one command, and every CLI tool on your machine becomes an HTTP endpoint with authentication, rate limiting, Swagger docs, and injection protection ... zero boilerplate.
npm install -g @munesoft/cli-to-api
# or just use npx — no install needednpx cli-to-api initFollow the prompts, or skip to a manual config:
{
"routes": [
{
"route": "/stripe/customers",
"command": "stripe customers list",
"method": "GET",
"params": ["limit"],
"description": "List Stripe customers"
}
],
"server": { "port": 3000 }
}npx cli-to-api start config.jsoncurl "http://localhost:3000/stripe/customers?limit=10"{
"success": true,
"output": "...",
"error": null,
"exitCode": 0,
"durationMs": 142,
"requestId": "f3a1b2c4-..."
}That's it. Your CLI is now an API.
| Feature | Details |
|---|---|
| 🗺 Auto-routing | JSON config → HTTP routes, zero code |
| 🔐 API key auth | X-API-Key header protection |
| 🛡 Injection prevention | Strict whitelist + character sanitization |
| ⏱ Timeouts | Per-route configurable, kills runaway processes |
| 🚦 Rate limiting | Configurable window + max requests |
| 📄 OpenAPI/Swagger | Auto-generated at /docs and /openapi.json |
| 📊 Structured responses | Always JSON with success, output, exitCode |
| 🔍 Dry-run mode | Preview command without executing |
| 🧹 Clean logs | Winston-based with request IDs |
| 📦 Programmable | Full TypeScript API for embedding |
npx cli-to-api start <config.json> [options]
Options:
-p, --port <number> Port to listen on (default: 3000)
-H, --host <host> Host to bind (default: 0.0.0.0)
-k, --api-key <key> Require API key (overrides config)
--dry-run Validate config, don't start servernpx cli-to-api init [options]
Options:
-o, --output <file> Output path (default: cli-to-api.config.json)Interactive wizard that generates a starter config.
npx cli-to-api validate <config.json>Check a config file for errors without starting anything.
{
"routes": [
{
"route": "/my/endpoint", // Required. Must start with /
"command": "mycli subcommand", // Required. Base command (no user input here)
"method": "GET", // GET | POST | PUT | DELETE | PATCH
"params": ["limit", "format"], // Allowed query/body params → CLI flags
"description": "What it does", // Shows in Swagger docs
"timeout": 30000, // ms. Default: 30000. Max: 300000
"flagStyle": "double", // "double" (--flag) or "single" (-flag)
"parseOutputAsJson": true, // Try to parse stdout as JSON
"tags": ["analytics"], // OpenAPI tag grouping
"env": { "MY_VAR": "value" } // Extra env vars for this command
}
],
"server": {
"port": 3000,
"host": "0.0.0.0",
"basePath": "/api/v1" // Optional prefix for all routes
},
"security": {
"apiKey": "your-secret-key", // Require X-API-Key header
"rateLimitWindowMs": 60000, // Rate limit window (default: 60s)
"rateLimitMax": 60 // Max requests per window (default: 60)
},
"logging": {
"level": "info", // error | warn | info | debug
"requests": true // Log HTTP requests
}
}Security is non-negotiable. Here's exactly what protects you:
User input never modifies the command — only the arguments. The command is locked in your config. A request can only pass values for params you've explicitly listed in params.
All commands run via Node's child_process.spawn() with shell: false. Arguments are passed as a discrete array — the shell never sees them as a string, so ;, &&, |, $(), backticks etc. are completely inert.
# What the OS sees (safe):
execvp("stripe", ["customers", "list", "--limit", "10"])
# What exec() would do (dangerous — never used):
sh -c "stripe customers list --limit 10; rm -rf /"
Every param value is validated against a strict allowlist regex before reaching spawn(). Characters blocked include: ; & | \ $ < > \ ( ) \n \r \t { } [ ] ! #`
Each route can set a timeout (default 30s). Process is killed with SIGTERM on expiry. Max 20 concurrent executions enforced globally.
Set security.apiKey in config or pass --api-key on startup. All routes (except /health and /docs) require the X-API-Key header.
Built-in rate limiting via express-rate-limit. Defaults to 60 requests/minute per IP.
{
"route": "/analyze/sentiment",
"command": "python3 /opt/scripts/sentiment.py",
"method": "POST",
"params": ["text", "model"],
"timeout": 60000,
"parseOutputAsJson": true
}curl -X POST http://localhost:3000/analyze/sentiment \
-H "Content-Type: application/json" \
-d '{"text": "This is amazing", "model": "fast"}'{
"route": "/db/backup",
"command": "pg_dump mydb",
"method": "POST",
"params": ["format"],
"description": "Trigger a database backup"
}{
"route": "/deploy/staging",
"command": "bash /opt/deploy/staging.sh",
"method": "POST",
"params": ["branch", "tag"],
"timeout": 120000
}{ "route": "/system/disk", "command": "df -h", "method": "GET", "params": [] },
{ "route": "/system/memory", "command": "free -m", "method": "GET", "params": [] },
{ "route": "/system/procs", "command": "ps aux", "method": "GET", "params": [] }Append ?dry_run=true to any route to preview the full command that would execute — without running it:
curl "http://localhost:3000/stripe/customers?limit=10&dry_run=true"{
"dryRun": true,
"command": "stripe",
"args": ["customers", "list", "--limit", "10"],
"fullCommand": "stripe customers list --limit 10",
"requestId": "..."
}Use as a library in your own Express/Node app:
import { createApp, startServer } from '@munesoft/cli-to-api';
const config = {
routes: [
{ route: '/echo', command: 'echo', method: 'GET', params: ['message'] }
]
};
// Embed into your existing Express app
const cliRouter = createApp(config);
myApp.use('/cli', cliRouter);
// Or start standalone
const { app, close, port } = await startServer(config, { port: 4000 });
console.log(`Running on port ${port}`);Every server automatically exposes:
| Endpoint | Description |
|---|---|
GET /health |
Status, uptime, route count |
GET /docs |
Swagger UI (interactive API docs) |
GET /openapi.json |
Raw OpenAPI 3.0 spec |
git clone https://github.com/munesoft/cli-to-api
cd cli-to-api
npm install
npm testTests cover: valid execution, invalid params, injection blocking, timeout handling, API auth, POST body params, dry-run mode, config validation.
- GET/POST/PUT/DELETE/PATCH support
- API key authentication
- Rate limiting
- Swagger/OpenAPI auto-generation
- Dry-run mode
- Timeout + concurrency limits
- Webhook trigger support
- Docker image
-
--helpauto-inference (parse CLI help output → params) - Output transformation plugins
MIT © munesoft