{
  "openrpc": "1.2.6",
  "info": {
    "title": "Hero Router",
    "version": "0.2.0",
    "description": "Hero RPC service router, documentation generator, and MCP gateway. Discovers OpenRPC services on Unix sockets, caches their specs, generates docs, and exposes each healthy service as an MCP-compatible endpoint for Claude Code CLI."
  },
  "methods": [
    {
      "name": "router.services",
      "summary": "List all discovered services with status, methods count, and source info",
      "params": [],
      "result": {
        "name": "services",
        "schema": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "service_id": { "type": "string", "description": "Stable UUID v5 for this service" },
              "title": { "type": "string", "description": "Human-readable service name" },
              "status": { "type": "string", "enum": ["healthy", "unhealthy", "inactive", "non_rpc", "unknown"] },
              "methods_count": { "type": "integer", "description": "Number of RPC methods in the spec" },
              "source": { "type": "string", "description": "Socket path, file path, or URL" },
              "source_kind": { "type": "string", "enum": ["socket", "file", "url"] }
            }
          }
        }
      }
    },
    {
      "name": "router.service",
      "summary": "Get full detail for a single service by its UUID",
      "params": [{ "name": "service_id", "required": true, "schema": { "type": "string" }, "description": "The service UUID" }],
      "result": { "name": "service", "schema": { "type": "object" } }
    },
    {
      "name": "router.scan",
      "summary": "Trigger an immediate re-scan of the socket directory and manual sources",
      "params": [],
      "result": {
        "name": "result",
        "schema": {
          "type": "object",
          "properties": {
            "discovered": { "type": "integer", "description": "Number of services found in this scan" }
          }
        }
      }
    },
    {
      "name": "router.openrpc",
      "summary": "Get the cached OpenRPC JSON spec for a service",
      "params": [{ "name": "service_id", "required": true, "schema": { "type": "string" } }],
      "result": { "name": "spec", "schema": { "type": "object", "description": "The full OpenRPC specification document" } }
    },
    {
      "name": "router.markdown",
      "summary": "Generate Markdown documentation from a service's OpenRPC spec",
      "params": [{ "name": "service_id", "required": true, "schema": { "type": "string" } }],
      "result": { "name": "markdown", "schema": { "type": "string", "description": "Markdown document with method signatures, params, and examples" } }
    },
    {
      "name": "router.html",
      "summary": "Generate standalone Bootstrap 5 HTML documentation for a service",
      "params": [{ "name": "service_id", "required": true, "schema": { "type": "string" } }],
      "result": { "name": "html", "schema": { "type": "string", "description": "Self-contained HTML page with dark theme" } }
    },
    {
      "name": "router.status",
      "summary": "Get router health, uptime, and service discovery summary",
      "params": [],
      "result": {
        "name": "status",
        "schema": {
          "type": "object",
          "properties": {
            "uptime_secs": { "type": "integer" },
            "total_services": { "type": "integer" },
            "healthy": { "type": "integer" },
            "inactive": { "type": "integer" },
            "last_scan_at": { "type": "string", "format": "date-time" }
          }
        }
      }
    },
    {
      "name": "router.add",
      "summary": "Register a manual OpenRPC spec by file path or URL. It will be fetched, validated, and added to the sidebar.",
      "params": [
        { "name": "source", "required": true, "schema": { "type": "string" }, "description": "Socket path, local file path, or HTTP(S) URL" },
        { "name": "name", "required": false, "schema": { "type": "string" }, "description": "Optional display name override" }
      ],
      "result": { "name": "result", "schema": { "type": "object" } }
    },
    {
      "name": "router.remove",
      "summary": "Unregister a manually added spec by service ID",
      "params": [{ "name": "service_id", "required": true, "schema": { "type": "string" } }],
      "result": { "name": "result", "schema": { "type": "object" } }
    },
    {
      "name": "router.mcp_logs",
      "summary": "Get recent MCP gateway request/response logs for debugging. Returns newest first.",
      "params": [
        { "name": "limit", "required": false, "schema": { "type": "integer", "default": 100 }, "description": "Max entries to return (default 100, max 500)" }
      ],
      "result": {
        "name": "logs",
        "schema": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": { "type": "integer", "description": "Log entry ID" },
              "timestamp": { "type": "string", "format": "date-time" },
              "source_ip": { "type": "string", "description": "IP address of the MCP client" },
              "service_name": { "type": "string", "description": "Target service name from URL path" },
              "mcp_method": { "type": "string", "description": "MCP method: initialize, tools/list, tools/call, etc." },
              "tool_name": { "type": "string", "description": "Tool name (for tools/call only)" },
              "request_body": { "type": "string", "description": "Raw JSON-RPC request" },
              "backend_request": { "type": "string", "description": "JSON-RPC sent to backend service (after param conversion)" },
              "response_body": { "type": "string", "description": "Raw JSON-RPC response" },
              "duration_ms": { "type": "integer", "description": "Request processing time in milliseconds" },
              "is_error": { "type": "boolean" }
            }
          }
        }
      }
    },
    {
      "name": "router.mcp_logs_clear",
      "summary": "Clear all MCP gateway logs",
      "params": [],
      "result": { "name": "result", "schema": { "type": "object" } }
    },
    {
      "name": "admin.health",
      "summary": "Simple health check — returns ok if the router is running",
      "params": [],
      "result": { "name": "health", "schema": { "type": "object", "properties": { "status": { "type": "string" } } } }
    },
    {
      "name": "router.contexts",
      "summary": "List all discovered service contexts (unique context labels from service entries)",
      "params": [],
      "result": {
        "name": "contexts",
        "schema": {
          "type": "array",
          "items": { "type": "string" },
          "description": "List of unique context strings across all discovered services"
        }
      }
    },
    {
      "name": "router.kill",
      "summary": "Delete the socket file for a socket-based service and remove it from the cache. Use to forcibly evict a dead service whose socket was not cleaned up.",
      "params": [
        { "name": "service_id", "required": true, "schema": { "type": "string" }, "description": "The service UUID (must be a socket-based service)" }
      ],
      "result": {
        "name": "result",
        "schema": { "type": "object", "properties": { "killed": { "type": "boolean" } } }
      }
    },
    {
      "name": "router.clean",
      "summary": "Scan the socket directory and delete stale socket files (sockets with no listening process). Returns the number of files deleted.",
      "params": [],
      "result": {
        "name": "result",
        "schema": { "type": "object", "properties": { "deleted": { "type": "integer", "description": "Number of stale socket files removed" } } }
      }
    },
    {
      "name": "router.check",
      "summary": "Trigger an immediate background re-scan (fire-and-forget, returns instantly). Equivalent to router.scan but does not wait for the scan to complete.",
      "params": [],
      "result": {
        "name": "result",
        "schema": { "type": "object", "properties": { "triggered": { "type": "boolean" } } }
      }
    },
    {
      "name": "rpc.health",
      "summary": "Lightweight health probe — returns ok if the JSON-RPC endpoint is responsive",
      "params": [],
      "result": {
        "name": "health",
        "schema": {
          "type": "object",
          "properties": {
            "status": { "type": "string", "description": "Always \"ok\" when the service is running" }
          }
        }
      }
    },
    {
      "name": "router.agent.run",
      "summary": "Run the self-contained AI agent pipeline (hero_logic 'service_agent_v3' workflow). Dynamically discovers services, compiles Python stubs, generates and executes a script, and returns a summarized answer. Omit 'service' for multi-service mode; set it to scope to a single service.",
      "params": [
        { "name": "prompt", "required": true, "schema": { "type": "string" }, "description": "Natural language description of what to do" },
        { "name": "service", "required": false, "schema": { "type": "string" }, "description": "Optional service name to scope the agent to a single service (e.g. 'hero_books'). Omit for multi-service mode." },
        { "name": "max_retries", "required": false, "schema": { "type": "integer", "default": 3 }, "description": "Max retry attempts on script failure" },
        { "name": "model", "required": false, "schema": { "type": "string" }, "description": "LLM model id (defaults to env HERO_LLM_MODEL or gpt-4o-mini)" }
      ],
      "result": {
        "name": "agent_result",
        "schema": {
          "type": "object",
          "properties": {
            "success": { "type": "boolean" },
            "answer": { "type": "string", "description": "Human-friendly answer summarizing the result" },
            "result": { "type": "string", "description": "Raw stdout from the executed script" },
            "script": { "type": "string", "description": "The generated Python script" },
            "attempts": { "type": "integer" },
            "duration_ms": { "type": "integer" },
            "error": { "type": "string" },
            "selected_services": { "type": "array", "items": { "type": "string" } },
            "play_sid": { "type": "string", "description": "hero_logic play SID — pass to router.agent.play for the execution trace" }
          }
        }
      }
    },
    {
      "name": "router.agent.start",
      "summary": "Start an AI agent pipeline asynchronously. Returns immediately with a play_sid; use router.agent.play to poll for results. Useful when the agent may take longer than a typical RPC timeout.",
      "params": [
        { "name": "prompt", "required": true, "schema": { "type": "string" }, "description": "Natural language description of what to do" },
        { "name": "service", "required": false, "schema": { "type": "string" }, "description": "Optional service name to scope the agent to a single service" },
        { "name": "model", "required": false, "schema": { "type": "string" }, "description": "LLM model id (defaults to env HERO_LLM_MODEL)" }
      ],
      "result": {
        "name": "result",
        "schema": {
          "type": "object",
          "properties": {
            "play_sid": { "type": "string", "description": "hero_logic play SID — pass to router.agent.play to poll status and retrieve the result" }
          },
          "required": ["play_sid"]
        }
      }
    },
    {
      "name": "router.agent.play",
      "summary": "Fetch the full hero_logic Play JSON (node-by-node execution trace) for a given play_sid returned by router.agent.run.",
      "params": [
        { "name": "play_sid", "required": true, "schema": { "type": "string" }, "description": "The play SID from router.agent.run" }
      ],
      "result": { "name": "play", "schema": { "type": "object", "description": "Full Play object from hero_logic including node_runs, status, input/output_data, token counts" } }
    },
    {
      "name": "router.agent.models",
      "summary": "List LLM models available via hero_aibroker for use with router.agent.run.",
      "params": [],
      "result": { "name": "models", "schema": { "type": "array", "description": "Array of model descriptors (ids and metadata)" } }
    },
    {
      "name": "router.python_client",
      "summary": "Get the auto-generated Python client source for a service. The client uses HTTP-over-Unix-socket with stdlib only.",
      "params": [
        { "name": "service", "required": true, "schema": { "type": "string" }, "description": "Service name or title (e.g. 'hero_books')" }
      ],
      "result": { "name": "client_source", "schema": { "type": "string", "description": "Python source code for the typed RPC client" } }
    },
    {
      "name": "router.python_interface",
      "summary": "Get the lightweight Python interface stub for a service. Designed for LLM context windows — one comment line per method.",
      "params": [
        { "name": "service", "required": true, "schema": { "type": "string" }, "description": "Service name or title (e.g. 'hero_books')" }
      ],
      "result": { "name": "interface_source", "schema": { "type": "string", "description": "Python interface stub with method signatures" } }
    },
    {
      "name": "terminal.list",
      "summary": "List all running router-owned terminal sessions",
      "params": [],
      "result": {
        "name": "sessions",
        "schema": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name":       { "type": "string",  "description": "Session name — stable identifier, also used in the browser URL hash" },
              "job_id":     { "type": "integer", "description": "hero_proc job ID (for debugging)" },
              "created_at": { "type": "string",  "format": "date-time" },
              "url":        { "type": "string",  "description": "Browser deep-link, e.g. http://[...]:9989/terminal#session=codescalers" },
              "shell":      { "type": "string",  "enum": ["nu", "bash", "tmux"], "description": "Shell type for this session" }
            },
            "required": ["name", "job_id", "created_at", "url"]
          }
        }
      }
    },
    {
      "name": "terminal.create",
      "summary": "Create a new named terminal session. Accepts an optional shell param (nu, bash, or tmux; defaults to nu). Returns the session with a browser URL the caller can link to directly.",
      "params": [
        { "name": "name", "required": true, "schema": { "type": "string" }, "description": "Session name — alphanumeric, hyphens, underscores only. Used as the URL hash fragment, e.g. 'codescalers'." },
        {
          "name": "shell",
          "required": false,
          "schema": {
            "type": "string",
            "enum": ["nu", "bash", "tmux"],
            "default": "nu",
            "description": "Shell to launch. Defaults to nu (nushell)."
          },
          "description": "Shell type for the new session. Defaults to nu."
        }
      ],
      "result": {
        "name": "session",
        "schema": {
          "type": "object",
          "properties": {
            "name":       { "type": "string",  "description": "Session name" },
            "job_id":     { "type": "integer", "description": "hero_proc job ID" },
            "created_at": { "type": "string",  "format": "date-time" },
            "url":        { "type": "string",  "description": "Full browser URL, e.g. http://[...]:9989/terminal#session=codescalers" },
            "shell":      { "type": "string",  "enum": ["nu", "bash", "tmux"], "description": "Shell type for this session" }
          },
          "required": ["name", "job_id", "created_at", "url"]
        }
      }
    },
    {
      "name": "terminal.get",
      "summary": "Get a terminal session by name. Returns the session regardless of phase (running or stopped).",
      "params": [
        { "name": "name", "required": true, "schema": { "type": "string" }, "description": "Session name" }
      ],
      "result": {
        "name": "session",
        "schema": {
          "type": "object",
          "properties": {
            "name":       { "type": "string" },
            "job_id":     { "type": "integer" },
            "created_at": { "type": "string", "format": "date-time" },
            "url":        { "type": "string", "description": "Browser deep-link URL for this session" }
          },
          "required": ["name", "job_id", "created_at", "url"]
        }
      }
    },
    {
      "name": "terminal.delete",
      "summary": "Cancel and delete a terminal session by name.",
      "params": [
        { "name": "name", "required": true, "schema": { "type": "string" }, "description": "Session name to delete" }
      ],
      "result": {
        "name": "result",
        "schema": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean", "description": "Always true on success" }
          }
        }
      }
    },
    {
      "name": "terminal.reply",
      "summary": "Inject a terminal-emulator reply (e.g. the CPR answer to ESC[6n) into the PTY stdin of the named session. The bytes are delivered to the program running inside the PTY and are NOT rendered as visible output. Use when a non-browser client drives a session and needs to answer a query issued by the program inside the PTY; browser clients answer such queries automatically through xterm.js.",
      "params": [
        { "name": "name",     "required": true, "schema": { "type": "string" }, "description": "Session name (must be currently attached over the PTY WebSocket)" },
        { "name": "data_b64", "required": true, "schema": { "type": "string" }, "description": "Reply bytes, base64-encoded" }
      ],
      "result": {
        "name": "result",
        "schema": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean", "description": "True when bytes were delivered to the PTY" }
          }
        }
      }
    },
    {
      "name": "terminal.write",
      "summary": "Send keystrokes / arbitrary bytes to the PTY stdin of an attached session. Equivalent to typing in the browser terminal. Requires the session to be currently attached over the PTY WebSocket.",
      "params": [
        { "name": "name",     "required": true, "schema": { "type": "string" }, "description": "Session name" },
        { "name": "data_b64", "required": true, "schema": { "type": "string" }, "description": "Input bytes, base64-encoded" }
      ],
      "result": {
        "name": "result",
        "schema": {
          "type": "object",
          "properties": {
            "ok":    { "type": "boolean" },
            "bytes": { "type": "integer", "description": "Number of bytes written" }
          }
        }
      }
    },
    {
      "name": "terminal.resize",
      "summary": "Resize the PTY of an attached session. Mirrors the resize event xterm.js sends when the browser viewport changes. Requires the session to be currently attached.",
      "params": [
        { "name": "name", "required": true, "schema": { "type": "string"  }, "description": "Session name" },
        { "name": "cols", "required": true, "schema": { "type": "integer" }, "description": "New column count" },
        { "name": "rows", "required": true, "schema": { "type": "integer" }, "description": "New row count" }
      ],
      "result": {
        "name": "result",
        "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } }
      }
    },
    {
      "name": "router.access.list",
      "summary": "Return the current admin-UI IP whitelist (state, allowed IPs, hero_proc reachability).",
      "params": [],
      "result": {
        "name": "access",
        "schema": {
          "type": "object",
          "properties": {
            "state":                { "type": "string", "enum": ["open", "enforced", "hero_proc_unreachable"] },
            "entries":              { "type": "array", "items": { "type": "string" } },
            "hero_proc_available":  { "type": "boolean" },
            "secret_key":           { "type": "string" },
            "context":              { "type": "string" }
          }
        }
      }
    },
    {
      "name": "router.access.add",
      "summary": "Add an IP to the admin-UI whitelist (persisted in hero_proc as the ADMIN_SECRETS secret).",
      "params": [
        { "name": "ip", "required": true, "schema": { "type": "string" }, "description": "IPv4 or IPv6 address" }
      ],
      "result": { "name": "result", "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } }
    },
    {
      "name": "router.access.remove",
      "summary": "Remove an IP from the admin-UI whitelist.",
      "params": [
        { "name": "ip", "required": true, "schema": { "type": "string" }, "description": "IPv4 or IPv6 address" }
      ],
      "result": { "name": "result", "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } }
    },
    {
      "name": "router.access.clear",
      "summary": "Delete the ADMIN_SECRETS secret entirely (open access).",
      "params": [],
      "result": { "name": "result", "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } }
    },
    {
      "name": "router.ssh_keys.list",
      "summary": "List entries from ~/.ssh/authorized_keys.",
      "params": [],
      "result": {
        "name": "keys",
        "schema": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id":          { "type": "string", "description": "16-char hex id of the line" },
              "algorithm":   { "type": "string" },
              "fingerprint": { "type": "string" },
              "comment":     { "type": "string" },
              "line":        { "type": "string" }
            }
          }
        }
      }
    },
    {
      "name": "router.ssh_keys.add",
      "summary": "Append an authorized_keys line. Fails if the key is already present or malformed.",
      "params": [
        { "name": "key", "required": true, "schema": { "type": "string" }, "description": "Full authorized_keys line" }
      ],
      "result": {
        "name": "key",
        "schema": {
          "type": "object",
          "properties": {
            "id":          { "type": "string" },
            "algorithm":   { "type": "string" },
            "fingerprint": { "type": "string" },
            "comment":     { "type": "string" },
            "line":        { "type": "string" }
          }
        }
      }
    },
    {
      "name": "router.ssh_keys.delete",
      "summary": "Remove an authorized_keys entry by its 16-char hex id.",
      "params": [
        { "name": "id", "required": true, "schema": { "type": "string" }, "description": "Key id from router.ssh_keys.list" }
      ],
      "result": { "name": "result", "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } }
    },
    {
      "name": "router.sse_multiplex_status",
      "summary": "Diagnostic snapshot of the browser SSE multiplexer — list of discovered channels and live upstream subscriptions.",
      "params": [],
      "result": {
        "name": "status",
        "schema": {
          "type": "object",
          "properties": {
            "discovered_channels": { "type": "array", "items": { "type": "string" } },
            "active_upstreams": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "channel": { "type": "string" },
                  "refcount": { "type": "integer" },
                  "socket_path": { "type": "string" },
                  "status": { "type": "string" },
                  "virtual": { "type": "boolean" }
                }
              }
            }
          }
        }
      }
    },
    {
      "name": "rpc.discover",
      "summary": "Return this OpenRPC specification",
      "params": [],
      "result": { "name": "spec", "schema": { "type": "object" } }
    }
  ]
}
