diff --git a/backend/main.py b/backend/main.py index ecc1a17..290ee59 100644 --- a/backend/main.py +++ b/backend/main.py @@ -231,6 +231,7 @@ class ClaudeProcessManager: self._broadcast_queues: List[asyncio.Queue] = [] self._is_ready = False self._status = "not_started" # not_started, starting, ready, dead + self._available_tools: List[dict] = [] # populated from system/init event @property def status(self): @@ -249,17 +250,18 @@ class ClaudeProcessManager: self._status = "starting" logger.info("Starting Claude interactive process...") + # Run as non-root user so --permission-mode bypassPermissions is allowed + # (Claude CLI refuses bypassPermissions when running as root) env = os.environ.copy() + env["HOME"] = "/home/claudeuser" # Build command: interactive claude with stream-json I/O # --permission-mode bypassPermissions allows the agent to use all tools # (Bash, file ops, MCP servers) without interactive confirmation prompts cmd = [ - "claude", - "--output-format", "stream-json", - "--input-format", "stream-json", - "--verbose", - "--permission-mode", "bypassPermissions", + "su", "-s", "/bin/bash", "claudeuser", "-c", + "claude --output-format stream-json --input-format stream-json " + "--verbose --permission-mode bypassPermissions" ] try: @@ -359,7 +361,8 @@ class ClaudeProcessManager: if event_type == "system" and event.get("subtype") == "init": self._current_session_id = event.get("session_id") self._is_ready = True - logger.info(f"Claude session initialized: {self._current_session_id}") + self._available_tools = event.get("tools", []) + logger.info(f"Claude session initialized: {self._current_session_id}, tools: {len(self._available_tools)}") elif event_type == "result": # End of a response turn @@ -636,6 +639,29 @@ async def claude_status(): } +@app.get("/api/claude/tools") +async def claude_tools(): + """Return the tools available in the current Claude session (from system/init).""" + tools = claude_mgr._available_tools + # Group by MCP server prefix (e.g. "mcp__truenas__list_pools" -> "truenas") + servers: dict = {} + builtin = [] + for t in tools: + name = t.get("name", "") + if name.startswith("mcp__"): + parts = name.split("__") + server = parts[1] if len(parts) > 1 else "unknown" + servers.setdefault(server, []).append(name) + else: + builtin.append(name) + return { + "total": len(tools), + "mcp_servers": {k: len(v) for k, v in servers.items()}, + "builtin_tools": builtin, + "raw": tools, + } + + # -------- Chat -------- @app.get("/api/chat/sessions")