InteractiveBox attaches your terminal directly to a shell running inside a VM — like SSH into a sandbox. Use it for interactive tools, REPLs, debugging sessions, or any workflow where you need a live terminal.
What you’ll build
A script that:
- Launches an InteractiveBox with a shell
- Runs interactive commands in a TTY session
- Connects to a Python REPL inside the VM
Prerequisites
npm install @boxlite-ai/boxlite
Requires Node.js 18+.
Step 1: Start a shell session
Launch an InteractiveBox and drop into a shell. The session stays open until you type exit or the shell terminates.
import asyncio
from boxlite import InteractiveBox
async def main():
async with InteractiveBox(image="alpine:latest") as box:
# You're now in an interactive shell inside the VM
# Type commands, see output in real-time
# Type "exit" to close the session
await box.wait()
print("Shell session ended")
if __name__ == "__main__":
asyncio.run(main())
python shell.py
# You'll see an Alpine Linux shell prompt
# Try: ls, whoami, cat /etc/os-release
# Type "exit" to quit
import { InteractiveBox } from '@boxlite-ai/boxlite';
async function main() {
const box = new InteractiveBox({ image: 'alpine:latest' });
try {
await box.start();
// You're now in an interactive shell inside the VM
// Type commands, see output in real-time
// Type "exit" to close the session
await box.wait();
} finally {
await box.stop();
}
console.log('Shell session ended');
}
main();
node shell.js
# You'll see an Alpine Linux shell prompt
# Try: ls, whoami, cat /etc/os-release
# Type "exit" to quit
What’s happening:
- InteractiveBox creates a VM with Alpine Linux and attaches your terminal to
/bin/sh
- Your keystrokes go directly into the VM’s shell, and output streams back in real-time
wait() blocks until the shell process exits (when you type exit or press Ctrl+D)
Step 2: Choose your shell
You can run any shell or REPL available in the image. Specify it with the shell parameter.
import asyncio
from boxlite import InteractiveBox
async def main():
# Use bash (available in Ubuntu, not Alpine)
async with InteractiveBox(
image="ubuntu:22.04",
shell="/bin/bash"
) as box:
await box.wait()
if __name__ == "__main__":
asyncio.run(main())
import asyncio
from boxlite import InteractiveBox
async def main():
# Drop straight into a Python REPL
async with InteractiveBox(
image="python:slim",
shell="/usr/bin/python3"
) as box:
await box.wait()
if __name__ == "__main__":
asyncio.run(main())
import { InteractiveBox } from '@boxlite-ai/boxlite';
async function main() {
// Use bash (available in Ubuntu, not Alpine)
const box = new InteractiveBox({
image: 'ubuntu:22.04',
shell: '/bin/bash',
});
try {
await box.start();
await box.wait();
} finally {
await box.stop();
}
}
main();
import { InteractiveBox } from '@boxlite-ai/boxlite';
async function main() {
// Drop straight into a Python REPL
const box = new InteractiveBox({
image: 'python:slim',
shell: '/usr/bin/python3',
});
try {
await box.start();
await box.wait();
} finally {
await box.stop();
}
}
main();
Make sure the shell you specify actually exists in the image. Alpine uses /bin/sh (busybox), not /bin/bash. Ubuntu has both.
Step 3: Choose the right TTY mode
The tty parameter controls how InteractiveBox handles terminal I/O. The right choice depends on how you’re using the box.
| Value | Python | Node.js | Behavior | Use when… |
|---|
| Auto-detect | tty=None (default) | tty: undefined (default) | Checks sys.stdin.isatty() / process.stdin.isTTY | You’re not sure — this works in most cases |
| Force TTY | tty=True | tty: true | Always enables PTY with I/O forwarding | You’re running from a terminal and want interactive control |
| No I/O | tty=False | tty: false | Disables I/O forwarding entirely | You’re controlling the box programmatically with exec() |
import asyncio
from boxlite import InteractiveBox
async def main():
# Force TTY mode — always interactive
async with InteractiveBox(
image="alpine:latest",
tty=True
) as box:
await box.wait()
if __name__ == "__main__":
asyncio.run(main())
import asyncio
from boxlite import InteractiveBox
async def main():
# No I/O forwarding — use exec() to send commands
async with InteractiveBox(
image="alpine:latest",
tty=False
) as box:
# InteractiveBox extends SimpleBox, so exec() works
result = await box.exec("cat", "/etc/os-release")
print(result.stdout)
if __name__ == "__main__":
asyncio.run(main())
import { InteractiveBox } from '@boxlite-ai/boxlite';
async function main() {
// Force TTY mode — always interactive
const box = new InteractiveBox({
image: 'alpine:latest',
tty: true,
});
try {
await box.start();
await box.wait();
} finally {
await box.stop();
}
}
main();
import { InteractiveBox } from '@boxlite-ai/boxlite';
async function main() {
// No I/O forwarding — use exec() to send commands
const box = new InteractiveBox({
image: 'alpine:latest',
tty: false,
});
try {
await box.start();
// InteractiveBox extends SimpleBox, so exec() works
const result = await box.exec('cat', '/etc/os-release');
console.log(result.stdout);
} finally {
await box.stop();
}
}
main();
InteractiveBox extends SimpleBox, so you always have access to exec() for running one-off commands. The tty parameter only controls whether the shell session itself gets I/O forwarding — exec() works regardless.
What’s next?