roboticus-api 0.11.3

HTTP routes, WebSocket, auth, rate limiting, and dashboard for the Roboticus agent runtime
Documentation
  function toast(msg) {
    var c = document.getElementById('toasts'); if (!c) return;
    var el = document.createElement('div'); el.className = 'toast'; el.textContent = msg;
    c.appendChild(el); setTimeout(function() { el.remove(); }, 4000);
  }

  var AUTH_CHECKED = false;
  var AUTH_REQUIRED = false;
  var API_KEY = null;

  function authHeaders(extra) {
    var h = extra || { 'Accept': 'application/json' };
    if (API_KEY) h['Authorization'] = 'Bearer ' + API_KEY;
    return h;
  }

  function ensureAuth() {
    if (AUTH_CHECKED) return Promise.resolve();
    return fetch(BASE + '/api/health', { headers: { 'Accept': 'application/json' } })
      .then(function() {
        AUTH_CHECKED = true;
        AUTH_REQUIRED = false;
        return fetch(BASE + '/api/config', { headers: { 'Accept': 'application/json' } });
      })
      .then(function(r) {
        if (r.status === 401) {
          AUTH_REQUIRED = true;
          API_KEY = prompt('Enter API key:');
          if (API_KEY === '' || API_KEY === null) API_KEY = null;
        }
        AUTH_CHECKED = true;
      })
      .catch(function() { AUTH_CHECKED = true; });
  }

  function api(path, opts) {
    opts = opts || {};
    var url = BASE + path;
    var defaultHeaders = { 'Accept': 'application/json' };
    if (opts.body) defaultHeaders['Content-Type'] = 'application/json';
    var headers = opts.headers ? authHeaders(opts.headers) : authHeaders(defaultHeaders);
    return fetch(url, { headers: headers, method: opts.method || 'GET', body: opts.body }).then(function(r) {
      if (!r.ok) return r.text().then(function(t) {
        // RFC 9457: parse problem+json detail field; fall back to raw text
        var msg = r.statusText; var pd = null;
        try { pd = JSON.parse(t); msg = pd.detail || pd.title || pd.error || t; } catch(_) { msg = t || msg; }
        var err = new Error(msg);
        err.status = r.status;
        err.problem = pd;
        throw err;
      });
      var ct = r.headers.get('content-type');
      if (ct && ct.indexOf('application/json') !== -1) return r.json();
      return r.text();
    });
  }

  function fetchWithFallback(url, fallback, label) {
    return api(url)
      .then(function(data) { return { ok: true, data: data, label: label }; })
      .catch(function(err) {
        return {
          ok: false,
          data: fallback,
          label: label,
          error: (err && err.message) ? err.message : String(err || 'unknown error')
        };
      });
  }

  function applySidebarIdentity(agentStatus, health) {
    var agent = agentStatus || {};
    var healthData = health || {};
    if (agent.name) setAgentDisplayName(agent.name);
    else if (healthData.agent) setAgentDisplayName(healthData.agent);
    var state = String(agent.state || healthData.status || 'unknown').toLowerCase();
    var dotEl = document.getElementById('agent-dot');
    if (dotEl) {
      dotEl.className = 'status-dot' + (state === 'running' ? '' : state === 'sleeping' ? ' warning' : ' error');
    }
    var stateEl = document.getElementById('agent-state');
    if (stateEl) stateEl.textContent = state || 'unknown';
    var ve = document.getElementById('version');
    if (ve) ve.textContent = healthData.version || 'unknown';
  }

  function refreshSidebarIdentity() {
    return Promise.all([
      api('/api/agent/status').catch(function() { return {}; }),
      api('/api/health').catch(function() { return {}; })
    ]).then(function(arr) {
      applySidebarIdentity(arr[0], arr[1]);
    }).catch(function() {});
  }