PocketClaw runs headless — no screen, no keyboard, just a phone connected to WiFi. The only way to check if the gateway was running, if WiFi was up, or how much RAM was left was to SSH in and run commands manually. For a device that should run autonomously, this is unacceptable.
The solution was to inject custom HTTP routes into OpenClaw's existing web server. Since OpenClaw's source code shouldn't be modified (it's npm-installed and would be overwritten on updates), the routes are injected via hijack.js by monkey-patching Node's http.Server.prototype.listen.
The HTTP intercept works by patching the listen method to wrap the request handler:
// In hijack.js — intercept http.Server.prototype.listen
const _origListen = require('http').Server.prototype.listen;
require('http').Server.prototype.listen = function() {
const server = this;
const _origEmit = server.emit;
server.emit = function(event, req, res) {
if (event === 'request') {
// Intercept custom routes before OpenClaw handles them
if (req.url === '/dashboard' || req.url === '/dashboard/') {
return serveDashboard(req, res);
}
if (req.url === '/api/status') {
return serveStatus(req, res);
}
}
return _origEmit.apply(this, arguments);
};
return _origListen.apply(this, arguments);
};The routes serve data from /proc filesystem:
function serveStatus(req, res) {
const meminfo = require('fs').readFileSync('/proc/meminfo', 'utf8');
const uptime = require('fs').readFileSync('/proc/uptime', 'utf8');
const data = {
ram: parseMeminfo(meminfo),
uptime: parseFloat(uptime.split(' ')[0]),
wifi: checkWifi(),
gateway: 'running',
timestamp: Date.now()
};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
}
function serveDashboard(req, res) {
// Serve inline HTML with CRT green theme
// Auto-refreshes every 3 seconds via fetch('/api/status')
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(dashboardHTML);
}# Test status API:
curl -s http://localhost:9000/api/status | python -m json.tool
# Expected: JSON with ram, uptime, wifi, gateway fields
# Test dashboard HTML:
curl -s http://localhost:9000/dashboard | head -5
# Expected: <!DOCTYPE html> with CRT styling
# Verify OpenClaw's own routes still work:
curl -s http://localhost:9000/api/v1/health
# Expected: OpenClaw health response (not intercepted)req.url BEFORE passing to OpenClaw. If OpenClaw has a conflicting route, the intercept wins-r (before everything), all servers are caughtclients3.google.com/generate_204 — the same endpoint Android uses for captive portal detection| Metric | Before | After |
|---|---|---|
| Status visibility | SSH only | Browser (any device) |
| Status check time | 30s (SSH + commands) | Instant (refresh) |
| Extra processes | N/A | 0 (same Node.js process) |
| Extra dependencies | N/A | 0 (built into hijack.js) |