Blocked by: #2539
Problem
The integration framework assumes key == CLI executable name — shutil.which(impl.key) is used at three detection sites. This breaks for agents like Rovo Dev where the key is rovodev but the executable is acli (invoked as acli rovodev).
Today there are two hardcoded special cases in check_tool() for similar mismatches:
- Claude — checks
~/.claude/local/claude and npm-local paths outside PATH
- kiro-cli — falls back to
shutil.which("kiro") when kiro-cli isn't found
Adding more special cases is not sustainable.
Proposed Solution
Add a cli_executable property to IntegrationBase:
@property
def cli_executable(self) -> str:
"""Executable name for CLI detection. Defaults to key."""
return self.key
Update the three detection sites to use it:
src/specify_cli/workflows/steps/command/__init__.py — shutil.which(impl.cli_executable)
src/specify_cli/workflows/steps/prompt/__init__.py — shutil.which(impl.cli_executable)
src/specify_cli/_utils.py check_tool() — resolve integration, use impl.cli_executable
Integration Overrides
Rovodev — executable is acli:
@property
def cli_executable(self) -> str:
return "acli"
Kiro-CLI — override to try both names, removing the hardcoded branch in check_tool():
@property
def cli_executable(self) -> str:
return "kiro-cli"
Claude — local path checks move into the integration class (e.g. override an is_cli_available() method), removing the hardcoded branch in check_tool().
Design Note
If kiro-cli's dual-binary case and Claude's non-PATH local installs are awkward to express with a single cli_executable string, consider either:
- A
cli_executables list property (check any match)
- An
is_cli_available() -> bool method that integrations override for custom detection, with the default implementation doing shutil.which(self.cli_executable)
The latter is more flexible and handles Claude's path checks cleanly.
Acceptance Criteria
Context
Follow-up from #2539 (rovodev integration). The integration scaffolding merged without dispatch support; this consolidates all CLI detection into a single extensible mechanism.
Blocked by: #2539
Problem
The integration framework assumes
key == CLI executable name—shutil.which(impl.key)is used at three detection sites. This breaks for agents like Rovo Dev where the key isrovodevbut the executable isacli(invoked asacli rovodev).Today there are two hardcoded special cases in
check_tool()for similar mismatches:~/.claude/local/claudeand npm-local paths outside PATHshutil.which("kiro")whenkiro-cliisn't foundAdding more special cases is not sustainable.
Proposed Solution
Add a
cli_executableproperty toIntegrationBase:Update the three detection sites to use it:
src/specify_cli/workflows/steps/command/__init__.py—shutil.which(impl.cli_executable)src/specify_cli/workflows/steps/prompt/__init__.py—shutil.which(impl.cli_executable)src/specify_cli/_utils.pycheck_tool()— resolve integration, useimpl.cli_executableIntegration Overrides
Rovodev — executable is
acli:Kiro-CLI — override to try both names, removing the hardcoded branch in
check_tool():Claude — local path checks move into the integration class (e.g. override an
is_cli_available()method), removing the hardcoded branch incheck_tool().Design Note
If kiro-cli's dual-binary case and Claude's non-PATH local installs are awkward to express with a single
cli_executablestring, consider either:cli_executableslist property (check any match)is_cli_available() -> boolmethod that integrations override for custom detection, with the default implementation doingshutil.which(self.cli_executable)The latter is more flexible and handles Claude's path checks cleanly.
Acceptance Criteria
cli_executableproperty (oris_cli_available()) onIntegrationBaseacliexecutablecheck_tool()AGENTS.mdupdated to documentcli_executable/is_cli_available()overrideContext
Follow-up from #2539 (rovodev integration). The integration scaffolding merged without dispatch support; this consolidates all CLI detection into a single extensible mechanism.