agent-cli 0.70.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_cli/__init__.py +5 -0
- agent_cli/__main__.py +6 -0
- agent_cli/_extras.json +14 -0
- agent_cli/_requirements/.gitkeep +0 -0
- agent_cli/_requirements/audio.txt +79 -0
- agent_cli/_requirements/faster-whisper.txt +215 -0
- agent_cli/_requirements/kokoro.txt +425 -0
- agent_cli/_requirements/llm.txt +183 -0
- agent_cli/_requirements/memory.txt +355 -0
- agent_cli/_requirements/mlx-whisper.txt +222 -0
- agent_cli/_requirements/piper.txt +176 -0
- agent_cli/_requirements/rag.txt +402 -0
- agent_cli/_requirements/server.txt +154 -0
- agent_cli/_requirements/speed.txt +77 -0
- agent_cli/_requirements/vad.txt +155 -0
- agent_cli/_requirements/wyoming.txt +71 -0
- agent_cli/_tools.py +368 -0
- agent_cli/agents/__init__.py +23 -0
- agent_cli/agents/_voice_agent_common.py +136 -0
- agent_cli/agents/assistant.py +383 -0
- agent_cli/agents/autocorrect.py +284 -0
- agent_cli/agents/chat.py +496 -0
- agent_cli/agents/memory/__init__.py +31 -0
- agent_cli/agents/memory/add.py +190 -0
- agent_cli/agents/memory/proxy.py +160 -0
- agent_cli/agents/rag_proxy.py +128 -0
- agent_cli/agents/speak.py +209 -0
- agent_cli/agents/transcribe.py +671 -0
- agent_cli/agents/transcribe_daemon.py +499 -0
- agent_cli/agents/voice_edit.py +291 -0
- agent_cli/api.py +22 -0
- agent_cli/cli.py +106 -0
- agent_cli/config.py +503 -0
- agent_cli/config_cmd.py +307 -0
- agent_cli/constants.py +27 -0
- agent_cli/core/__init__.py +1 -0
- agent_cli/core/audio.py +461 -0
- agent_cli/core/audio_format.py +299 -0
- agent_cli/core/chroma.py +88 -0
- agent_cli/core/deps.py +191 -0
- agent_cli/core/openai_proxy.py +139 -0
- agent_cli/core/process.py +195 -0
- agent_cli/core/reranker.py +120 -0
- agent_cli/core/sse.py +87 -0
- agent_cli/core/transcription_logger.py +70 -0
- agent_cli/core/utils.py +526 -0
- agent_cli/core/vad.py +175 -0
- agent_cli/core/watch.py +65 -0
- agent_cli/dev/__init__.py +14 -0
- agent_cli/dev/cli.py +1588 -0
- agent_cli/dev/coding_agents/__init__.py +19 -0
- agent_cli/dev/coding_agents/aider.py +24 -0
- agent_cli/dev/coding_agents/base.py +167 -0
- agent_cli/dev/coding_agents/claude.py +39 -0
- agent_cli/dev/coding_agents/codex.py +24 -0
- agent_cli/dev/coding_agents/continue_dev.py +15 -0
- agent_cli/dev/coding_agents/copilot.py +24 -0
- agent_cli/dev/coding_agents/cursor_agent.py +48 -0
- agent_cli/dev/coding_agents/gemini.py +28 -0
- agent_cli/dev/coding_agents/opencode.py +15 -0
- agent_cli/dev/coding_agents/registry.py +49 -0
- agent_cli/dev/editors/__init__.py +19 -0
- agent_cli/dev/editors/base.py +89 -0
- agent_cli/dev/editors/cursor.py +15 -0
- agent_cli/dev/editors/emacs.py +46 -0
- agent_cli/dev/editors/jetbrains.py +56 -0
- agent_cli/dev/editors/nano.py +31 -0
- agent_cli/dev/editors/neovim.py +33 -0
- agent_cli/dev/editors/registry.py +59 -0
- agent_cli/dev/editors/sublime.py +20 -0
- agent_cli/dev/editors/vim.py +42 -0
- agent_cli/dev/editors/vscode.py +15 -0
- agent_cli/dev/editors/zed.py +20 -0
- agent_cli/dev/project.py +568 -0
- agent_cli/dev/registry.py +52 -0
- agent_cli/dev/skill/SKILL.md +141 -0
- agent_cli/dev/skill/examples.md +571 -0
- agent_cli/dev/terminals/__init__.py +19 -0
- agent_cli/dev/terminals/apple_terminal.py +82 -0
- agent_cli/dev/terminals/base.py +56 -0
- agent_cli/dev/terminals/gnome.py +51 -0
- agent_cli/dev/terminals/iterm2.py +84 -0
- agent_cli/dev/terminals/kitty.py +77 -0
- agent_cli/dev/terminals/registry.py +48 -0
- agent_cli/dev/terminals/tmux.py +58 -0
- agent_cli/dev/terminals/warp.py +132 -0
- agent_cli/dev/terminals/zellij.py +78 -0
- agent_cli/dev/worktree.py +856 -0
- agent_cli/docs_gen.py +417 -0
- agent_cli/example-config.toml +185 -0
- agent_cli/install/__init__.py +5 -0
- agent_cli/install/common.py +89 -0
- agent_cli/install/extras.py +174 -0
- agent_cli/install/hotkeys.py +48 -0
- agent_cli/install/services.py +87 -0
- agent_cli/memory/__init__.py +7 -0
- agent_cli/memory/_files.py +250 -0
- agent_cli/memory/_filters.py +63 -0
- agent_cli/memory/_git.py +157 -0
- agent_cli/memory/_indexer.py +142 -0
- agent_cli/memory/_ingest.py +408 -0
- agent_cli/memory/_persistence.py +182 -0
- agent_cli/memory/_prompt.py +91 -0
- agent_cli/memory/_retrieval.py +294 -0
- agent_cli/memory/_store.py +169 -0
- agent_cli/memory/_streaming.py +44 -0
- agent_cli/memory/_tasks.py +48 -0
- agent_cli/memory/api.py +113 -0
- agent_cli/memory/client.py +272 -0
- agent_cli/memory/engine.py +361 -0
- agent_cli/memory/entities.py +43 -0
- agent_cli/memory/models.py +112 -0
- agent_cli/opts.py +433 -0
- agent_cli/py.typed +0 -0
- agent_cli/rag/__init__.py +3 -0
- agent_cli/rag/_indexer.py +67 -0
- agent_cli/rag/_indexing.py +226 -0
- agent_cli/rag/_prompt.py +30 -0
- agent_cli/rag/_retriever.py +156 -0
- agent_cli/rag/_store.py +48 -0
- agent_cli/rag/_utils.py +218 -0
- agent_cli/rag/api.py +175 -0
- agent_cli/rag/client.py +299 -0
- agent_cli/rag/engine.py +302 -0
- agent_cli/rag/models.py +55 -0
- agent_cli/scripts/.runtime/.gitkeep +0 -0
- agent_cli/scripts/__init__.py +1 -0
- agent_cli/scripts/check_plugin_skill_sync.py +50 -0
- agent_cli/scripts/linux-hotkeys/README.md +63 -0
- agent_cli/scripts/linux-hotkeys/toggle-autocorrect.sh +45 -0
- agent_cli/scripts/linux-hotkeys/toggle-transcription.sh +58 -0
- agent_cli/scripts/linux-hotkeys/toggle-voice-edit.sh +58 -0
- agent_cli/scripts/macos-hotkeys/README.md +45 -0
- agent_cli/scripts/macos-hotkeys/skhd-config-example +5 -0
- agent_cli/scripts/macos-hotkeys/toggle-autocorrect.sh +12 -0
- agent_cli/scripts/macos-hotkeys/toggle-transcription.sh +37 -0
- agent_cli/scripts/macos-hotkeys/toggle-voice-edit.sh +37 -0
- agent_cli/scripts/nvidia-asr-server/README.md +99 -0
- agent_cli/scripts/nvidia-asr-server/pyproject.toml +27 -0
- agent_cli/scripts/nvidia-asr-server/server.py +255 -0
- agent_cli/scripts/nvidia-asr-server/shell.nix +32 -0
- agent_cli/scripts/nvidia-asr-server/uv.lock +4654 -0
- agent_cli/scripts/run-openwakeword.sh +11 -0
- agent_cli/scripts/run-piper-windows.ps1 +30 -0
- agent_cli/scripts/run-piper.sh +24 -0
- agent_cli/scripts/run-whisper-linux.sh +40 -0
- agent_cli/scripts/run-whisper-macos.sh +6 -0
- agent_cli/scripts/run-whisper-windows.ps1 +51 -0
- agent_cli/scripts/run-whisper.sh +9 -0
- agent_cli/scripts/run_faster_whisper_server.py +136 -0
- agent_cli/scripts/setup-linux-hotkeys.sh +72 -0
- agent_cli/scripts/setup-linux.sh +108 -0
- agent_cli/scripts/setup-macos-hotkeys.sh +61 -0
- agent_cli/scripts/setup-macos.sh +76 -0
- agent_cli/scripts/setup-windows.ps1 +63 -0
- agent_cli/scripts/start-all-services-windows.ps1 +53 -0
- agent_cli/scripts/start-all-services.sh +178 -0
- agent_cli/scripts/sync_extras.py +138 -0
- agent_cli/server/__init__.py +3 -0
- agent_cli/server/cli.py +721 -0
- agent_cli/server/common.py +222 -0
- agent_cli/server/model_manager.py +288 -0
- agent_cli/server/model_registry.py +225 -0
- agent_cli/server/proxy/__init__.py +3 -0
- agent_cli/server/proxy/api.py +444 -0
- agent_cli/server/streaming.py +67 -0
- agent_cli/server/tts/__init__.py +3 -0
- agent_cli/server/tts/api.py +335 -0
- agent_cli/server/tts/backends/__init__.py +82 -0
- agent_cli/server/tts/backends/base.py +139 -0
- agent_cli/server/tts/backends/kokoro.py +403 -0
- agent_cli/server/tts/backends/piper.py +253 -0
- agent_cli/server/tts/model_manager.py +201 -0
- agent_cli/server/tts/model_registry.py +28 -0
- agent_cli/server/tts/wyoming_handler.py +249 -0
- agent_cli/server/whisper/__init__.py +3 -0
- agent_cli/server/whisper/api.py +413 -0
- agent_cli/server/whisper/backends/__init__.py +89 -0
- agent_cli/server/whisper/backends/base.py +97 -0
- agent_cli/server/whisper/backends/faster_whisper.py +225 -0
- agent_cli/server/whisper/backends/mlx.py +270 -0
- agent_cli/server/whisper/languages.py +116 -0
- agent_cli/server/whisper/model_manager.py +157 -0
- agent_cli/server/whisper/model_registry.py +28 -0
- agent_cli/server/whisper/wyoming_handler.py +203 -0
- agent_cli/services/__init__.py +343 -0
- agent_cli/services/_wyoming_utils.py +64 -0
- agent_cli/services/asr.py +506 -0
- agent_cli/services/llm.py +228 -0
- agent_cli/services/tts.py +450 -0
- agent_cli/services/wake_word.py +142 -0
- agent_cli-0.70.5.dist-info/METADATA +2118 -0
- agent_cli-0.70.5.dist-info/RECORD +196 -0
- agent_cli-0.70.5.dist-info/WHEEL +4 -0
- agent_cli-0.70.5.dist-info/entry_points.txt +4 -0
- agent_cli-0.70.5.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""AI coding agent adapters for the dev module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import CodingAgent
|
|
6
|
+
from .registry import (
|
|
7
|
+
detect_current_agent,
|
|
8
|
+
get_agent,
|
|
9
|
+
get_all_agents,
|
|
10
|
+
get_available_agents,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CodingAgent",
|
|
15
|
+
"detect_current_agent",
|
|
16
|
+
"get_agent",
|
|
17
|
+
"get_all_agents",
|
|
18
|
+
"get_available_agents",
|
|
19
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Aider AI coding agent adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import CodingAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Aider(CodingAgent):
|
|
9
|
+
"""Aider - AI pair programming in your terminal."""
|
|
10
|
+
|
|
11
|
+
name = "aider"
|
|
12
|
+
command = "aider"
|
|
13
|
+
install_url = "https://aider.chat"
|
|
14
|
+
detect_process_name = "aider"
|
|
15
|
+
|
|
16
|
+
def prompt_args(self, prompt: str) -> list[str]:
|
|
17
|
+
"""Return prompt using --message flag.
|
|
18
|
+
|
|
19
|
+
Aider uses -m/--message for initial prompts:
|
|
20
|
+
`aider --message "your prompt here"`
|
|
21
|
+
|
|
22
|
+
See: https://aider.chat/docs/scripting.html
|
|
23
|
+
"""
|
|
24
|
+
return ["--message", prompt]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Base class for AI coding agent adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
from abc import ABC
|
|
8
|
+
from pathlib import PurePath
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CodingAgent(ABC):
|
|
16
|
+
"""Abstract base class for AI coding agent adapters."""
|
|
17
|
+
|
|
18
|
+
# Display name for the agent
|
|
19
|
+
name: str
|
|
20
|
+
|
|
21
|
+
# CLI command to invoke the agent
|
|
22
|
+
command: str
|
|
23
|
+
|
|
24
|
+
# Alternative command names (for detection)
|
|
25
|
+
alt_commands: tuple[str, ...] = ()
|
|
26
|
+
|
|
27
|
+
# URL for installation instructions
|
|
28
|
+
install_url: str = ""
|
|
29
|
+
|
|
30
|
+
# Declarative detection: env var that indicates running inside this agent
|
|
31
|
+
# e.g., "CLAUDECODE" for Claude Code (checks if env var is set to "1")
|
|
32
|
+
detect_env_var: str | None = None
|
|
33
|
+
|
|
34
|
+
# Declarative detection: process name to look for in parent processes
|
|
35
|
+
# e.g., "aider" will match any parent process containing "aider"
|
|
36
|
+
detect_process_name: str | None = None
|
|
37
|
+
|
|
38
|
+
def detect(self) -> bool:
|
|
39
|
+
"""Check if this agent is currently running/active in the environment.
|
|
40
|
+
|
|
41
|
+
Default implementation uses declarative detection attributes.
|
|
42
|
+
Override for custom detection logic.
|
|
43
|
+
"""
|
|
44
|
+
# Check env var first (faster) - checks for "1" specifically
|
|
45
|
+
if self.detect_env_var and os.environ.get(self.detect_env_var) == "1":
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
# Fall back to parent process detection
|
|
49
|
+
if self.detect_process_name:
|
|
50
|
+
parent_names = _get_parent_process_names()
|
|
51
|
+
return any(self.detect_process_name in name for name in parent_names)
|
|
52
|
+
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
def is_available(self) -> bool:
|
|
56
|
+
"""Check if this agent is installed and available."""
|
|
57
|
+
if shutil.which(self.command):
|
|
58
|
+
return True
|
|
59
|
+
return any(shutil.which(cmd) for cmd in self.alt_commands)
|
|
60
|
+
|
|
61
|
+
def get_executable(self) -> str | None:
|
|
62
|
+
"""Get the path to the executable."""
|
|
63
|
+
if exe := shutil.which(self.command):
|
|
64
|
+
return exe
|
|
65
|
+
for cmd in self.alt_commands:
|
|
66
|
+
if exe := shutil.which(cmd):
|
|
67
|
+
return exe
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def prompt_args(self, prompt: str) -> list[str]:
|
|
71
|
+
"""Return the CLI arguments to pass an initial prompt to the agent.
|
|
72
|
+
|
|
73
|
+
Override this method in subclasses for agents that support initial prompts.
|
|
74
|
+
Default implementation returns empty list (prompt not supported).
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
prompt: The initial prompt to pass to the agent
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
List of CLI arguments (e.g., ["prompt text"] or ["-m", "prompt text"])
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
del prompt # unused in base implementation
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
def launch_command(
|
|
87
|
+
self,
|
|
88
|
+
path: Path, # noqa: ARG002
|
|
89
|
+
extra_args: list[str] | None = None,
|
|
90
|
+
prompt: str | None = None,
|
|
91
|
+
) -> list[str]:
|
|
92
|
+
"""Return the command to launch this agent in a directory.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
path: The directory to launch the agent in
|
|
96
|
+
extra_args: Additional arguments to pass to the agent
|
|
97
|
+
prompt: Optional initial prompt to pass to the agent
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
List of command arguments
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
exe = self.get_executable()
|
|
104
|
+
if exe is None:
|
|
105
|
+
msg = f"{self.name} is not installed"
|
|
106
|
+
if self.install_url:
|
|
107
|
+
msg += f". Install from {self.install_url}"
|
|
108
|
+
raise RuntimeError(msg)
|
|
109
|
+
cmd = [exe]
|
|
110
|
+
if extra_args:
|
|
111
|
+
cmd.extend(extra_args)
|
|
112
|
+
if prompt:
|
|
113
|
+
cmd.extend(self.prompt_args(prompt))
|
|
114
|
+
return cmd
|
|
115
|
+
|
|
116
|
+
def get_env(self) -> dict[str, str]:
|
|
117
|
+
"""Get any additional environment variables needed."""
|
|
118
|
+
return {}
|
|
119
|
+
|
|
120
|
+
def __repr__(self) -> str: # noqa: D105
|
|
121
|
+
status = "available" if self.is_available() else "not installed"
|
|
122
|
+
return f"<{self.__class__.__name__} {self.name!r} ({status})>"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _get_parent_process_names() -> list[str]:
|
|
126
|
+
"""Get names of parent processes (for detecting current agent).
|
|
127
|
+
|
|
128
|
+
Extracts names from both process.name() and cmdline.
|
|
129
|
+
This handles Node.js CLIs that don't set process.title:
|
|
130
|
+
- process.name() returns 'node' for most Node.js CLI tools
|
|
131
|
+
- cmdline contains the actual script path like '/path/to/cn'
|
|
132
|
+
- CLI tools that set process.title (like Claude) show their name directly
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
import psutil # noqa: PLC0415
|
|
136
|
+
|
|
137
|
+
process = psutil.Process(os.getpid())
|
|
138
|
+
names = []
|
|
139
|
+
for _ in range(10): # Limit depth
|
|
140
|
+
process = process.parent()
|
|
141
|
+
if process is None:
|
|
142
|
+
break
|
|
143
|
+
# Add the process name (works for native binaries and tools that set process.title)
|
|
144
|
+
names.append(process.name().lower())
|
|
145
|
+
|
|
146
|
+
# Also check cmdline for the actual command (handles Node.js/Python CLIs)
|
|
147
|
+
# e.g., cmdline=['node', '/path/to/cn', '--version'] → extract 'cn'
|
|
148
|
+
try:
|
|
149
|
+
cmdline = process.cmdline()
|
|
150
|
+
if len(cmdline) >= 2: # noqa: PLR2004
|
|
151
|
+
# Get the script/command from cmdline[1] (the actual CLI tool)
|
|
152
|
+
cmd_name = PurePath(cmdline[1]).name.lower()
|
|
153
|
+
# Remove common extensions
|
|
154
|
+
for ext in (".js", ".py", ".sh", ".exe"):
|
|
155
|
+
if cmd_name.endswith(ext):
|
|
156
|
+
cmd_name = cmd_name[: -len(ext)]
|
|
157
|
+
break
|
|
158
|
+
if cmd_name and cmd_name not in names:
|
|
159
|
+
names.append(cmd_name)
|
|
160
|
+
except (psutil.AccessDenied, psutil.ZombieProcess, IndexError):
|
|
161
|
+
pass
|
|
162
|
+
return names
|
|
163
|
+
except ImportError:
|
|
164
|
+
# psutil not available, return empty list
|
|
165
|
+
return []
|
|
166
|
+
except Exception:
|
|
167
|
+
return []
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Claude Code AI coding agent adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .base import CodingAgent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ClaudeCode(CodingAgent):
|
|
12
|
+
"""Claude Code (Anthropic's CLI coding agent)."""
|
|
13
|
+
|
|
14
|
+
name = "claude"
|
|
15
|
+
command = "claude"
|
|
16
|
+
alt_commands = ("claude-code",)
|
|
17
|
+
install_url = "https://code.claude.com/docs/en/overview"
|
|
18
|
+
detect_env_var = "CLAUDECODE"
|
|
19
|
+
detect_process_name = "claude"
|
|
20
|
+
|
|
21
|
+
def prompt_args(self, prompt: str) -> list[str]:
|
|
22
|
+
"""Return prompt as positional argument.
|
|
23
|
+
|
|
24
|
+
Claude Code accepts prompt as a positional argument:
|
|
25
|
+
`claude "your prompt here"`
|
|
26
|
+
|
|
27
|
+
See: claude --help
|
|
28
|
+
"""
|
|
29
|
+
return [prompt]
|
|
30
|
+
|
|
31
|
+
def get_executable(self) -> str | None:
|
|
32
|
+
"""Get the Claude executable path."""
|
|
33
|
+
# Check common installation path first
|
|
34
|
+
local_claude = Path.home() / ".claude" / "local" / "claude"
|
|
35
|
+
if local_claude.exists() and os.access(local_claude, os.X_OK):
|
|
36
|
+
return str(local_claude)
|
|
37
|
+
|
|
38
|
+
# Fall back to PATH lookup
|
|
39
|
+
return super().get_executable()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""OpenAI Codex CLI coding agent adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import CodingAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Codex(CodingAgent):
|
|
9
|
+
"""OpenAI Codex CLI coding agent."""
|
|
10
|
+
|
|
11
|
+
name = "codex"
|
|
12
|
+
command = "codex"
|
|
13
|
+
install_url = "https://github.com/openai/codex"
|
|
14
|
+
detect_process_name = "codex"
|
|
15
|
+
|
|
16
|
+
def prompt_args(self, prompt: str) -> list[str]:
|
|
17
|
+
"""Return prompt as positional argument.
|
|
18
|
+
|
|
19
|
+
Codex accepts prompt as a positional argument:
|
|
20
|
+
`codex "your prompt here"`
|
|
21
|
+
|
|
22
|
+
See: codex --help
|
|
23
|
+
"""
|
|
24
|
+
return [prompt]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Continue Dev CLI coding agent adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import CodingAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContinueDev(CodingAgent):
|
|
9
|
+
"""Continue Dev - AI code assistant."""
|
|
10
|
+
|
|
11
|
+
name = "continue"
|
|
12
|
+
command = "cn"
|
|
13
|
+
install_url = "https://continue.dev"
|
|
14
|
+
# Detection via cmdline extraction (cn runs as 'node' but cmdline contains '/path/to/cn')
|
|
15
|
+
detect_process_name = "cn"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""GitHub Copilot CLI coding agent adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import CodingAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Copilot(CodingAgent):
|
|
9
|
+
"""GitHub Copilot CLI coding agent."""
|
|
10
|
+
|
|
11
|
+
name = "copilot"
|
|
12
|
+
command = "copilot"
|
|
13
|
+
install_url = "https://github.com/github/copilot-cli"
|
|
14
|
+
detect_process_name = "copilot"
|
|
15
|
+
|
|
16
|
+
def prompt_args(self, prompt: str) -> list[str]:
|
|
17
|
+
"""Return prompt using --prompt flag.
|
|
18
|
+
|
|
19
|
+
Copilot CLI uses -p/--prompt for initial prompts:
|
|
20
|
+
`copilot --prompt "your prompt here"`
|
|
21
|
+
|
|
22
|
+
See: https://github.com/github/copilot-cli
|
|
23
|
+
"""
|
|
24
|
+
return ["--prompt", prompt]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Cursor Agent CLI coding agent adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from .base import CodingAgent
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CursorAgent(CodingAgent):
|
|
15
|
+
"""Cursor Agent - AI agent mode for Cursor editor."""
|
|
16
|
+
|
|
17
|
+
name = "cursor-agent"
|
|
18
|
+
command = "cursor-agent"
|
|
19
|
+
alt_commands = ("cursor",)
|
|
20
|
+
install_url = "https://cursor.com"
|
|
21
|
+
|
|
22
|
+
def detect(self) -> bool:
|
|
23
|
+
"""Detect if running inside Cursor Agent.
|
|
24
|
+
|
|
25
|
+
CURSOR_AGENT uses presence check (not == "1"), so custom detection needed.
|
|
26
|
+
"""
|
|
27
|
+
return os.environ.get("CURSOR_AGENT") is not None
|
|
28
|
+
|
|
29
|
+
def launch_command(
|
|
30
|
+
self,
|
|
31
|
+
path: Path, # noqa: ARG002
|
|
32
|
+
extra_args: list[str] | None = None,
|
|
33
|
+
prompt: str | None = None,
|
|
34
|
+
) -> list[str]:
|
|
35
|
+
"""Return the command to launch Cursor Agent."""
|
|
36
|
+
exe = self.get_executable()
|
|
37
|
+
if exe is None:
|
|
38
|
+
msg = f"{self.name} is not installed"
|
|
39
|
+
if self.install_url:
|
|
40
|
+
msg += f". Install from {self.install_url}"
|
|
41
|
+
raise RuntimeError(msg)
|
|
42
|
+
# Try cursor-agent first, fall back to cursor cli
|
|
43
|
+
cmd = [exe] if exe.endswith("cursor-agent") else [exe, "cli"]
|
|
44
|
+
if extra_args:
|
|
45
|
+
cmd.extend(extra_args)
|
|
46
|
+
if prompt:
|
|
47
|
+
cmd.extend(self.prompt_args(prompt))
|
|
48
|
+
return cmd
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Google Gemini CLI coding agent adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import CodingAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Gemini(CodingAgent):
|
|
9
|
+
"""Google Gemini CLI coding agent."""
|
|
10
|
+
|
|
11
|
+
name = "gemini"
|
|
12
|
+
command = "gemini"
|
|
13
|
+
install_url = "https://github.com/google-gemini/gemini-cli"
|
|
14
|
+
detect_process_name = "gemini"
|
|
15
|
+
|
|
16
|
+
def prompt_args(self, prompt: str) -> list[str]:
|
|
17
|
+
"""Return prompt using -i/--prompt-interactive flag.
|
|
18
|
+
|
|
19
|
+
Gemini CLI uses -i for interactive mode with initial prompt:
|
|
20
|
+
`gemini -i "your prompt here"`
|
|
21
|
+
|
|
22
|
+
Note: -p/--prompt is non-interactive (exits after response).
|
|
23
|
+
|
|
24
|
+
Evidence: `gemini --help` shows:
|
|
25
|
+
-i, --prompt-interactive Execute the provided prompt and continue
|
|
26
|
+
in interactive mode
|
|
27
|
+
"""
|
|
28
|
+
return ["-i", prompt]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""OpenCode CLI coding agent adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import CodingAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OpenCode(CodingAgent):
|
|
9
|
+
"""OpenCode - AI coding assistant."""
|
|
10
|
+
|
|
11
|
+
name = "opencode"
|
|
12
|
+
command = "opencode"
|
|
13
|
+
install_url = "https://opencode.ai"
|
|
14
|
+
detect_env_var = "OPENCODE"
|
|
15
|
+
detect_process_name = "opencode"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Registry for AI coding agent adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from agent_cli.dev.registry import Registry
|
|
6
|
+
|
|
7
|
+
from .aider import Aider
|
|
8
|
+
from .base import CodingAgent # noqa: TC001
|
|
9
|
+
from .claude import ClaudeCode
|
|
10
|
+
from .codex import Codex
|
|
11
|
+
from .continue_dev import ContinueDev
|
|
12
|
+
from .copilot import Copilot
|
|
13
|
+
from .cursor_agent import CursorAgent
|
|
14
|
+
from .gemini import Gemini
|
|
15
|
+
from .opencode import OpenCode
|
|
16
|
+
|
|
17
|
+
# All available coding agents (in priority order for detection)
|
|
18
|
+
_AGENTS: list[type[CodingAgent]] = [
|
|
19
|
+
ClaudeCode,
|
|
20
|
+
Codex,
|
|
21
|
+
Gemini,
|
|
22
|
+
Aider,
|
|
23
|
+
Copilot,
|
|
24
|
+
ContinueDev,
|
|
25
|
+
OpenCode,
|
|
26
|
+
CursorAgent,
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
_registry: Registry[CodingAgent] = Registry(_AGENTS)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_all_agents() -> list[CodingAgent]:
|
|
33
|
+
"""Get instances of all registered coding agents."""
|
|
34
|
+
return _registry.get_all()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_available_agents() -> list[CodingAgent]:
|
|
38
|
+
"""Get all installed/available coding agents."""
|
|
39
|
+
return _registry.get_available()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def detect_current_agent() -> CodingAgent | None:
|
|
43
|
+
"""Detect which coding agent we're currently running in."""
|
|
44
|
+
return _registry.detect_current()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_agent(name: str) -> CodingAgent | None:
|
|
48
|
+
"""Get a coding agent by name."""
|
|
49
|
+
return _registry.get_by_name(name)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Editor adapters for the dev module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import Editor
|
|
6
|
+
from .registry import (
|
|
7
|
+
detect_current_editor,
|
|
8
|
+
get_all_editors,
|
|
9
|
+
get_available_editors,
|
|
10
|
+
get_editor,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Editor",
|
|
15
|
+
"detect_current_editor",
|
|
16
|
+
"get_all_editors",
|
|
17
|
+
"get_available_editors",
|
|
18
|
+
"get_editor",
|
|
19
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Base class for editor adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
from abc import ABC
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Editor(ABC):
|
|
15
|
+
"""Abstract base class for editor adapters."""
|
|
16
|
+
|
|
17
|
+
# Display name for the editor
|
|
18
|
+
name: str
|
|
19
|
+
|
|
20
|
+
# CLI command to invoke the editor
|
|
21
|
+
command: str
|
|
22
|
+
|
|
23
|
+
# Alternative command names
|
|
24
|
+
alt_commands: tuple[str, ...] = ()
|
|
25
|
+
|
|
26
|
+
# URL for installation instructions
|
|
27
|
+
install_url: str = ""
|
|
28
|
+
|
|
29
|
+
# Declarative detection: env vars that indicate running inside this editor
|
|
30
|
+
# e.g., ("NVIM", "NVIM_LISTEN_ADDRESS") for Neovim
|
|
31
|
+
detect_env_vars: tuple[str, ...] = ()
|
|
32
|
+
|
|
33
|
+
# Declarative detection: value to look for in TERM_PROGRAM
|
|
34
|
+
# e.g., "vscode" will match if TERM_PROGRAM contains "vscode" (case-insensitive)
|
|
35
|
+
detect_term_program: str | None = None
|
|
36
|
+
|
|
37
|
+
def detect(self) -> bool:
|
|
38
|
+
"""Check if currently running inside this editor's terminal.
|
|
39
|
+
|
|
40
|
+
Default implementation uses declarative detection attributes.
|
|
41
|
+
Override for custom detection logic.
|
|
42
|
+
"""
|
|
43
|
+
# Check env vars first
|
|
44
|
+
for env_var in self.detect_env_vars:
|
|
45
|
+
if os.environ.get(env_var):
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
# Check TERM_PROGRAM
|
|
49
|
+
if self.detect_term_program:
|
|
50
|
+
term_program = os.environ.get("TERM_PROGRAM")
|
|
51
|
+
if term_program and self.detect_term_program.lower() in term_program.lower():
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
def is_available(self) -> bool:
|
|
57
|
+
"""Check if this editor is installed and available."""
|
|
58
|
+
if shutil.which(self.command):
|
|
59
|
+
return True
|
|
60
|
+
return any(shutil.which(cmd) for cmd in self.alt_commands)
|
|
61
|
+
|
|
62
|
+
def get_executable(self) -> str | None:
|
|
63
|
+
"""Get the path to the executable."""
|
|
64
|
+
if exe := shutil.which(self.command):
|
|
65
|
+
return exe
|
|
66
|
+
for cmd in self.alt_commands:
|
|
67
|
+
if exe := shutil.which(cmd):
|
|
68
|
+
return exe
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def open_command(self, path: Path) -> list[str]:
|
|
72
|
+
"""Return the command to open a directory in this editor.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
path: The directory to open
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of command arguments
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
exe = self.get_executable()
|
|
82
|
+
if exe is None:
|
|
83
|
+
msg = f"{self.name} is not installed"
|
|
84
|
+
raise RuntimeError(msg)
|
|
85
|
+
return [exe, path.as_posix()]
|
|
86
|
+
|
|
87
|
+
def __repr__(self) -> str: # noqa: D105
|
|
88
|
+
status = "available" if self.is_available() else "not installed"
|
|
89
|
+
return f"<{self.__class__.__name__} {self.name!r} ({status})>"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Cursor editor adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import Editor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Cursor(Editor):
|
|
9
|
+
"""Cursor - AI-first code editor."""
|
|
10
|
+
|
|
11
|
+
name = "cursor"
|
|
12
|
+
command = "cursor"
|
|
13
|
+
install_url = "https://cursor.com"
|
|
14
|
+
detect_env_vars = ("CURSOR_AGENT",)
|
|
15
|
+
# No detect_term_program - not verified (Cursor is proprietary)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Emacs editor adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .base import Editor
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Emacs(Editor):
|
|
14
|
+
"""Emacs - An extensible, customizable text editor.
|
|
15
|
+
|
|
16
|
+
Detection via INSIDE_EMACS only. The legacy EMACS env var is deprecated.
|
|
17
|
+
|
|
18
|
+
Evidence: https://github.com/emacs-mirror/emacs/blob/master/etc/NEWS.25
|
|
19
|
+
Quote: "'M-x shell' and 'M-x compile' no longer set the EMACS environment
|
|
20
|
+
variable. This avoids clashing when other programs use the variable
|
|
21
|
+
for other purposes. [...] Use the INSIDE_EMACS environment variable
|
|
22
|
+
instead."
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
name = "emacs"
|
|
26
|
+
command = "emacs"
|
|
27
|
+
alt_commands = ("emacsclient",)
|
|
28
|
+
install_url = "https://www.gnu.org/software/emacs/"
|
|
29
|
+
detect_env_vars = ("INSIDE_EMACS",) # EMACS is deprecated since Emacs 25
|
|
30
|
+
# No detect_term_program - Emacs doesn't set TERM_PROGRAM
|
|
31
|
+
|
|
32
|
+
def open_command(self, path: Path) -> list[str]:
|
|
33
|
+
"""Return the command to open a directory in Emacs.
|
|
34
|
+
|
|
35
|
+
Uses background mode (&) for standalone emacs to match GTR behavior.
|
|
36
|
+
emacsclient uses -n flag which already runs in background.
|
|
37
|
+
"""
|
|
38
|
+
exe = self.get_executable()
|
|
39
|
+
if exe is None:
|
|
40
|
+
msg = f"{self.name} is not installed"
|
|
41
|
+
raise RuntimeError(msg)
|
|
42
|
+
# Use emacsclient if available for faster opening (-n = don't wait)
|
|
43
|
+
if "emacsclient" in exe:
|
|
44
|
+
return [exe, "-n", path.as_posix()]
|
|
45
|
+
# Run standalone emacs in background like GTR does
|
|
46
|
+
return ["sh", "-c", f'{exe} "{path.as_posix()}" &']
|