Skip to content

Your shell is a URL. Execute commands via HTTP, share terminal sessions with a link, collaborate in real-time—no SSH keys, no configuration, just pure HTTP.

Launch GUI applications instantly: Type firefox & in the terminal and it appears in your browser via the matching display URL. No setup. No configuration. Just type and see.

Every Hoody container includes hoody-terminal, transforming your Linux shell into a first-class web service accessible via HTTP endpoints.


hoody-terminal provides complete shell control through HTTP:

  • 🌐 Web Terminal UI - Full-featured browser terminal—replaces SSH for most use cases
  • 🖥️ Launch GUI Apps Instantly - Type firefox & and it appears in browser—zero configuration
  • ⚡ Execute Commands - Run any shell command via POST request, get stdout/stderr/exit codes
  • 🔄 Persistent Sessions - Stateful terminals that remember working directory and environment
  • 👥 Multiplayer Sessions - Multiple users typing in the same terminal simultaneously
  • 📊 System Monitoring - Query CPU, memory, disk, processes, ports via HTTP
  • 📡 WebSocket Streaming - Real-time output for long-running commands
  • 📸 Screenshots - Capture terminal state as PNG/JPEG/GIF

Official Technical Reference:

For complete endpoint documentation with all parameters, responses, and examples:

Command Execution:

  • POST /api/v1/terminal/execute - Execute shell commands
    • Body params: command, id, timeout, wait, cwd (per-command working directory), env
    • Query params: terminal_id, cwd (initial working directory for new/reset local sessions), cwd_auto_create, shell, user, ssh_host, ssh_user, ssh_password, ssh_key, reset
    • Modes: Synchronous (wait: true) or Asynchronous (wait: false)
  • GET /api/v1/terminal/result/{command_id} - Poll async command result
    • Returns: status, exit_code, stdout, stderr, duration_ms
  • POST /api/v1/terminal/execute/{command_id}/abort - Abort a running command (SIGINT or force SIGKILL)
    • Body: { "force": false }
  • POST /api/v1/terminal/write - Type raw input into a session PTY (interactive prompts, y/n, sudo password)
    • Query params: terminal_id
    • Body: { "input": "text", "enter": true } — raw byte injection as if typed at a keyboard

Session Management:

Terminal Automation (TUI Control):

System Resource Monitoring:

System Control:

System Introspection:

WebSocket:

Health:

Web Interface & Authentication:


This is how most users access containers—replaces SSH for daily work:

https://{project}-{container}-terminal-1.{server}.containers.hoody.icu
https://{project}-{container}-terminal-2.{server}.containers.hoody.icu
https://{project}-{container}-terminal-3.{server}.containers.hoody.icu

Each number is a separate terminal session in the SAME container:

  • terminal-1 - Your main terminal session
  • terminal-2 - A second terminal for monitoring logs
  • terminal-3 - A third terminal for running tests

All in one container. Switch between them by opening different URLs. Each maintains its own state.

Open in ANY browser—phone, tablet, laptop, TV—and you have a full Linux terminal. No SSH client needed. No configuration. Just a URL.

Key insight: The number in the URL (terminal-1, terminal-2) IS the terminal ID. Switching terminals doesn’t change containers—you’re just opening another shell session in the same computer.

Your work persists across all terminals:

  • Files you create in terminal-1 are visible in terminal-2
  • Services you start in terminal-2 are accessible from terminal-3
  • Environment on the container remains consistent
  • This is one computer with multiple shell sessions

Display integration: Pair a terminal with a display by setting the display field when the session is created — the kit then exports DISPLAY=:N into that shell. The common convention is to match the numbers:

  • terminal-1 with display: "1"DISPLAY=:1
  • terminal-2 with display: "2"DISPLAY=:2
  • terminal-5 with display: "5"DISPLAY=:5

With that pairing in place, GUI programs you launch in terminal-5 appear in display-5. There is no automatic terminal_id ⇒ DISPLAY mapping — pass the display field explicitly (or export DISPLAY=:3 inside the shell) to target a given display. This lets you organize applications across displays while controlling from any terminal.

Shell selection—choose your preferred shell:

Pre-installed shells: 🐚 bash (default) • ⚡ zsh • 🐠 fish • 📋 tmux • 🐚 sh

ShellDescriptionLaunch
🐚 bashDefault shell, universal compatibilityDefault or ?shell=bash
zshModern shell, oh-my-zsh compatible, better completion?shell=zsh or exec zsh
🐠 fishFriendly shell, syntax highlighting, autosuggestions?shell=fish or exec fish
📋 tmuxTerminal multiplexer, shared between web and SSH?shell=tmux
🐚 shBourne shell, minimal, POSIX-compliant?shell=sh

Quick shell switching:

Terminal window
exec zsh # Switch to zsh
exec fish # Switch to fish
tmux # Launch tmux

Changing shells via URL: When switching shells using the ?shell= parameter on an existing session, use ?reset=true to ensure a clean start:

# Recommended: Reset when changing shells
https://PROJECT-CONTAINER-terminal-1.hoody.icu/?shell=zsh&reset=true
# This ensures the new shell starts fresh without inheriting state from the previous shell

Customize via URL parameters:

?shell=zsh # Shell choice
&fontSize=14 # Larger text
&readonly=true # View-only mode
&title=Production%20Logs # Custom title
&panel=https://docs.hoody.com&panel-width=40% # Side panel with docs

See: Web Terminal UI → for 39 customization options.

Access web terminal sessions from SSH →:

tmux sessions are shared between web and SSH access. The terminal number in the URL maps to tmux session ID:

Terminal window
# Web terminal-3: https://{project}-{container}-terminal-3.{server}.containers.hoody.icu
# SSH to same session:
ssh user@container
tmux attach -t 3
# Now you're in the exact same session as the web terminal
# Edit files, see command history—everything synced

This lets you access web terminal sessions from traditional SSH clients while maintaining full session state.

For scripts and AI agents, use the HTTP API directly:

Terminal window
# Execute a command in your container
hoody terminal sessions exec --command "ls -la /app" --wait
# Run a command asynchronously
hoody terminal sessions exec --command "npm run build" --no-wait --timeout 300
POST Execute a shell command via HTTP
/api/v1/terminal/execute
Click "Run" to execute the request

Response:

{
"success": true,
"command_id": "cmd-123",
"terminal_id": "1",
"status": "completed",
"exit_code": 0,
"stdout": "total 48\ndrwxr-xr-x 5 user user 4096 Nov 9 14:30 .\n...",
"stderr": "",
"duration_ms": 5
}

The breakthrough: Your entire shell is now accessible to:

  • AI agents (can execute commands via HTTP)
  • Mobile devices (POST from your phone)
  • Other containers (cross-container orchestration)
  • Embedded iframes (terminals in documentation)
  • Automation scripts (no SSH setup needed)

The URL terminal number determines which session executes the command:

  • terminal-1.hoody.icu/execute → Executes in terminal session 1
  • terminal-2.hoody.icu/execute → Executes in terminal session 2
  • Each session is isolated but in the same container

Sessions persist across requests:

// Execute in terminal-1
const terminalUrl = 'https://PROJECT_ID-CONTAINER_ID-terminal-1.SERVER_NAME.containers.hoody.icu';
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({ command: 'cd /app', wait: true })
});
// Later, same terminal-1 - still in /app directory
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({ command: 'ls', wait: true })
});
// Lists contents of /app (working directory preserved)

Critical understanding: Each terminal URL (terminal-1, terminal-2, terminal-3) represents a distinct shell session within the SAME container:

  • Files created in terminal-1 are immediately accessible in terminal-2
  • Processes started in terminal-2 can be seen from terminal-3
  • All terminals share the same filesystem, users, services—it’s ONE computer

State persists within each terminal session:

  • Current working directory (cd commands remembered)
  • Environment variables (exports remain)
  • Shell history
  • Background processes
  • Open file descriptors

Work persists across the container: Files, services, databases—everything remains regardless of which terminal you use.

Don’t want to cd first? Use the cwd parameter to execute commands in any directory:

// Execute in /var/log without changing session's working directory
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({
command: 'tail -f app.log',
cwd: '/var/log',
wait: false
})
});
// Session's working directory unchanged—next command runs in original location
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({ command: 'pwd' }) // Still in /home/user
});

Practical uses:

  • One-off commands in specific directories - Check logs, run tests, inspect files without navigating
  • Parallel operations - Different terminals in different directories simultaneously
  • CI/CD workflows - Execute build commands in consistent locations regardless of session state
  • Scripts - Always run from the correct directory without cd chains
// Build frontend and backend simultaneously in different directories
const build = async () => {
// Terminal-1: Frontend build
fetch(terminal1Url + '/execute', {
method: 'POST',
body: JSON.stringify({
command: 'npm run build',
cwd: '/app/frontend',
wait: false
})
});
// Terminal-2: Backend build (same time)
fetch(terminal2Url + '/execute', {
method: 'POST',
body: JSON.stringify({
command: 'go build',
cwd: '/app/backend',
wait: false
})
});
};

Key insight: cwd overrides directory temporarily for that ONE command only—session state remains untouched.

Important: The cwd parameter affects the command execution directory but does NOT change the session’s working directory. If the terminal is already running with a different working directory, cwd only applies to that single command.

For guaranteed directory control: Use ?reset=true when you need to ensure a specific starting directory:

# Guarantee terminal starts in /app directory
https://PROJECT-CONTAINER-terminal-1.hoody.icu/?reset=true
# Then use cwd parameter to execute in specific locations
POST /api/v1/terminal/execute
{
"command": "npm test",
"cwd": "/app/frontend" // Executes in /app/frontend, session stays in /home/user
}

Reset + cwd workflow:

// Reset terminal to clean state
await fetch(terminalUrl + '?reset=true');
// Now session is in /home/user
// Use cwd for specific directory execution without changing session
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({
command: 'npm run build',
cwd: '/app/dist' // Runs in /app/dist, session remains in /home/user
})
});

Need a clean slate? Use ?reset=true to restart the shell session:

https://PROJECT-CONTAINER-terminal-1.hoody.icu/?reset=true

What reset does:

  • ✅ Kills all processes in the session
  • ✅ Clears environment variables
  • ✅ Resets working directory to /home/user
  • ✅ Clears shell history
  • ✅ Fresh .bashrc / .zshrc execution
  • ✅ Removes temporary state and caches

When to use reset:

// After testing that polluted the environment
// URL: ?reset=true
// Result: Clean environment, no leaked variables
// After failed deployment left processes running
// URL: ?reset=true
// Result: All processes killed, fresh start
// After experimenting with system configurations
// URL: ?reset=true
// Result: Back to default state
// Switching between different project contexts
// URL: ?reset=true
// Result: No leftover environment from previous project

Practical workflow:

// Option 1: Reset via URL parameter (web terminal)
window.location = terminalUrl + '?reset=true';
// Option 2: Reset via DELETE endpoint (API)
await fetch(terminalUrl + '/api/v1/terminal/1', {
method: 'DELETE' // Kills session, next request creates fresh one
});
// Next command executes in brand new session
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({ command: 'env' }) // Clean environment
});

Reset vs Delete:

  • ?reset=true - Immediate clean start, auto-reconnects web terminal
  • DELETE /terminal/{id} - Kills session, requires new request to recreate
  • Both give you a fresh shell—choose based on whether you’re using the web UI or API

Common scenarios:

  • Development cycles - Reset between test runs to avoid state contamination
  • User demos - Start each demo with a clean environment
  • CI/CD stages - Reset before each deployment step
  • Troubleshooting - Eliminate environment issues by starting fresh

Share a terminal URL and everyone types together:

Multiple users connecting to:

https://{project}-{container}-terminal-1.{server}.containers.hoody.icu

Each connected client:

  • ✅ Sees the same terminal output (the PTY broadcasts to every attached client)
  • ✅ Can type commands simultaneously (all share one shell/screen)
  • ✅ Can attach read-only with ?readonly=true (input blocked for that client only — mix read-only viewers with read-write collaborators in the same session)

Perfect for:

  • Pair programming (both typing in same session)
  • Teaching Linux (instructor and students share terminal)
  • Customer support (solve issues together)
  • Team debugging (everyone sees live output)

See: Multiplayer by Default → for collaboration philosophy.

The killer feature: Start any GUI program and see it immediately in your browser.

Terminal window
# In a terminal paired with display 2 (created with display: "2"), run Firefox
firefox &
# It appears in display-2
# Open: https://{project}-{container}-display-2.{server}.containers.hoody.icu

That’s it. Pair the session with a display, type the command in the terminal URL, and the GUI appears in the matching display URL. No configuration. No setup. Instant graphical applications.

Works with ANY GUI program:

Terminal window
# In terminal-3
code /app # VS Code opens in display-3
libreoffice report.pdf # LibreOffice opens in display-3
gimp photo.jpg # GIMP opens in display-3
chrome # Chrome opens in display-3

Why this is revolutionary:

Traditional remote desktop: Configure VNC/RDP server, install client, connect to desktop, open terminal, run program (finally).

Hoody: Type command in terminal URL. Program appears in display URL. Done.

Your phone can now run:

  • Terminal URL on phone → Type code .
  • Display URL on tablet → See VS Code running
  • Control from anywhere, view from anywhere

Perfect for:

  • Quick GUI app testing (start in browser instantly)
  • Mobile development workflows (command on phone, view on tablet)
  • Teaching (instructor types, students see GUI appear)
  • Documentation (embed terminal + display showing live app)

See Displays → for the full desktop experience.

The first time a terminal/desktop session for display N is created (CLI, SDK call, or URL), Hoody Terminal boots an X server on :N and attaches the dunst notification daemon to it. That’s why the URL trick above just works — and it’s also why notifications sent to display N via the Notifications kit start dispatching as soon as the session exists.

6. SSH to Remote Servers (No Client Needed)

Section titled “6. SSH to Remote Servers (No Client Needed)”

The breakthrough: Manage ANY server from a browser—no SSH client, no keys, no configuration.

Hoody Terminal becomes your SSH client. Connect to remote servers via HTTP—the Hoody container makes the SSH connection FOR you.

Traditional SSH workflow:

  1. Install SSH client on your device
  2. Generate SSH keys
  3. Copy keys to server
  4. Remember server addresses and ports
  5. Use command line to connect

Hoody workflow:

  1. Open terminal URL in browser
  2. Add SSH parameters to URL
  3. You’re connected

Password authentication:

https://PROJECT-CONTAINER-terminal-1.hoody.icu/
?ssh_host=production-server.com
&ssh_user=admin
&ssh_password=your_password

SSH key authentication:

# Base64-encode the key first:
# SSH_KEY=$(base64 -w0 /home/user/.ssh/id_rsa | jq -sRr @uri)
https://PROJECT-CONTAINER-terminal-1.hoody.icu/
?ssh_host=192.168.1.100
&ssh_user=root
&ssh_key=<base64-encoded-private-key-content>

Custom port:

# Base64-encode the key first:
# SSH_KEY=$(base64 -w0 /keys/deploy_key | jq -sRr @uri)
https://PROJECT-CONTAINER-terminal-1.hoody.icu/
?ssh_host=server.example.com:2222
&ssh_user=deploy
&ssh_key=<base64-encoded-private-key-content>

You’re now controlling the remote server—through HTTP—from any device with a browser.

The SSH connection persists—send commands repeatedly to the same remote server:

// Connect to production database server
const remoteSession = await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({
command: 'systemctl status postgresql',
wait: true
}),
headers: {
'Content-Type': 'application/json'
}
}).then(r => r.text());

URL already has SSH params:

import { readFileSync } from 'fs';
// Base64-encode the private key before embedding in the URL
const sshKey = readFileSync('/secure/postgres.key', 'base64');
const terminalUrl = 'https://PROJECT-CONTAINER-terminal-5.hoody.icu' +
'?ssh_host=db-server.internal' +
'&ssh_user=postgres' +
'&ssh_key=' + encodeURIComponent(sshKey);

Now every POST to this URL executes commands on db-server.internal:

// All these execute on the REMOTE server, not the Hoody container
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({ command: 'pg_dump production > backup.sql' })
});
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({ command: 'du -sh backup.sql' })
});
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({ command: 'gzip backup.sql' })
});

The SSH connection stays open—session state persists across requests.

Manage servers from phones/tablets:

  • No SSH app needed
  • No terminal emulator
  • Just a browser and a URL

Share server access without credentials:

https://docs-server-terminal-1.hoody.icu?ssh_host=docs.internal&ssh_user=readonly

Send this URL to your team. They can:

  • ✅ Execute commands on docs.internal
  • ✅ Access immediately (no credential setup)
  • ✅ View output in browser
  • ❌ Never see the actual SSH credentials

Control via proxy permissions:

  • IP whitelist for production servers
  • Password protect staging access
  • JWT tokens for automated deployments
  • See Permissions →

Monitor production logs from phone:

# base64 -w0 /keys/aws-prod.pem | jq -sRr @uri → use that value for ssh_key
https://PROJECT-CONTAINER-terminal-monitor.hoody.icu/
?ssh_host=prod-app-1.aws.com
&ssh_user=ubuntu
&ssh_key=<base64-encoded-private-key-content>
&command=tail%20-f%20/var/log/app.log

Bookmark this URL. Open on phone. See live production logs—no SSH client needed.

Multi-server deployment script:

const servers = [
'web-1.production.com',
'web-2.production.com',
'web-3.production.com'
];
// Base64-encode deploy key once before the loop
import { readFileSync } from 'fs';
const deployKey = encodeURIComponent(readFileSync('/secure/deploy.key', 'base64'));
// Deploy to all servers via HTTP
for (const server of servers) {
const terminalUrl = `https://PROJECT-CONTAINER-terminal-deploy.hoody.icu` +
`?ssh_host=${server}` +
`&ssh_user=deploy` +
`&ssh_key=${deployKey}`;
await fetch(terminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({
command: 'cd /app && git pull && systemctl restart app',
wait: true,
timeout: 300
})
});
console.log(`Deployed to ${server}`);
}

Database backup automation:

import { readFileSync } from 'fs';
// Runs on Hoody container, connects to DB server via SSH, executes backup
const pgKey = encodeURIComponent(readFileSync('/keys/postgres.key', 'base64'));
const backupUrl = 'https://PROJECT-CONTAINER-terminal-backup.hoody.icu' +
'?ssh_host=db-primary.internal' +
'&ssh_user=postgres' +
'&ssh_key=' + pgKey;
// This command runs on db-primary.internal, not Hoody container
await fetch(backupUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({
command: `
pg_dump production | gzip > /backups/prod_$(date +%Y%m%d).sql.gz &&
aws s3 cp /backups/prod_$(date +%Y%m%d).sql.gz s3://backups/ &&
echo "Backup complete"
`,
wait: true
})
});

Security audit from phone:

# base64 -w0 /keys/security-audit.key | jq -sRr @uri → use that value for ssh_key
https://PROJECT-CONTAINER-terminal-audit.hoody.icu/
?ssh_host=firewall.company.com
&ssh_user=security
&ssh_key=<base64-encoded-private-key-content>
&command=iptables%20-L%20-n%20-v

Open URL. See firewall rules. No laptop. No SSH client. Just HTTP.

Connections auto-maintain until you reset:

import { readFileSync } from 'fs';
// ssh_key must be base64-encoded private key content, not a file path
const sshKeyB64 = readFileSync('/path/to/key', 'base64');
// Session created with SSH params
const url = terminalUrl + '?ssh_host=server.com&ssh_user=admin&ssh_key=' + encodeURIComponent(sshKeyB64);
// First command: Connects via SSH
await fetch(url + '/execute', { method: 'POST', body: JSON.stringify({ command: 'pwd' }) });
// Subsequent commands: Reuses connection (fast)
await fetch(url + '/execute', { method: 'POST', body: JSON.stringify({ command: 'ls' }) });
await fetch(url + '/execute', { method: 'POST', body: JSON.stringify({ command: 'df -h' }) });
// Connection stays open across requests—no reconnection overhead

Reset to change servers:

// Switch to different server
window.location = terminalUrl +
'?reset=true' + // Closes previous SSH connection
'&ssh_host=new-server.com' +
'&ssh_user=deploy' +
// ssh_key must be base64-encoded private key content, not a file path
'&ssh_key=' + encodeURIComponent(readFileSync('/keys/deploy.key', 'base64'));

Key insights:

  • ✅ SSH connections maintained by Hoody container
  • ✅ Your device only uses HTTP (browser/fetch)
  • ✅ Sessions persist—fast repeated commands
  • ✅ Credentials stored securely in container (keys in /keys/)
  • ✅ Access control via proxy permissions, not SSH keys
  • ✅ Works from ANY device with a browser

This transforms server management: Your phone can now control production servers, run database backups, monitor logs, deploy code—all via HTTP, all without an SSH client.

7. AI Agent Side Panel (hoody-agent Integration)

Section titled “7. AI Agent Side Panel (hoody-agent Integration)”

The perfect pairing: Terminal + AI agent side-by-side in one browser window.

Hoody Terminal supports embedding hoody-agent directly in a side panel—giving you an AI assistant that can see your terminal, suggest commands, and help you work.

Add the agent URL as a side panel:

https://PROJECT-CONTAINER-terminal-1.hoody.icu/
?panel=https://PROJECT-CONTAINER-workspaces-1.hoody.icu
&panel-width=40%

What you get:

  • Left 40%: Hoody Agent (chat interface, suggestions, explanations)
  • Right 60%: Terminal (command execution)
  • Same container: Agent and terminal share filesystem, processes, state

The workflow:

  1. Ask agent: “How do I find large files?”
  2. Agent responds: du -sh * | sort -h
  3. Execute command in terminal (right side)
  4. Agent sees result and offers next step
  5. Repeat—AI-assisted shell work

Each terminal can have its own dedicated agent:

# Terminal-1 with agent-1 (configured for frontend context)
https://PROJECT-CONTAINER-terminal-1.hoody.icu/
?panel=https://PROJECT-CONTAINER-workspaces-1.hoody.icu
&panel-width=35%
# Terminal-2 with agent-2 (configured for backend context)
https://PROJECT-CONTAINER-terminal-2.hoody.icu/
?panel=https://PROJECT-CONTAINER-workspaces-2.hoody.icu
&panel-width=35%
# Terminal-3 with agent-3 (configured for DevOps context)
https://PROJECT-CONTAINER-terminal-3.hoody.icu/
?panel=https://PROJECT-CONTAINER-workspaces-3.hoody.icu
&panel-width=35%

Each agent instance:

  • ✅ Configured for specific context (via profiles, memory, system prompt)
  • ✅ Has access to terminal history in its pane
  • ✅ Can see files and processes in the container
  • ✅ Provides specialized assistance based on its configuration

Bookmark these URLs—instant AI-assisted development environments.

1. Learning Linux/DevOps:

https://PROJECT-CONTAINER-terminal-1.hoody.icu/
?panel=https://PROJECT-CONTAINER-workspaces-1.hoody.icu
&panel-width=40%
  • Ask agent to explain commands before running them
  • Agent provides context, flags, common patterns
  • Execute in terminal, see results
  • Agent explains output and suggests next steps

2. Debugging Issues:

https://PROJECT-CONTAINER-terminal-2.hoody.icu/
?panel=https://PROJECT-CONTAINER-workspaces-2.hoody.icu
&panel-width=35%
  • Describe problem to agent: “API returning 500 errors”
  • Agent suggests diagnostic commands: tail -f /var/log/app.log
  • Execute commands in terminal
  • Agent analyzes logs, suggests fixes
  • Apply fixes, agent monitors results

3. Infrastructure Management:

https://PROJECT-CONTAINER-terminal-3.hoody.icu/
?panel=https://PROJECT-CONTAINER-workspaces-3.hoody.icu
&panel-width=40%
&ssh_host=production-server.com
&ssh_user=admin
  • Combined SSH + Agent + Terminal
  • SSH to production server (via HTTP)
  • Agent helps with server management commands
  • Safe infrastructure changes with AI assistance

4. Code Review + Testing:

https://PROJECT-CONTAINER-terminal-4.hoody.icu/
?panel=https://PROJECT-CONTAINER-workspaces-4.hoody.icu
&panel-width=45%
  • Agent reviews code in /app/src
  • Suggests improvements and test commands
  • Execute tests in terminal: npm test
  • Agent analyzes test output, suggests fixes
  • Iterate until tests pass

What the agent can do from the panel:

  • 📖 Read files in the container
  • 🔍 Execute commands (if you grant permission)
  • 📊 Analyze output from terminal
  • 💡 Suggest solutions based on context
  • 📝 Remember conversation across page reloads (via agent state)
  • 🔗 Access container services (databases, web servers, etc.)

Panel width customization:

&panel-width=30% # Smaller panel, more terminal space
&panel-width=50% # Equal split
&panel-width=60% # Larger panel for complex agent responses

Pick based on your workflow—debugging needs more terminal space, learning needs more agent space.

Multiple agents for complex projects:

Terminal window
# Frontend terminal with agent-1 (configured for React/TypeScript)
open "https://PROJECT-CONTAINER-terminal-1.hoody.icu/?panel=https://PROJECT-CONTAINER-workspaces-1.hoody.icu"
# Backend terminal with agent-2 (configured for Go/databases)
open "https://PROJECT-CONTAINER-terminal-2.hoody.icu/?panel=https://PROJECT-CONTAINER-workspaces-2.hoody.icu"
# Database terminal with agent-3 (configured for PostgreSQL)
open "https://PROJECT-CONTAINER-terminal-3.hoody.icu/?panel=https://PROJECT-CONTAINER-workspaces-3.hoody.icu&ssh_host=db.internal"

Each agent instance configured differently:

  • agent-1: React/TypeScript context (via profiles and memory)
  • agent-2: Go/API context (via profiles and memory)
  • agent-3: PostgreSQL/DBA context (via profiles and memory)

All working in the same container—files created by one terminal visible to others, services accessible across terminals, perfect for full-stack development.

Traditional setup:

  1. Open terminal application
  2. Open separate AI chat in browser
  3. Switch between them
  4. Copy/paste commands and output
  5. Context lost between apps

Hoody setup:

  1. Open one URL (terminal + agent)
  2. Agent sees what you type
  3. Agent sees command output
  4. Suggest, execute, analyze—all in one window
  5. Context preserved automatically

The URL is the environment. Share the URL, you share the entire setup—terminal, agent, SSH connection, panel layout, everything.

Perfect for:

  • 🎓 Teaching - Instructor shares terminal+agent URL, students learn with AI assistance
  • 👥 Pair Programming - Both developers see terminal and agent suggestions
  • 🐛 Support - Share debugging session with AI already analyzing the problem
  • 📚 Documentation - Embed terminal+agent showing live examples

See Web Terminal UI → for all panel customization options.

Query your container’s state via HTTP:

Terminal window
# Get CPU/memory/disk stats
hoody terminal system resources
# List running processes sorted by CPU
hoody terminal processes list --sort cpu --limit 10
# Check what ports are listening
hoody terminal system ports
# View X11 displays
hoody terminal system display-info

All system information accessible via HTTP endpoints. No need to SSH in and run commands—just query the API.

Execute commands on OTHER servers through hoody-terminal:

POST Execute command on remote server via SSH bridge
/api/v1/terminal/execute?ssh_host=prod.example.com&ssh_user=admin&ssh_password=...
Click "Run" to execute the request

The container becomes an HTTP-to-SSH bridge. Your phone can now execute commands on your production servers via HTTP.


SSH Client (installed) → SSH Server (configured) → Shell (finally)

Problems:

  • SSH client required (not available on phones, tablets, watches)
  • Key management (private keys, known_hosts, permissions)
  • Port forwarding complexity
  • Not embeddable (can’t iframe SSH)
  • Not multiplayer (one session per connection)
  • AI can’t use (binary protocol)
Any HTTP Client → hoody-terminal URL → Shell (immediately)

Advantages:

  • ✅ Any device with browser works (phones, tablets, watches, TVs)
  • ✅ Zero key management (URL is the credential)
  • ✅ Naturally embeddable (<iframe src="terminal-url" />)
  • ✅ Multiplayer by default (share URL = instant collaboration)
  • ✅ AI-native (LLMs understand HTTP requests)
  • ✅ Observable (all HTTP requests logged)
  • ✅ MITM-able via hoody-exec (enhance any command automatically)

Because terminals are HTTP:

  1. Embed in Documentation

    <iframe src="https://demo-terminal.hoody.icu/?cmd=bHMgLWxh&readonly=true" />

    Live terminals in your docs showing actual command execution.

  2. Phone Executes Production Commands

    // From mobile browser
    await fetch(terminalUrl + '/execute', {
    method: 'POST',
    body: JSON.stringify({
    command: 'pm2 restart api',
    wait: true
    })
    });
  3. AI Orchestrates Infrastructure

    // AI agent deploys automatically
    const steps = [
    'git pull origin main',
    'npm install --production',
    'npm run build',
    'pm2 restart app'
    ];
    for (const cmd of steps) {
    await fetch(terminalUrl + '/execute?terminal_id=deploy', {
    method: 'POST',
    body: JSON.stringify({ command: cmd, wait: true })
    });
    }
  4. Cascading Execution

    // Terminal A executes command that triggers Terminal B
    await fetch(containerA_terminal + '/execute', {
    method: 'POST',
    body: JSON.stringify({
    command: `curl -X POST ${containerB_terminal}/execute -d '{"command":"npm test"}'`,
    wait: true
    })
    });

One-off commands with immediate results:

POST Check disk space with quick command
/api/v1/terminal/execute?terminal_id=1
Click "Run" to execute the request

Start task asynchronously, check later:

// Use terminal-3 for build tasks
const buildTerminalUrl = 'https://PROJECT_ID-CONTAINER_ID-terminal-3.SERVER_NAME.containers.hoody.icu';
const response = await fetch(buildTerminalUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({
command: 'npm run build',
wait: false,
timeout: 300
})
});
const { command_id } = await response.json();
// Poll for result
const checkStatus = async () => {
const result = await fetch(buildTerminalUrl + `/api/v1/terminal/result/${command_id}`)
.then(r => r.json());
if (result.status === 'completed') {
console.log(`Build finished in ${result.duration_ms}ms`);
console.log(result.stdout);
} else {
setTimeout(checkStatus, 2000);
}
};
checkStatus();

Check progress visually: Open the web terminal URL in your browser to watch the build running in real-time—same terminal-3 URL shows live output.

Mix workflows: Start command via API (from phone, script, CI/CD), then monitor progress visually in the web terminal from any browser.

Multi-step workflows—commands execute in sequence in the same session:

const deployCommands = [
'cd /app',
'git pull origin main',
'npm ci',
'npm run build',
'pm2 restart api',
'pm2 logs api --lines 50'
];
// Use terminal-2 for deployments
const deployUrl = 'https://PROJECT_ID-CONTAINER_ID-terminal-2.SERVER_NAME.containers.hoody.icu';
for (const command of deployCommands) {
const response = await fetch(deployUrl + '/api/v1/terminal/execute', {
method: 'POST',
body: JSON.stringify({ command, wait: true })
});
const result = await response.json();
if (result.exit_code !== 0) {
console.error(`Failed at: ${command}`);
console.error(result.stderr);
break;
}
console.log(`${command}`);
}
// All commands executed in terminal-2's session
// Working directory persisted across commands (cd /app stayed active)

Monitor container resources:

async function checkHealth(terminalUrl) {
const response = await fetch(terminalUrl + '/api/v1/system/resources');
const { cpu, memory, disk } = await response.json();
const alerts = [];
if (cpu.usage_percent > 80) {
alerts.push(`⚠️ CPU at ${cpu.usage_percent}%`);
}
if (memory.usage_percent > 85) {
alerts.push(`⚠️ Memory at ${memory.usage_percent}%`);
}
if (disk.usage_percent > 90) {
alerts.push(`⚠️ Disk at ${disk.usage_percent}%`);
}
return alerts;
}
// Run every 5 minutes
setInterval(() => {
const alerts = await checkHealth(terminalUrl);
if (alerts.length > 0) {
console.log('Health alerts:', alerts);
}
}, 300000);

Collaborative debugging: Team member encounters a bug. Instead of screen sharing:

  1. Share terminal URL: https://{project}-{container}-terminal-2.{server}.containers.hoody.icu
  2. Senior dev opens URL on phone while in meeting
  3. Types fix directly in shared session
  4. Bug resolved in 30 seconds

No screen share setup. No “can you see this?”. Just instant collaboration.

AI agents execute while you orchestrate:

// AI agent executes commands via the terminal HTTP API
const TERMINAL_URL = 'https://PROJECT_ID-CONTAINER_ID-terminal-1.SERVER_NAME.containers.hoody.icu';
const commands = [
'npm create vite@latest my-app -- --template react-ts',
'cd my-app && npm install',
'npm run dev'
];
for (const command of commands) {
const result = await fetch(`${TERMINAL_URL}/api/v1/terminal/execute`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command, wait: true })
});
console.log(await result.json());
}
// You described what you want built. AI executed it via HTTP.

No SSH setup needed for automation:

# Deploy from GitHub Actions, GitLab CI, any CI/CD
import requests
terminal_url = os.environ['HOODY_TERMINAL_URL']
response = requests.post(
f'{terminal_url}/api/v1/terminal/execute',
json={
'command': './deploy.sh production',
'terminal_id': 'ci',
'wait': False,
'timeout': 600
}
)
command_id = response.json()['command_id']
# Monitor deployment status via command_id

Solve issues together in real-time:

Support agent and customer both open:

https://{customer-container}-terminal-1.{server}.containers.hoody.icu

Both see the same terminal. Both can type. Agent fixes issue while customer watches. No more “type this command” followed by typing errors.

Admin your servers from anywhere:

Your phone’s browser → Terminal URL → Execute:

  • systemctl restart nginx
  • tail -f /var/log/app.log
  • docker ps
  • htop

Full Linux shell on your phone. Because it’s just a URL.

Embed working terminals in docs:

<!-- Your documentation -->
<p>To check system status, run:</p>
<iframe
src="https://demo-terminal.hoody.icu/?cmd=c3lzdGVtY3RsIHN0YXR1cyBuZ2lueA==&readonly=true"
height="400"
/>

Readers see ACTUAL execution, not just code blocks. Interactive learning.


Group related commands using the same terminal URL to maintain session context:

// ✅ Good - Same terminal URL maintains state
const deployUrl = 'https://PROJECT_ID-CONTAINER_ID-terminal-2.SERVER_NAME.containers.hoody.icu';
await fetch(deployUrl + '/execute', {
method: 'POST',
body: JSON.stringify({ command: 'cd /app', wait: true })
});
await fetch(deployUrl + '/execute', {
method: 'POST',
body: JSON.stringify({ command: 'npm install', wait: true })
});
// Runs in /app (working directory preserved)
// ❌ Bad - Different terminal URLs = different sessions
await fetch('...terminal-1.hoody.icu/execute', {
body: JSON.stringify({ command: 'cd /app' })
});
await fetch('...terminal-2.hoody.icu/execute', {
body: JSON.stringify({ command: 'npm install' })
});
// Runs in ~ (terminal-2 has its own separate session)

Remember: terminal-1, terminal-2, terminal-3 are all in the SAME container. They all see the same files, same services, same system. They just have independent shell sessions (separate working directories, separate command history).

Always use wait: false for commands that might take >3 seconds:

// ✅ Async for builds
await fetch('.../execute', {
body: JSON.stringify({
command: 'npm run build',
wait: false,
timeout: 300
})
});
// ❌ Sync would block for minutes

Prevent runaway processes with sensible timeouts:

{
command: 'npm install',
timeout: 300, // 5 minutes max
wait: false
}

Don’t assume success—verify exit codes:

const result = await execute({ command: 'npm test', wait: true });
if (result.exit_code !== 0) {
console.error('Tests failed:', result.stderr);
// Handle failure
} else {
console.log('Tests passed!');
}

Delete sessions when done to prevent resource leaks:

Terminal window
# List active sessions
GET /api/v1/terminal/sessions
# Delete old sessions
DELETE /api/v1/terminal/{terminal_id}

For debugging or exploration, open the web URL directly:

https://{project}-{container}-terminal-1.{server}.containers.hoody.icu

Better than API for: typing multiple commands, seeing colored output, scrolling history.

Query resource endpoints before intensive operations:

const { cpu, memory, disk } = await fetch('.../system/resources')
.then(r => r.json());
if (cpu.usage_percent > 90) {
console.warn('CPU at capacity - delay operation');
}

Yes, if the terminal is running as root or the user has sudo permissions. Configure the user when creating the container or use user parameter when starting the terminal session. For security, consider using separate containers for privileged vs unprivileged operations.

How do I execute commands on remote servers via SSH?

Section titled “How do I execute commands on remote servers via SSH?”

Include SSH parameters in the execute request: ssh_host, ssh_user, ssh_password (or ssh_key). The hoody-terminal service establishes the SSH connection and executes the command, returning results via HTTP. Your container becomes an HTTP-to-SSH gateway.

Can AI agents really use terminals effectively?

Section titled “Can AI agents really use terminals effectively?”

Absolutely. LLMs understand HTTP natively and can construct command execution requests without special training. They can handle multi-step workflows, check exit codes, parse output, and adapt based on results—all via standard HTTP calls. No SDK needed.

What happens to running commands if I close my browser?

Section titled “What happens to running commands if I close my browser?”

Commands continue executing on the server. Terminal sessions are server-side, not browser-side. Close your laptop, command keeps running. Check status later via the /result/{command_id} endpoint.

Yes! Use iframes to embed terminal URLs directly in your app. Common pattern: documentation with live command execution, dashboards showing server logs, customer portals with diagnostic terminals.

How many terminal sessions can one container have?

Section titled “How many terminal sessions can one container have?”

Theoretically unlimited. Practically: hundreds of concurrent sessions work fine. Each session consumes minimal RAM (~10MB). Use different terminal_id values for isolated sessions or same terminal_id for shared/multiplayer sessions.

Does the web terminal work on mobile devices?

Section titled “Does the web terminal work on mobile devices?”

Yes! Native browser support on iOS and Android. The UI adapts to touch input, includes an on-screen keyboard option, and supports mobile gestures. Full Linux terminal from your phone’s browser.

Can I capture screenshots of terminal sessions?

Section titled “Can I capture screenshots of terminal sessions?”

Yes, via GET /api/v1/terminal/screenshot?terminal_id=1&format=png. Returns visual snapshot as PNG/JPEG/GIF. Useful for documentation, tutorials, or monitoring dashboards showing live terminal state.

How do I get the complete command history for a session?

Section titled “How do I get the complete command history for a session?”

Use GET /api/v1/terminal/history/{terminal_id} to retrieve all commands executed, their exit codes, and execution times. Perfect for auditing, debugging, or understanding what changed in a session.


Check container is running:

Terminal window
curl "https://api.hoody.icu/api/v1/containers/{container_id}?runtime=true" \
-H "Authorization: Bearer $HOODY_TOKEN"

Verify status: "running" and runtime_info.terminals shows active sessions.

Start container if stopped:

Terminal window
curl -X POST "https://api.hoody.icu/api/v1/containers/{container_id}/start" \
-H "Authorization: Bearer $HOODY_TOKEN"

Possible causes:

  1. Container not running - Start it via Hoody API
  2. Wrong URL - Verify terminal-1 (not terminal1 or terminal)
  3. Proxy permissions - Check if terminal access is restricted
  4. Browser blocking iframe - Some browsers block cross-origin iframes

Quick test:

Terminal window
# Direct browser access (not iframe)
https://{project}-{container}-terminal-1.{server}.containers.hoody.icu

For long-running commands, use async mode:

// ✅ Correct - Async for npm install
{
command: 'npm install',
wait: false,
timeout: 300
}
// ❌ Wrong - Sync will timeout
{
command: 'npm install',
wait: true // Blocks for minutes!
}

Increase timeout if needed:

{ timeout: 600 } // 10 minutes for large builds

Check stderr, not just exit_code:

const result = await execute({ command: 'npm test', wait: true });
// Some commands exit 0 but write errors to stderr
if (result.stderr.includes('ERROR') || result.stderr.includes('FAIL')) {
console.error('Command had errors:', result.stderr);
}

Ensure using same terminal_id:

// ✅ Correct - Same session
terminal_id: "prod-session" // State persists
// ❌ Wrong - Different sessions
terminal_id: Math.random() // New session each time

Sessions are deleted on:

  • Explicit DELETE request
  • Container restart
  • Terminal service restart

Check SSH parameters:

{
command: 'ls',
ssh_host: 'prod.example.com',
ssh_port: 22, // Default if omitted
ssh_user: 'admin',
ssh_password: 'your-password' // Or ssh_key
}

Verify SSH connectivity:

Terminal window
# Test from container first
ssh admin@prod.example.com

Common issues:

  • Firewall blocking SSH port
  • Wrong credentials
  • Host or network unreachable from the container (host key verification is not an issue — the kit connects with StrictHostKeyChecking=no, so unknown hosts are accepted automatically)

Explore other interactive services:

Displays

Full desktop environments accessible via URL—run VS Code, browsers, any GUI application.

Explore Displays →

Browser

Chrome automation as REST API—control browsers via HTTP, scrape websites, run tests.

Explore Browser →

Exec

Transform any script into an HTTP endpoint—your code becomes an API automatically.

Explore Exec →

Master terminal workflows:


Your shell is a URL.
Execute from anywhere.
Collaborate in real-time.
AI-native by design.

This is how terminals work in the HTTP era.