Skip to content

Router Module

voice_agent.router

Command routing for voice transcriptions.

Parses intent from transcribed text and routes to appropriate handlers.

CommandType

Bases: Enum

Types of commands that can be parsed from voice input.

Source code in src/voice_agent/router.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class CommandType(Enum):
    """Types of commands that can be parsed from voice input."""

    APPROVE = auto()
    REJECT = auto()
    STICKY_APPROVE = auto()
    CLEAR_STICKY = auto()
    LIST_APPROVALS = auto()
    STATUS = auto()
    CLEAR = auto()
    SWITCH_PROJECT = auto()
    CANCEL = auto()
    RESTART = auto()
    RESUME = auto()
    SESSIONS = auto()
    PROMPT = auto()

ParsedCommand dataclass

Result of parsing a voice transcription.

Attributes:

Name Type Description
command_type CommandType

The type of command detected.

text str

The original transcription text.

project str | None

Project name if SWITCH_PROJECT command.

Source code in src/voice_agent/router.py
28
29
30
31
32
33
34
35
36
37
38
39
40
@dataclass
class ParsedCommand:
    """Result of parsing a voice transcription.

    Attributes:
        command_type: The type of command detected.
        text: The original transcription text.
        project: Project name if SWITCH_PROJECT command.
    """

    command_type: CommandType
    text: str
    project: str | None = None

parse_command(text, projects=None)

Parse a voice transcription into a command.

Parameters:

Name Type Description Default
text str

Transcribed text from voice input.

required
projects dict[str, str] | None

Optional mapping of project names to paths for switch detection.

None

Returns:

Type Description
ParsedCommand

ParsedCommand with detected intent.

Source code in src/voice_agent/router.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def parse_command(text: str, projects: dict[str, str] | None = None) -> ParsedCommand:
    """Parse a voice transcription into a command.

    Args:
        text: Transcribed text from voice input.
        projects: Optional mapping of project names to paths for switch detection.

    Returns:
        ParsedCommand with detected intent.
    """
    # Strip punctuation and whitespace for matching
    lower_text = text.lower().strip().rstrip(".,!?")

    # Check for exact or near-exact matches first
    if lower_text in APPROVE_KEYWORDS:
        return ParsedCommand(command_type=CommandType.APPROVE, text=text)

    if lower_text in REJECT_KEYWORDS:
        return ParsedCommand(command_type=CommandType.REJECT, text=text)

    # Check for status keywords
    for keyword in STATUS_KEYWORDS:
        if keyword in lower_text:
            return ParsedCommand(command_type=CommandType.STATUS, text=text)

    # Check for clear context keywords (exact match)
    if lower_text in CLEAR_KEYWORDS:
        return ParsedCommand(command_type=CommandType.CLEAR, text=text)

    # Check for sticky approve keywords
    for keyword in STICKY_APPROVE_KEYWORDS:
        if keyword in lower_text:
            return ParsedCommand(command_type=CommandType.STICKY_APPROVE, text=text)

    # Check for clear sticky keywords
    for keyword in CLEAR_STICKY_KEYWORDS:
        if keyword in lower_text:
            return ParsedCommand(command_type=CommandType.CLEAR_STICKY, text=text)

    # Check for list approvals keywords
    for keyword in LIST_APPROVALS_KEYWORDS:
        if keyword in lower_text:
            return ParsedCommand(command_type=CommandType.LIST_APPROVALS, text=text)

    # Check for cancel/escape keywords
    for keyword in CANCEL_KEYWORDS:
        if keyword in lower_text:
            return ParsedCommand(command_type=CommandType.CANCEL, text=text)

    # Check for restart keywords (exact match to avoid false positives)
    if lower_text in RESTART_KEYWORDS:
        return ParsedCommand(command_type=CommandType.RESTART, text=text)

    # Check for resume keywords (exact match)
    if lower_text in RESUME_KEYWORDS:
        return ParsedCommand(command_type=CommandType.RESUME, text=text)

    # Check for sessions keywords
    for keyword in SESSIONS_KEYWORDS:
        if keyword in lower_text:
            return ParsedCommand(command_type=CommandType.SESSIONS, text=text)

    # Check for project switch commands
    if projects:
        # Check for "on PROJECT: command" format first
        if lower_text.startswith("on ") and ":" in lower_text:
            parts = lower_text.split(":", 1)
            project_part = parts[0][3:].strip()
            for name in projects:
                if name in project_part or project_part in name:
                    return ParsedCommand(
                        command_type=CommandType.SWITCH_PROJECT,
                        text=parts[1].strip() if len(parts) > 1 else text,
                        project=name,
                    )

        for prefix in ("work on ", "switch to ", "on "):
            if lower_text.startswith(prefix):
                project_name = lower_text[len(prefix) :].strip().rstrip(":")
                if project_name in projects:
                    return ParsedCommand(
                        command_type=CommandType.SWITCH_PROJECT,
                        text=text,
                        project=project_name,
                    )
                # Check partial matches
                for name in projects:
                    if name in project_name or project_name in name:
                        return ParsedCommand(
                            command_type=CommandType.SWITCH_PROJECT,
                            text=text,
                            project=name,
                        )

    # Check for skill invocation: "skill X" -> "/X"
    if lower_text.startswith("skill "):
        skill_name = lower_text[6:].strip()
        if skill_name:
            return ParsedCommand(
                command_type=CommandType.PROMPT, text=f"/{skill_name}"
            )

    # Default: treat as a prompt to send to Claude
    return ParsedCommand(command_type=CommandType.PROMPT, text=text)