{
  "schema_version": 1,
  "type": "skill",
  "slug": "container-conduit",
  "title": "Container Conduit",
  "brief": "WebSocket bridge connecting a main Gallia container to satellite Adom containers — remote command execution, persistent shell sessions, file transfer, system monitoring, and an interactive terminal da",
  "version": "1.0.1",
  "tags": [],
  "license": "MIT",
  "source_path": "SKILL.md",
  "readme": "# Container Conduit\n\nContainer Conduit is a WebSocket bridge that connects a main Gallia container to one or more satellite Adom containers. It gives Claude Code **full remote control** over satellite containers — execute commands, transfer files, monitor system health, and open persistent interactive shell sessions — all without leaving your main workspace.\n\nThe Gallia Viewer dashboard provides a browser-based interactive terminal (xterm.js) with full TTY support for curses applications like `vim`, `nano`, and `htop`.\n\n---\n\n## Architecture\n\n```\nMain Container (Gallia)                  Satellite Container(s)\n  server.js (WS port 8800)  ←──WS──→  agent.js\n  HTTP API  (port 8801)                   ↑\n       ↑                              install.sh (bootstrap)\n  MCP tools (mcp/server.js)\n       ↑\n  Claude Code / Dashboard\n```\n\n| Component | Role |\n|-----------|------|\n| **server.js** | WebSocket server (port 8800) + HTTP API (port 8801) — accepts agent connections, relays commands, serves the dashboard |\n| **agent.js** | Lightweight satellite agent — handles exec, file ops, system status, and PTY sessions |\n| **mcp/server.js** | MCP stdio server — exposes all Container Conduit tools to Claude Code |\n| **dashboard.html** | Interactive management dashboard in Gallia Viewer with Shell, Terminal, and Prompts tabs |\n\n---\n\n## MCP Tools\n\nAll tools are prefixed `container_` and available to Claude Code via MCP:\n\n### Command Execution\n\n| Tool | Description |\n|------|-------------|\n| `container_exec` | Execute a shell command on a specific container (one-shot) |\n| `container_exec_all` | Execute a command on **all** connected containers simultaneously |\n\n### Persistent Shell Sessions\n\n| Tool | Description |\n|------|-------------|\n| `container_shell_start` | Start a persistent interactive shell session (returns `sessionId`) |\n| `container_shell_exec` | Run a command in a persistent shell — **state persists across calls** (cwd, env vars, aliases) |\n| `container_shell_stop` | Stop a persistent shell session |\n\n### File Operations\n\n| Tool | Description |\n|------|-------------|\n| `container_file_read` | Read a file from a remote container |\n| `container_file_write` | Write a file to a remote container |\n| `container_file_list` | List directory contents on a remote container |\n\n### Container Management\n\n| Tool | Description |\n|------|-------------|\n| `container_list` | List all connected satellite containers |\n| `container_status` | Get system status (memory, disk, uptime) of a container |\n| `container_kick` | Disconnect a container (it will auto-reconnect) |\n| `container_create` | Create a new Adom container (repo + curium + container) |\n| `container_install_cmd` | Generate the one-liner install command for a satellite |\n| `container_dashboard` | Display the Conduit dashboard in Gallia Viewer |\n\n---\n\n## Persistent Shell Sessions\n\nThe `container_shell_*` tools give Claude Code a **persistent interactive shell** on any satellite container. Unlike `container_exec` (which spawns a new process per call), shell sessions preserve state across calls:\n\n```\ncontainer_shell_start(\"service-test1\")        → sessionId\ncontainer_shell_exec(sessionId, \"cd /tmp\")\ncontainer_shell_exec(sessionId, \"pwd\")        → /tmp         (cwd persists!)\ncontainer_shell_exec(sessionId, \"export FOO=bar\")\ncontainer_shell_exec(sessionId, \"echo $FOO\")  → bar          (env vars persist!)\ncontainer_shell_exec(sessionId, \"whoami\")     → adom\ncontainer_shell_stop(sessionId)\n```\n\n### How It Works\n\n1. `shell_start` spawns a PTY on the satellite agent (real terminal via `script -qfc 'bash -i'`)\n2. `shell_exec` wraps each command with SOH-byte sentinel markers, sends it as PTY input, and waits for the output between the markers\n3. The server buffers PTY output and uses pattern matching to detect command completion and extract the result\n4. Sessions auto-cleanup after **30 minutes** of inactivity\n\nThis approach gives Claude Code a true interactive shell experience — including `sudo`, package management, process control, and anything else you'd do in a terminal — all through the MCP tool interface.\n\n---\n\n## Dashboard (Gallia Viewer)\n\nOpen the interactive management dashboard:\n\n```\ncontainer_dashboard()\n```\n\nThe dashboard has three tabs:\n\n- **Shell** — Full interactive xterm.js terminal connected to a remote container via PTY WebSocket relay. Supports curses-based applications (`nano`, `vim`, `htop`, etc.)\n- **Terminal** — Simple command execution with output display (non-interactive)\n- **Claude Prompts** — Pre-built prompts for common container management tasks\n\nThe left panel shows all connected containers with name, hostname, IP, capabilities, and connection age.\n\n---\n\n## Installing the Agent\n\n### One-liner install (direct network)\n\n```bash\ncurl -sL http://<MAIN_IP>:8800/agent/install | CC_NAME=my-service bash\n```\n\n### Proxy install (cross-container via Coder proxy)\n\n```bash\ncurl -sL https://coder.<SLUG>.containers.adom.inc/proxy/8800/agent/install | \\\n  CC_BASE_URL=https://coder.<SLUG>.containers.adom.inc/proxy/8800 \\\n  CC_NAME=my-service sudo -E bash\n```\n\nThe install script:\n1. Creates `/opt/container-conduit/` owned by `adom`\n2. Downloads `agent.js` and `package.json` from the main container\n3. Runs `npm install`\n4. Writes `config.json` with WebSocket URL and token\n5. Starts the agent from `~/project`\n6. Adds a cron `@reboot` entry for auto-start\n\n### Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `CC_HOST` | *(required)* | Main container IP |\n| `CC_PORT` | `8800` | Main container WS port |\n| `CC_TOKEN` | `adom-container-token-2025` | Authentication token |\n| `CC_NAME` | `$(hostname)` | Container name shown in dashboard |\n| `CC_BASE_URL` | `http://$CC_HOST:$CC_PORT` | Override download URL (for proxy setups) |\n| `CC_WS_URL` | *auto-detected* | Override WebSocket URL (e.g. `wss://...`) |\n\n---\n\n## Auto-Start\n\nContainer Conduit registers with the Gallia auto-start system via `service.json`. Both `gallia-start.sh` and `gallia-watchdog.sh` auto-discover this manifest — the server starts on boot and restarts automatically if it crashes.\n\n---\n\n## Troubleshooting\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| Agent can't connect | Internal hostname not routable | Use `CC_BASE_URL` with the Coder proxy URL |\n| `\\r': command not found` during install | CRLF line endings | Run `sed -i 's/\\r$//' /opt/container-conduit/agent.js` |\n| Permission denied on `/opt/container-conduit` | Script not run with sudo | Use `sudo -E bash` to preserve env vars |\n| Dashboard shows 0 containers | API URL detection fails | Restart MCP server (it injects `__CONDUIT_API_BASE__`) |\n| PTY timeout on Connect | Agent running old code | Restart agent with updated `agent.js` |\n| Shell tab WebSocket error | Port 8801 not reachable via proxy | Ensure `server.js` binds 8801 to `0.0.0.0` |",
  "author": {
    "name": "Kyle Bergstedt",
    "email": "[email protected]"
  },
  "visibility": {
    "public": true
  },
  "hero": null,
  "sample_prompts": [],
  "discovery_triggers": [],
  "discovery_pitch": null,
  "metadata": {},
  "created_at": "2026-05-28T05:30:17.825Z",
  "updated_at": "2026-05-28T05:30:17.825Z",
  "sub_skills": [],
  "parent_app": null
}