Skip to content

sanshao85/persistent-terminal-api

Repository files navigation

Persistent Terminal API

License: MIT Node.js Version TypeScript

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 | 简体中文

FeaturesQuick StartAPI DocsWeb UIContributing


🌟 Features

  • 🖥️ 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 cwd parameter prevents path confusion
  • 👥 Multi-User Isolation - Each user (AI assistant) can only access their own terminals
  • 🆕 ANSI Sequence Cleaning - Optional clean=true parameter removes control sequences to save 30-90% tokens
  • 🎨 Web UI Included - Beautiful web interface with xterm.js for visual management

📋 Table of Contents

🚀 Quick Start

Prerequisites

  • Node.js >= 18.0.0
  • npm or yarn

Installation

# 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

Start Backend API

# Development mode (with auto-reload)
npm run dev

# Production mode
npm run build
npm start

API will be available at: http://localhost:2556/api

Start Frontend Web UI

cd frontend
node server.js

Web UI will be available at: http://localhost:2555

Quick Test

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

⚙️ Configuration

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=info

📚 API Documentation

Base URL

http://localhost:2556/api

Endpoints

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

1. Create Terminal

POST /terminals

⚠️ Both 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"
  }
}

2. Send Input

POST /terminals/:terminalId/input

Automatically adds \n if not present.

Request:

{
  "userId": "claude-assistant-1",
  "input": "npm start"
}

3. Read Output

GET /terminals/:terminalId/output

Query Parameters:

  • userId (required) - User identifier
  • since - Read from line number
  • mode - full, head, tail, head-tail
  • tailLines - 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=100

Response:

{
  "success": true,
  "data": {
    "output": "...",
    "totalLines": 150,
    "nextReadFrom": 150,
    "hasMore": false,
    "truncated": false,
    "stats": {
      "totalBytes": 5000,
      "estimatedTokens": 1250,
      "linesShown": 30,
      "linesOmitted": 120
    }
  }
}

4. Get Statistics

GET /terminals/:terminalId/stats?userId=user-1

5. List Terminals

GET /terminals?userId=user-1

6. Terminate Terminal

DELETE /terminals/:terminalId

{
  "userId": "user-1",
  "signal": "SIGTERM"
}

7. Health Check

GET /health

🎨 Web UI

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.js

Or configure auto-start in your process manager.

💡 Usage Examples

Example 1: Start Development Server

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"

Example 2: Monitor Long-Running Process

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

Example 3: AI Assistant Integration

import 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)

🏗️ Architecture

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

Key Design Decisions

  1. Auto-Execute Commands - Automatically adds \n to execute commands
  2. Smart Buffering - Circular buffer prevents memory overflow
  3. ANSI Cleaning - Optional token optimization for AI assistants
  4. Directory Safety - Required cwd prevents accidental operations
  5. Multi-User Isolation - Permission checks on all operations

📊 Performance

  • 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=true parameter

🔒 Security Considerations

⚠️ Important: This API has no authentication by default!

For production use:

  1. ✅ Add authentication (API keys, JWT, OAuth)
  2. ✅ Implement rate limiting
  3. ✅ Restrict CORS origins (change CORS_ORIGIN in .env)
  4. ✅ Limit max terminals per user
  5. ✅ Validate and sanitize all inputs
  6. ✅ Use HTTPS in production
  7. ✅ Run in isolated container/sandbox
  8. ✅ Monitor for command injection attempts

🐛 Troubleshooting

Port Already in Use

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

Terminal Not Executing Commands

Ensure the working directory (cwd) exists and has proper permissions.

Buffer Overflow

Increase MAX_BUFFER_SIZE in .env or use incremental reading with since parameter.

Sessions Timing Out

Increase SESSION_TIMEOUT in .env (value in milliseconds).

🤝 Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

📧 Support


Made with ❤️ for AI Assistants

⬆ Back to Top

About

A standalone REST API service for persistent terminal session management, designed for AI assistants

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published