Terminal Service
TL;DR
The terminal service provides client.terminal.execute() for one-shot commands and
client.terminal.stream() for interactive sessions with real-time output. Supports
execute options (timeout, cwd, env, shell), observer mode (read-only), signal handling,
terminal resize, multi-client sessions, and syntax-highlighted file viewing via ccat.
The terminal service provides command execution and interactive sessions.
How do I execute a command?
from cmdop import AsyncCMDOPClient
# Connect to remote agent using async context manager (auto-closes on exit)
async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
# Select the target machine for terminal commands
await client.terminal.set_machine("my-server")
# Execute a one-shot command and capture output + exit code
output, exit_code = await client.terminal.execute("ls -la")
print(output) # Command stdout/stderr as string
print(f"Exit code: {exit_code}") # 0 = success, non-zero = errorWhat execute options are available?
# Execute with custom options for timeout, directory, env, and shell
output, code = await client.terminal.execute(
command="./script.sh",
timeout=300, # Max execution time in seconds (default: 30)
cwd="/app", # Working directory for the command
env={"KEY": "value"}, # Additional environment variables to set
shell="/bin/bash", # Shell to use (default: system shell)
)How do I use interactive sessions?
How do I create a stream?
# Retrieve the active terminal session for the target machine
session = await client.terminal.get_active_session("my-server")
# Create a bidirectional stream for interactive I/O
stream = client.terminal.stream()
# Register a callback that fires on every chunk of terminal output
stream.on_output(lambda data: print(data.decode(), end=""))
# Connect the stream to the session (begins receiving output)
await stream.attach(session.session_id)How do I send input?
# Send a command (include \n to press Enter)
await stream.send_input(b"ls -la\n")
# Send special keys as raw bytes
await stream.send_input(b"\x03") # Ctrl+C — interrupt running process
await stream.send_input(b"\t") # Tab — trigger autocomplete
await stream.send_input(b"\x1b[A") # Up arrow — recall previous commandHow do I resize the terminal?
# Update terminal dimensions (triggers SIGWINCH on the remote process)
await stream.resize(rows=24, cols=80)How do I send signals?
await stream.send_signal("SIGINT") # Interrupt (like Ctrl+C)
await stream.send_signal("SIGTERM") # Graceful termination request
await stream.send_signal("SIGKILL") # Force kill (cannot be caught)How do I detach from a session?
await stream.close() # Detach without terminating sessionWhat stream events are available?
stream = client.terminal.stream()
# Fires when terminal output data is received (bytes)
stream.on_output(lambda data: print(data.decode(), end=""))
# Fires when the stream connection is lost
stream.on_disconnect(lambda: print("Disconnected"))
# Fires when the stream automatically reconnects
stream.on_reconnect(lambda: print("Reconnected"))
# Fires on stream errors (connection issues, protocol errors)
stream.on_error(lambda e: print(f"Error: {e}"))How does observer mode work?
Read-only access:
# Create a read-only stream — can watch but not type
stream = client.terminal.stream(mode="observer")
# Observers can still receive all terminal output
stream.on_output(handle_output)
# Attach to an existing session in read-only mode
await stream.attach(session.session_id)
# Attempting to send input as an observer raises PermissionError
# await stream.send_input(b"command") # PermissionErrorHow do I manage sessions?
Get Active Session
# Get the current active session for a specific machine
session = await client.terminal.get_active_session("my-server")
print(f"Session ID: {session.session_id}") # Unique session UUID
print(f"Status: {session.status}") # Connection status
print(f"Machine: {session.machine_hostname}") # Target machine name
print(f"Created: {session.created_at}") # Session creation timestampList Sessions
# List all terminal sessions across all machines
response = await client.terminal.list_sessions()
for session in response.sessions:
print(f"{session.machine_hostname}: {session.status}") # Machine name and statusGet Session by ID
# Fetch a specific session by its unique ID
session = await client.terminal.get_session(session_id="sess_abc123")What session properties are available?
session = await client.terminal.get_active_session("my-server")
# All available session properties
session.session_id # Unique UUID for this session
session.status # PENDING, CONNECTED, GRACE_PERIOD, or DISCONNECTED
session.machine_hostname # Hostname of the connected machine
session.workspace_id # Workspace this session belongs to
session.created_at # When the session was created
session.connected_at # When the session first connected
session.last_heartbeat_at # Last keepalive timestamp
session.attached_clients # List of currently attached client streamsHow do multiple clients share a session?
Multiple clients can attach to the same session:
# Client A creates a stream and attaches to the session
stream_a = client.terminal.stream()
await stream_a.attach(session.session_id)
# Client B attaches to the same session (collaborative mode)
stream_b = client.terminal.stream()
await stream_b.attach(session.session_id)
# Both clients receive identical terminal output in real time
# Both can send input if they have operator permissionsHow do I wait for a prompt pattern?
import asyncio
# Wait until a specific prompt string appears in terminal output
async def wait_for_prompt(stream, prompt="$"):
found = asyncio.Event() # Event to signal when prompt is detected
buffer = [] # Accumulate output chunks
def check(data):
buffer.append(data)
# Check if the prompt pattern appears in accumulated output
if prompt.encode() in b"".join(buffer):
found.set() # Signal that prompt was found
stream.on_output(check) # Register the check callback
# Wait up to 30 seconds for the prompt to appear
await asyncio.wait_for(found.wait(), timeout=30)
return b"".join(buffer).decode() # Return all output as a stringHow does syntax highlighting work?
When connected via SSH, use ccat for syntax-highlighted file viewing:
# In SSH session
ccat main.py # Python with colors
ccat config.yaml # YAML with colors
ccat -n main.go # With line numbers
ccat -t dracula app.js # Different themeThe ccat command is an alias for cmdop highlight which supports 285+ languages and 70+ themes.
Available Options
ccat <file> # Auto-detect language
ccat -n <file> # Show line numbers
ccat -l python <file> # Force language
ccat -t dracula <file> # Use different theme (default: monokai)
ccat --list-languages # Show supported languages
ccat --list-themes # Show available themesDisable Colors
NO_COLOR=1 ccat file.py # Plain output (for piping)What are common command patterns?
Run Multiple Commands
# Sequential — each command runs in its own shell invocation
output1, _ = await client.terminal.execute("cd /app")
output2, _ = await client.terminal.execute("npm install")
output3, _ = await client.terminal.execute("npm run build")
# Chained — all commands run in a single shell (shares working directory)
output, code = await client.terminal.execute(
"cd /app && npm install && npm run build" # Stops on first failure
)With Error Handling
# Execute and check the exit code to detect failures
output, code = await client.terminal.execute("./deploy.sh")
if code != 0: # Non-zero exit code indicates an error
raise Exception(f"Deploy failed: {output}")Background Process
# Start a long-running process in the background with &
await stream.send_input(b"./long_task.sh &\n")
# Retrieve the PID of the last backgrounded process
await stream.send_input(b"echo $!\n")Error Handling
from cmdop.exceptions import (
SessionNotFoundError, # No active session found for the machine
PermissionError, # Insufficient permissions (e.g., observer mode)
TimeoutError # Command exceeded its timeout limit
)
try:
output, code = await client.terminal.execute("command")
except SessionNotFoundError:
print("No active session for this machine") # Machine not connected
except PermissionError:
print("Not authorized") # Missing required permissions
except TimeoutError:
print("Command timed out") # Exceeded timeout_secondsNext
- Files Service — File operations
- Streaming — Real-time streaming
Last updated on