A standalone REST API service for persistent terminal session management
Designed to enable AI assistants (Claude, GPT, etc.) to execute long-running commands without blocking
English | 简体中文
Features • Quick Start • API Docs • Web UI • Contributing
- 🖥️ Persistent Terminal Sessions - Terminals run in background even when clients disconnect
- 🔄 Auto-Execute Commands - Automatically adds newline to execute commands
- 📦 Smart Output Buffering - Circular buffer with configurable size and intelligent truncation
- 📊 Statistics & Monitoring - Track output size, line count, and token estimates
- ⏰ Auto-Cleanup - Automatically removes inactive sessions after timeout
- 🌐 REST API - Simple HTTP/JSON interface
- 🔒 Graceful Shutdown - Properly terminates all terminals on exit
- 📂 Directory Awareness - Required
cwdparameter prevents path confusion - 👥 Multi-User Isolation - Each user (AI assistant) can only access their own terminals
- 🆕 ANSI Sequence Cleaning - Optional
clean=trueparameter removes control sequences to save 30-90% tokens - 🎨 Web UI Included - Beautiful web interface with xterm.js for visual management
- Quick Start
- Installation
- Configuration
- API Documentation
- Web UI
- Usage Examples
- Architecture
- Performance
- Security
- Troubleshooting
- Contributing
- License
- Node.js >= 18.0.0
- npm or yarn
# Clone the repository
git clone https://github.com/yourusername/persistent-terminal-api.git
cd persistent-terminal-api
# Install dependencies
npm install
# Copy environment file
cp .env.example .env# Development mode (with auto-reload)
npm run dev
# Production mode
npm run build
npm startAPI will be available at: http://localhost:2556/api
cd frontend
node server.jsWeb UI will be available at: http://localhost:2555
# Create a terminal
curl -X POST http://localhost:2556/api/terminals \
-H "Content-Type: application/json" \
-d '{"userId":"test-user","cwd":"/tmp"}'
# Get terminal ID from response, then send a command
curl -X POST http://localhost:2556/api/terminals/{TERMINAL_ID}/input \
-H "Content-Type: application/json" \
-d '{"userId":"test-user","input":"echo Hello World"}'
# Read output
curl "http://localhost:2556/api/terminals/{TERMINAL_ID}/output?userId=test-user&mode=tail&tailLines=10"Default configuration in .env:
# Backend API
PORT=2556
HOST=0.0.0.0
# Frontend Web UI
FRONTEND_PORT=2555
# Terminal Settings
MAX_BUFFER_SIZE=10000 # Maximum output buffer size
SESSION_TIMEOUT=86400000 # 24 hours in milliseconds
# Security
CORS_ORIGIN=* # Restrict in production!
# Logging
LOG_LEVEL=infohttp://localhost:2556/api
| Method | Endpoint | Description |
|---|---|---|
| POST | /terminals |
Create a new terminal session |
| POST | /terminals/:id/input |
Send input to terminal |
| GET | /terminals/:id/output |
Read terminal output |
| GET | /terminals/:id/stats |
Get terminal statistics |
| GET | /terminals |
List all terminals |
| DELETE | /terminals/:id |
Terminate a terminal |
| GET | /health |
Health check |
POST /terminals
userId and cwd are required!
Request:
{
"userId": "claude-assistant-1",
"cwd": "/path/to/project",
"shell": "/bin/bash",
"env": { "NODE_ENV": "development" },
"cols": 80,
"rows": 24
}Response:
{
"success": true,
"data": {
"terminalId": "abc-123",
"userId": "claude-assistant-1",
"pid": 12345,
"shell": "/bin/bash",
"cwd": "/path/to/project",
"created": "2025-10-04T00:00:00.000Z",
"status": "active"
}
}POST /terminals/:terminalId/input
Automatically adds \n if not present.
Request:
{
"userId": "claude-assistant-1",
"input": "npm start"
}GET /terminals/:terminalId/output
Query Parameters:
userId(required) - User identifiersince- Read from line numbermode-full,head,tail,head-tailtailLines- Number of lines from end (default: 50)headLines- Number of lines from start (default: 50)maxLines- Max lines in full mode (default: 1000)clean- 🆕 Remove ANSI sequences (true/false)
Examples:
# Get last 30 lines (recommended)
GET /terminals/abc-123/output?userId=user-1&mode=tail&tailLines=30
# Get clean output (no ANSI, saves tokens)
GET /terminals/abc-123/output?userId=user-1&mode=tail&tailLines=30&clean=true
# Incremental reading
GET /terminals/abc-123/output?userId=user-1&since=100Response:
{
"success": true,
"data": {
"output": "...",
"totalLines": 150,
"nextReadFrom": 150,
"hasMore": false,
"truncated": false,
"stats": {
"totalBytes": 5000,
"estimatedTokens": 1250,
"linesShown": 30,
"linesOmitted": 120
}
}
}GET /terminals/:terminalId/stats?userId=user-1
GET /terminals?userId=user-1
DELETE /terminals/:terminalId
{
"userId": "user-1",
"signal": "SIGTERM"
}GET /health
Access the web interface at http://localhost:2555
Features:
- 📊 View all terminal sessions
- 🎯 Filter terminals by user ID
- 🖥️ Interactive terminal with xterm.js
- ⌨️ Command input box
- 🎨 Dark/Light theme toggle
- 🛑 Bulk operations (stop all)
Starting the Web UI:
cd frontend
node server.jsOr configure auto-start in your process manager.
USER_ID="claude-assistant-1"
# 1. Create terminal
TERMINAL_ID=$(curl -s -X POST http://localhost:2556/api/terminals \
-H "Content-Type: application/json" \
-d "{\"userId\":\"$USER_ID\",\"cwd\":\"/path/to/project\"}" \
| jq -r '.data.terminalId')
# 2. Start dev server
curl -X POST http://localhost:2556/api/terminals/$TERMINAL_ID/input \
-H "Content-Type: application/json" \
-d "{\"userId\":\"$USER_ID\",\"input\":\"npm run dev\"}"
# 3. Wait and check output
sleep 5
curl "http://localhost:2556/api/terminals/$TERMINAL_ID/output?userId=$USER_ID&mode=tail&tailLines=30&clean=true"# Poll for new output every 5 seconds
LAST_LINE=0
while true; do
OUTPUT=$(curl -s "http://localhost:2556/api/terminals/$TERMINAL_ID/output?userId=$USER_ID&since=$LAST_LINE&clean=true")
echo "$OUTPUT" | jq -r '.data.output'
LAST_LINE=$(echo "$OUTPUT" | jq -r '.data.nextReadFrom')
sleep 5
doneimport requests
class TerminalClient:
def __init__(self, base_url, user_id):
self.base_url = base_url
self.user_id = user_id
def create_terminal(self, cwd):
response = requests.post(f"{self.base_url}/terminals", json={
"userId": self.user_id,
"cwd": cwd
})
return response.json()['data']['terminalId']
def execute_command(self, terminal_id, command):
requests.post(f"{self.base_url}/terminals/{terminal_id}/input", json={
"userId": self.user_id,
"input": command
})
def get_output(self, terminal_id, clean=True):
params = {
"userId": self.user_id,
"mode": "tail",
"tailLines": 50,
"clean": "true" if clean else "false"
}
response = requests.get(
f"{self.base_url}/terminals/{terminal_id}/output",
params=params
)
return response.json()['data']['output']
# Usage
client = TerminalClient("http://localhost:2556/api", "claude-assistant-1")
terminal_id = client.create_terminal("/path/to/project")
client.execute_command(terminal_id, "npm start")
time.sleep(5)
output = client.get_output(terminal_id, clean=True)
print(output)src/
├── index.ts # Entry point & graceful shutdown
├── server.ts # Express server setup
├── routes/
│ └── terminals.ts # API routes
├── controllers/
│ └── terminalController.ts # Request handlers
├── services/
│ ├── terminalManager.ts # Terminal lifecycle & ANSI cleaning
│ └── outputBuffer.ts # Circular buffer with smart reading
├── middleware/
│ ├── errorHandler.ts # Error handling
│ └── logger.ts # Request logging
└── types/
└── index.ts # TypeScript definitions
frontend/
├── server.js # Static file server
├── index.html # Web UI
└── app.js # Frontend logic
- Auto-Execute Commands - Automatically adds
\nto execute commands - Smart Buffering - Circular buffer prevents memory overflow
- ANSI Cleaning - Optional token optimization for AI assistants
- Directory Safety - Required
cwdprevents accidental operations - Multi-User Isolation - Permission checks on all operations
- Concurrent Sessions: 50+ terminals
- API Response Time: < 100ms (excluding command execution)
- Memory Usage: < 500MB (50 sessions with 10K buffer each)
- CPU Usage: < 50% (normal load)
- Token Savings: 30-90% with
clean=trueparameter
For production use:
- ✅ Add authentication (API keys, JWT, OAuth)
- ✅ Implement rate limiting
- ✅ Restrict CORS origins (change
CORS_ORIGINin.env) - ✅ Limit max terminals per user
- ✅ Validate and sanitize all inputs
- ✅ Use HTTPS in production
- ✅ Run in isolated container/sandbox
- ✅ Monitor for command injection attempts
# Check what's using the port
lsof -i :2556
lsof -i :2555
# Kill processes
lsof -ti :2556 | xargs kill -9
lsof -ti :2555 | xargs kill -9Ensure the working directory (cwd) exists and has proper permissions.
Increase MAX_BUFFER_SIZE in .env or use incremental reading with since parameter.
Increase SESSION_TIMEOUT in .env (value in milliseconds).
Contributions are welcome! Please see CONTRIBUTING.md for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- node-pty - PTY bindings for Node.js
- xterm.js - Terminal emulator for the web
- Express - Web framework
- 📖 Documentation - Full AI assistant guide (中文)
- 🐛 Issue Tracker
- 💬 Discussions
Made with ❤️ for AI Assistants