How FluidVoice Command Mode Executes Terminal Commands from Voice Input

FluidVoice's Command Mode translates spoken requests into terminal commands by routing transcribed voice through an LLM agent that validates, executes, and verifies shell commands via a zsh subprocess.

FluidVoice is an open-source macOS application that bridges voice input and terminal automation. The Command Mode feature acts as an AI agent that listens to natural language, converts speech into executable shell commands, and manages the entire execution lifecycle with safety guardrails. According to the altic-dev/FluidVoice source code, this pipeline relies on a tight integration between SwiftUI views, a @MainActor-isolated service layer, and a raw zsh process spawner.

Voice Capture and Command Initiation

Command Mode begins in the UI layer when the user finishes speaking. The CommandModeView forwards the transcribed text to CommandModeService.processUserCommand(_:), which appends the message to the conversation history and persists the chat.

// In CommandModeView – when the user finishes speaking
Task {
    await commandModeService.processUserCommand(transcribedText)
}

This entry point, located in Sources/Fluid/Services/CommandModeService.swift (lines 307–324), marks the transition from voice input to the LLM-driven execution pipeline.

LLM Orchestration and Tool Calling

The System Prompt and execute_terminal_command

Inside processNextTurn(), the service constructs an OpenAI-style chat payload combining a system prompt with the conversation history. The system prompt (lines 376–419 in CommandModeService.swift) instructs the model to use the function calling tool named execute_terminal_command and to follow a strict "check → execute → verify" workflow.

// Conceptual representation of the system prompt logic
// Defined in callLLM() around lines 376-419
let systemPrompt = """
You are a terminal assistant. Use the execute_terminal_command tool.
Always follow: checking → executing → verifying.
"""

Parsing the Tool Call Response

When the LLM decides a command is necessary, callLLM() returns an LLMResponse containing a toolCall field. This structure includes the command string, an optional working directory, and a purpose field with values like checking, executing, or verifying. The service maps this to a stepType via determineStepType(for:purpose:) (lines 850–891).

{
  "name": "execute_terminal_command",
  "arguments": {
    "command": "ls -la ~/Downloads",
    "workingDirectory": "/Users/joe",
    "purpose": "checking"
  }
}

Safety Guardrails for Destructive Commands

Before execution, the service validates the command through isDestructiveCommand(_:) (lines 594–633). This guard detects dangerous keywords like rm or sudo and triggers a pending-command state that requires explicit user confirmation. This prevents accidental data loss or privilege escalation directly from voice input.

Terminal Execution via Zsh

Once cleared (or if non-destructive), executeCommand(_:workingDirectory:callId:purpose:) (lines 636–658) delegates to TerminalService.execute(command:workingDirectory:). This spawns a /bin/zsh process on a background thread, captures stdout and stderr, enforces a timeout, and returns a CommandResult struct.

// Inside CommandModeService
await self.executeCommand(tc.command,
                         workingDirectory: tc.workingDirectory,
                         callId: tc.id,
                         purpose: tc.purpose)

// Inside TerminalService (lines 66-84)
let result = await terminalService.execute(
    command: "ls -la ~/Downloads",
    workingDirectory: "/Users/joe"
)

The entire service layer runs on the main actor (@MainActor) to keep UI state synchronized, while the actual shell execution remains non-blocking.

Feedback Loop and Verification

After execution, the service wraps the raw result in EnhancedCommandResult and converts it to JSON via toJSON() (lines 668–686). This structured data includes the exit code, output, error stream, and execution time.

{
  "success": true,
  "command": "ls -la ~/Downloads",
  "output": "total 0\n-rw-r--r--  1 joe  staff  0 Sep  9 12:34 file.txt",
  "error": null,
  "exitCode": 0,
  "executionTimeMs": 12,
  "purpose": "checking"
}

The JSON is appended to the conversation history as a tool message, and processNextTurn() is invoked again (line 662). This creates a verification loop, allowing the LLM to inspect the result and determine if further steps are required. The cycle terminates when the LLM produces a final text response without additional tool calls.

Summary

  • CommandModeService.swift (lines 307–324) initiates the flow by receiving transcribed voice and maintaining conversation state.
  • LLM tool calling uses the execute_terminal_command function with a "check-execute-verify" workflow defined in the system prompt (lines 376–419).
  • Safety checks via isDestructiveCommand (lines 594–633) require user confirmation for destructive operations like rm or sudo.
  • TerminalService.swift (lines 66–84) executes raw /bin/zsh commands asynchronously with timeout enforcement.
  • Verification loops continue until the LLM confirms task completion, using EnhancedCommandResult JSON (lines 668–686) to feed terminal output back into the conversation.

Frequently Asked Questions

Does FluidVoice Command Mode work on Linux or Windows?

No. According to the source code in TerminalService.swift, the application explicitly spawns /bin/zsh processes, which ties the execution layer to macOS. The path assumptions and shell-specific behavior are not portable to other operating systems.

What happens if a voice command is ambiguous?

The LLM can request clarification by returning a text response instead of a tool call, or it can execute a purpose: "checking" command first to inspect the environment before proceeding with the main operation. This multi-step reasoning is enforced by the system prompt instructions in callLLM().

How does FluidVoice prevent dangerous commands from running automatically?

The isDestructiveCommand(_:) guard (lines 594–633 in CommandModeService.swift) scans command strings for destructive patterns like rm, sudo, or > redirects. When detected, the service enters a pending state and surfaces a confirmation dialog to the user through the @MainActor-bound UI layer before proceeding with execution.

Can I customize the LLM system prompt for Command Mode?

Yes. The system prompt defining the "check → execute → verify" workflow and the execute_terminal_command tool specification is defined in callLLM() around lines 376–419 of CommandModeService.swift. Modifying this string allows you to adjust the agent's behavior, though recompilation is required.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →