codemaster-cli 1.0.1__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.
- codemaster_cli-1.0.1.dist-info/METADATA +645 -0
- codemaster_cli-1.0.1.dist-info/RECORD +174 -0
- codemaster_cli-1.0.1.dist-info/WHEEL +4 -0
- codemaster_cli-1.0.1.dist-info/entry_points.txt +3 -0
- vibe/__init__.py +6 -0
- vibe/acp/__init__.py +0 -0
- vibe/acp/acp_agent_loop.py +746 -0
- vibe/acp/entrypoint.py +81 -0
- vibe/acp/tools/__init__.py +0 -0
- vibe/acp/tools/base.py +100 -0
- vibe/acp/tools/builtins/bash.py +134 -0
- vibe/acp/tools/builtins/read_file.py +54 -0
- vibe/acp/tools/builtins/search_replace.py +129 -0
- vibe/acp/tools/builtins/todo.py +65 -0
- vibe/acp/tools/builtins/write_file.py +98 -0
- vibe/acp/tools/session_update.py +118 -0
- vibe/acp/utils.py +213 -0
- vibe/cli/__init__.py +0 -0
- vibe/cli/autocompletion/__init__.py +0 -0
- vibe/cli/autocompletion/base.py +22 -0
- vibe/cli/autocompletion/path_completion.py +177 -0
- vibe/cli/autocompletion/slash_command.py +99 -0
- vibe/cli/cli.py +229 -0
- vibe/cli/clipboard.py +69 -0
- vibe/cli/commands.py +116 -0
- vibe/cli/entrypoint.py +173 -0
- vibe/cli/history_manager.py +91 -0
- vibe/cli/plan_offer/adapters/http_whoami_gateway.py +67 -0
- vibe/cli/plan_offer/decide_plan_offer.py +87 -0
- vibe/cli/plan_offer/ports/whoami_gateway.py +23 -0
- vibe/cli/terminal_setup.py +323 -0
- vibe/cli/textual_ui/__init__.py +0 -0
- vibe/cli/textual_ui/ansi_markdown.py +58 -0
- vibe/cli/textual_ui/app.py +1546 -0
- vibe/cli/textual_ui/app.tcss +1020 -0
- vibe/cli/textual_ui/external_editor.py +32 -0
- vibe/cli/textual_ui/handlers/__init__.py +5 -0
- vibe/cli/textual_ui/handlers/event_handler.py +147 -0
- vibe/cli/textual_ui/widgets/__init__.py +0 -0
- vibe/cli/textual_ui/widgets/approval_app.py +192 -0
- vibe/cli/textual_ui/widgets/banner/banner.py +85 -0
- vibe/cli/textual_ui/widgets/banner/petit_chat.py +176 -0
- vibe/cli/textual_ui/widgets/braille_renderer.py +58 -0
- vibe/cli/textual_ui/widgets/chat_input/__init__.py +7 -0
- vibe/cli/textual_ui/widgets/chat_input/body.py +214 -0
- vibe/cli/textual_ui/widgets/chat_input/completion_manager.py +58 -0
- vibe/cli/textual_ui/widgets/chat_input/completion_popup.py +43 -0
- vibe/cli/textual_ui/widgets/chat_input/container.py +195 -0
- vibe/cli/textual_ui/widgets/chat_input/text_area.py +365 -0
- vibe/cli/textual_ui/widgets/compact.py +41 -0
- vibe/cli/textual_ui/widgets/config_app.py +171 -0
- vibe/cli/textual_ui/widgets/context_progress.py +30 -0
- vibe/cli/textual_ui/widgets/load_more.py +43 -0
- vibe/cli/textual_ui/widgets/loading.py +201 -0
- vibe/cli/textual_ui/widgets/messages.py +277 -0
- vibe/cli/textual_ui/widgets/no_markup_static.py +11 -0
- vibe/cli/textual_ui/widgets/path_display.py +28 -0
- vibe/cli/textual_ui/widgets/proxy_setup_app.py +127 -0
- vibe/cli/textual_ui/widgets/question_app.py +496 -0
- vibe/cli/textual_ui/widgets/spinner.py +194 -0
- vibe/cli/textual_ui/widgets/status_message.py +76 -0
- vibe/cli/textual_ui/widgets/teleport_message.py +31 -0
- vibe/cli/textual_ui/widgets/tool_widgets.py +371 -0
- vibe/cli/textual_ui/widgets/tools.py +201 -0
- vibe/cli/textual_ui/windowing/__init__.py +29 -0
- vibe/cli/textual_ui/windowing/history.py +105 -0
- vibe/cli/textual_ui/windowing/history_windowing.py +71 -0
- vibe/cli/textual_ui/windowing/state.py +105 -0
- vibe/cli/update_notifier/__init__.py +47 -0
- vibe/cli/update_notifier/adapters/filesystem_update_cache_repository.py +59 -0
- vibe/cli/update_notifier/adapters/github_update_gateway.py +101 -0
- vibe/cli/update_notifier/adapters/pypi_update_gateway.py +107 -0
- vibe/cli/update_notifier/ports/update_cache_repository.py +16 -0
- vibe/cli/update_notifier/ports/update_gateway.py +53 -0
- vibe/cli/update_notifier/update.py +139 -0
- vibe/cli/update_notifier/whats_new.py +49 -0
- vibe/core/__init__.py +5 -0
- vibe/core/agent_loop.py +1075 -0
- vibe/core/agents/__init__.py +31 -0
- vibe/core/agents/manager.py +166 -0
- vibe/core/agents/models.py +143 -0
- vibe/core/auth/__init__.py +6 -0
- vibe/core/auth/crypto.py +137 -0
- vibe/core/auth/github.py +178 -0
- vibe/core/autocompletion/__init__.py +0 -0
- vibe/core/autocompletion/completers.py +257 -0
- vibe/core/autocompletion/file_indexer/__init__.py +10 -0
- vibe/core/autocompletion/file_indexer/ignore_rules.py +156 -0
- vibe/core/autocompletion/file_indexer/indexer.py +179 -0
- vibe/core/autocompletion/file_indexer/store.py +169 -0
- vibe/core/autocompletion/file_indexer/watcher.py +71 -0
- vibe/core/autocompletion/fuzzy.py +189 -0
- vibe/core/autocompletion/path_prompt.py +108 -0
- vibe/core/autocompletion/path_prompt_adapter.py +149 -0
- vibe/core/config.py +983 -0
- vibe/core/config_PATCH_INSTRUCTIONS.md +77 -0
- vibe/core/llm/__init__.py +0 -0
- vibe/core/llm/backend/anthropic.py +630 -0
- vibe/core/llm/backend/base.py +38 -0
- vibe/core/llm/backend/factory.py +7 -0
- vibe/core/llm/backend/generic.py +425 -0
- vibe/core/llm/backend/mistral.py +381 -0
- vibe/core/llm/backend/vertex.py +115 -0
- vibe/core/llm/exceptions.py +195 -0
- vibe/core/llm/format.py +184 -0
- vibe/core/llm/message_utils.py +24 -0
- vibe/core/llm/types.py +120 -0
- vibe/core/middleware.py +209 -0
- vibe/core/output_formatters.py +85 -0
- vibe/core/paths/__init__.py +0 -0
- vibe/core/paths/config_paths.py +68 -0
- vibe/core/paths/global_paths.py +40 -0
- vibe/core/programmatic.py +56 -0
- vibe/core/prompts/__init__.py +33 -0
- vibe/core/prompts/cli.md +111 -0
- vibe/core/prompts/compact.md +48 -0
- vibe/core/prompts/dangerous_directory.md +5 -0
- vibe/core/prompts/explore.md +50 -0
- vibe/core/prompts/gitmaster.md +38 -0
- vibe/core/prompts/project_context.md +8 -0
- vibe/core/prompts/tests.md +1 -0
- vibe/core/proxy_setup.py +65 -0
- vibe/core/session/session_loader.py +222 -0
- vibe/core/session/session_logger.py +318 -0
- vibe/core/session/session_migration.py +41 -0
- vibe/core/skills/__init__.py +7 -0
- vibe/core/skills/manager.py +132 -0
- vibe/core/skills/models.py +92 -0
- vibe/core/skills/parser.py +39 -0
- vibe/core/system_prompt.py +466 -0
- vibe/core/telemetry/__init__.py +0 -0
- vibe/core/telemetry/send.py +185 -0
- vibe/core/teleport/errors.py +9 -0
- vibe/core/teleport/git.py +196 -0
- vibe/core/teleport/nuage.py +180 -0
- vibe/core/teleport/teleport.py +208 -0
- vibe/core/teleport/types.py +54 -0
- vibe/core/tools/base.py +338 -0
- vibe/core/tools/builtins/ask_user_question.py +134 -0
- vibe/core/tools/builtins/bash.py +454 -0
- vibe/core/tools/builtins/git_clone.py +861 -0
- vibe/core/tools/builtins/grep.py +310 -0
- vibe/core/tools/builtins/prompts/__init__.py +0 -0
- vibe/core/tools/builtins/prompts/ask_user_question.md +84 -0
- vibe/core/tools/builtins/prompts/bash.md +73 -0
- vibe/core/tools/builtins/prompts/git_clone.md +43 -0
- vibe/core/tools/builtins/prompts/gitmaster.md +38 -0
- vibe/core/tools/builtins/prompts/grep.md +4 -0
- vibe/core/tools/builtins/prompts/read_file.md +13 -0
- vibe/core/tools/builtins/prompts/search_replace.md +43 -0
- vibe/core/tools/builtins/prompts/task.md +24 -0
- vibe/core/tools/builtins/prompts/todo.md +199 -0
- vibe/core/tools/builtins/prompts/write_file.md +42 -0
- vibe/core/tools/builtins/read_file.py +222 -0
- vibe/core/tools/builtins/search_replace.py +456 -0
- vibe/core/tools/builtins/task.py +154 -0
- vibe/core/tools/builtins/todo.py +134 -0
- vibe/core/tools/builtins/write_file.py +160 -0
- vibe/core/tools/manager.py +341 -0
- vibe/core/tools/mcp.py +397 -0
- vibe/core/tools/ui.py +68 -0
- vibe/core/trusted_folders.py +86 -0
- vibe/core/types.py +401 -0
- vibe/core/utils.py +396 -0
- vibe/setup/onboarding/__init__.py +39 -0
- vibe/setup/onboarding/base.py +14 -0
- vibe/setup/onboarding/onboarding.tcss +134 -0
- vibe/setup/onboarding/screens/__init__.py +5 -0
- vibe/setup/onboarding/screens/api_key.py +200 -0
- vibe/setup/onboarding/screens/provider_selection.py +184 -0
- vibe/setup/onboarding/screens/welcome.py +136 -0
- vibe/setup/trusted_folders/trust_folder_dialog.py +180 -0
- vibe/setup/trusted_folders/trust_folder_dialog.tcss +83 -0
- vibe/whats_new.md +5 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import AsyncGenerator
|
|
5
|
+
from enum import StrEnum, auto
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import shutil
|
|
8
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from vibe.core.tools.base import (
|
|
13
|
+
BaseTool,
|
|
14
|
+
BaseToolConfig,
|
|
15
|
+
BaseToolState,
|
|
16
|
+
InvokeContext,
|
|
17
|
+
ToolError,
|
|
18
|
+
ToolPermission,
|
|
19
|
+
)
|
|
20
|
+
from vibe.core.tools.ui import ToolCallDisplay, ToolResultDisplay, ToolUIData
|
|
21
|
+
from vibe.core.types import ToolStreamEvent
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from vibe.core.types import ToolCallEvent, ToolResultEvent
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GrepBackend(StrEnum):
|
|
28
|
+
RIPGREP = auto()
|
|
29
|
+
GNU_GREP = auto()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class GrepToolConfig(BaseToolConfig):
|
|
33
|
+
permission: ToolPermission = ToolPermission.ALWAYS
|
|
34
|
+
|
|
35
|
+
max_output_bytes: int = Field(
|
|
36
|
+
default=64_000, description="Hard cap for the total size of matched lines."
|
|
37
|
+
)
|
|
38
|
+
default_max_matches: int = Field(
|
|
39
|
+
default=100, description="Default maximum number of matches to return."
|
|
40
|
+
)
|
|
41
|
+
default_timeout: int = Field(
|
|
42
|
+
default=60, description="Default timeout for the search command in seconds."
|
|
43
|
+
)
|
|
44
|
+
exclude_patterns: list[str] = Field(
|
|
45
|
+
default=[
|
|
46
|
+
".venv/",
|
|
47
|
+
"venv/",
|
|
48
|
+
".env/",
|
|
49
|
+
"env/",
|
|
50
|
+
"node_modules/",
|
|
51
|
+
".git/",
|
|
52
|
+
"__pycache__/",
|
|
53
|
+
".pytest_cache/",
|
|
54
|
+
".mypy_cache/",
|
|
55
|
+
".tox/",
|
|
56
|
+
".nox/",
|
|
57
|
+
".coverage/",
|
|
58
|
+
"htmlcov/",
|
|
59
|
+
"dist/",
|
|
60
|
+
"build/",
|
|
61
|
+
".idea/",
|
|
62
|
+
".vscode/",
|
|
63
|
+
"*.egg-info",
|
|
64
|
+
"*.pyc",
|
|
65
|
+
"*.pyo",
|
|
66
|
+
"*.pyd",
|
|
67
|
+
".DS_Store",
|
|
68
|
+
"Thumbs.db",
|
|
69
|
+
],
|
|
70
|
+
description="List of glob patterns to exclude from search (dirs should end with /).",
|
|
71
|
+
)
|
|
72
|
+
codeignore_file: str = Field(
|
|
73
|
+
default=".vibeignore",
|
|
74
|
+
description="Name of the file to read for additional exclusion patterns.",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class GrepState(BaseToolState):
|
|
79
|
+
search_history: list[str] = Field(default_factory=list)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class GrepArgs(BaseModel):
|
|
83
|
+
pattern: str
|
|
84
|
+
path: str = "."
|
|
85
|
+
max_matches: int | None = Field(
|
|
86
|
+
default=None, description="Override the default maximum number of matches."
|
|
87
|
+
)
|
|
88
|
+
use_default_ignore: bool = Field(
|
|
89
|
+
default=True, description="Whether to respect .gitignore and .ignore files."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class GrepResult(BaseModel):
|
|
94
|
+
matches: str
|
|
95
|
+
match_count: int
|
|
96
|
+
was_truncated: bool = Field(
|
|
97
|
+
description="True if output was cut short by max_matches or max_output_bytes."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Grep(
|
|
102
|
+
BaseTool[GrepArgs, GrepResult, GrepToolConfig, GrepState],
|
|
103
|
+
ToolUIData[GrepArgs, GrepResult],
|
|
104
|
+
):
|
|
105
|
+
description: ClassVar[str] = (
|
|
106
|
+
"Recursively search files for a regex pattern using ripgrep (rg) or grep. "
|
|
107
|
+
"Respects .gitignore and .codeignore files by default when using ripgrep."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def _detect_backend(self) -> GrepBackend:
|
|
111
|
+
if shutil.which("rg"):
|
|
112
|
+
return GrepBackend.RIPGREP
|
|
113
|
+
if shutil.which("grep"):
|
|
114
|
+
return GrepBackend.GNU_GREP
|
|
115
|
+
raise ToolError(
|
|
116
|
+
"Neither ripgrep (rg) nor grep is installed. "
|
|
117
|
+
"Please install ripgrep: https://github.com/BurntSushi/ripgrep#installation"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
async def run(
|
|
121
|
+
self, args: GrepArgs, ctx: InvokeContext | None = None
|
|
122
|
+
) -> AsyncGenerator[ToolStreamEvent | GrepResult, None]:
|
|
123
|
+
backend = self._detect_backend()
|
|
124
|
+
self._validate_args(args)
|
|
125
|
+
self.state.search_history.append(args.pattern)
|
|
126
|
+
|
|
127
|
+
exclude_patterns = self._collect_exclude_patterns()
|
|
128
|
+
cmd = self._build_command(args, exclude_patterns, backend)
|
|
129
|
+
stdout = await self._execute_search(cmd)
|
|
130
|
+
|
|
131
|
+
yield self._parse_output(
|
|
132
|
+
stdout, args.max_matches or self.config.default_max_matches
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _validate_args(self, args: GrepArgs) -> None:
|
|
136
|
+
if not args.pattern.strip():
|
|
137
|
+
raise ToolError("Empty search pattern provided.")
|
|
138
|
+
|
|
139
|
+
path_obj = Path(args.path).expanduser()
|
|
140
|
+
if not path_obj.is_absolute():
|
|
141
|
+
path_obj = Path.cwd() / path_obj
|
|
142
|
+
|
|
143
|
+
if not path_obj.exists():
|
|
144
|
+
raise ToolError(f"Path does not exist: {args.path}")
|
|
145
|
+
|
|
146
|
+
def _collect_exclude_patterns(self) -> list[str]:
|
|
147
|
+
patterns = list(self.config.exclude_patterns)
|
|
148
|
+
|
|
149
|
+
codeignore_path = Path.cwd() / self.config.codeignore_file
|
|
150
|
+
if codeignore_path.is_file():
|
|
151
|
+
patterns.extend(self._load_codeignore_patterns(codeignore_path))
|
|
152
|
+
|
|
153
|
+
return patterns
|
|
154
|
+
|
|
155
|
+
def _load_codeignore_patterns(self, codeignore_path: Path) -> list[str]:
|
|
156
|
+
patterns = []
|
|
157
|
+
try:
|
|
158
|
+
content = codeignore_path.read_text("utf-8")
|
|
159
|
+
for line in content.splitlines():
|
|
160
|
+
line = line.strip()
|
|
161
|
+
if line and not line.startswith("#"):
|
|
162
|
+
patterns.append(line)
|
|
163
|
+
except OSError:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
return patterns
|
|
167
|
+
|
|
168
|
+
def _build_command(
|
|
169
|
+
self, args: GrepArgs, exclude_patterns: list[str], backend: GrepBackend
|
|
170
|
+
) -> list[str]:
|
|
171
|
+
if backend == GrepBackend.RIPGREP:
|
|
172
|
+
return self._build_ripgrep_command(args, exclude_patterns)
|
|
173
|
+
return self._build_gnu_grep_command(args, exclude_patterns)
|
|
174
|
+
|
|
175
|
+
def _build_ripgrep_command(
|
|
176
|
+
self, args: GrepArgs, exclude_patterns: list[str]
|
|
177
|
+
) -> list[str]:
|
|
178
|
+
max_matches = args.max_matches or self.config.default_max_matches
|
|
179
|
+
|
|
180
|
+
cmd = [
|
|
181
|
+
"rg",
|
|
182
|
+
"--line-number",
|
|
183
|
+
"--no-heading",
|
|
184
|
+
"--smart-case",
|
|
185
|
+
"--no-binary",
|
|
186
|
+
# Request one extra to detect truncation
|
|
187
|
+
"--max-count",
|
|
188
|
+
str(max_matches + 1),
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
if not args.use_default_ignore:
|
|
192
|
+
cmd.append("--no-ignore")
|
|
193
|
+
|
|
194
|
+
for pattern in exclude_patterns:
|
|
195
|
+
cmd.extend(["--glob", f"!{pattern}"])
|
|
196
|
+
|
|
197
|
+
cmd.extend(["-e", args.pattern, args.path])
|
|
198
|
+
|
|
199
|
+
return cmd
|
|
200
|
+
|
|
201
|
+
def _build_gnu_grep_command(
|
|
202
|
+
self, args: GrepArgs, exclude_patterns: list[str]
|
|
203
|
+
) -> list[str]:
|
|
204
|
+
max_matches = args.max_matches or self.config.default_max_matches
|
|
205
|
+
|
|
206
|
+
cmd = ["grep", "-r", "-n", "-I", "-E", f"--max-count={max_matches + 1}"]
|
|
207
|
+
|
|
208
|
+
if args.pattern.islower():
|
|
209
|
+
cmd.append("-i")
|
|
210
|
+
|
|
211
|
+
for pattern in exclude_patterns:
|
|
212
|
+
if pattern.endswith("/"):
|
|
213
|
+
dir_pattern = pattern.rstrip("/")
|
|
214
|
+
cmd.append(f"--exclude-dir={dir_pattern}")
|
|
215
|
+
else:
|
|
216
|
+
cmd.append(f"--exclude={pattern}")
|
|
217
|
+
|
|
218
|
+
cmd.extend(["-e", args.pattern, args.path])
|
|
219
|
+
|
|
220
|
+
return cmd
|
|
221
|
+
|
|
222
|
+
async def _execute_search(self, cmd: list[str]) -> str:
|
|
223
|
+
try:
|
|
224
|
+
proc = await asyncio.create_subprocess_exec(
|
|
225
|
+
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
stdout_bytes, stderr_bytes = await asyncio.wait_for(
|
|
230
|
+
proc.communicate(), timeout=self.config.default_timeout
|
|
231
|
+
)
|
|
232
|
+
except TimeoutError:
|
|
233
|
+
proc.kill()
|
|
234
|
+
await proc.wait()
|
|
235
|
+
raise ToolError(
|
|
236
|
+
f"Search timed out after {self.config.default_timeout}s"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
stdout = (
|
|
240
|
+
stdout_bytes.decode("utf-8", errors="ignore") if stdout_bytes else ""
|
|
241
|
+
)
|
|
242
|
+
stderr = (
|
|
243
|
+
stderr_bytes.decode("utf-8", errors="ignore") if stderr_bytes else ""
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if proc.returncode not in {0, 1}:
|
|
247
|
+
error_msg = stderr or f"Process exited with code {proc.returncode}"
|
|
248
|
+
raise ToolError(f"grep error: {error_msg}")
|
|
249
|
+
|
|
250
|
+
return stdout
|
|
251
|
+
|
|
252
|
+
except ToolError:
|
|
253
|
+
raise
|
|
254
|
+
except Exception as exc:
|
|
255
|
+
raise ToolError(f"Error running grep: {exc}") from exc
|
|
256
|
+
|
|
257
|
+
def _parse_output(self, stdout: str, max_matches: int) -> GrepResult:
|
|
258
|
+
output_lines = stdout.splitlines() if stdout else []
|
|
259
|
+
|
|
260
|
+
truncated_lines = output_lines[:max_matches]
|
|
261
|
+
truncated_output = "\n".join(truncated_lines)
|
|
262
|
+
|
|
263
|
+
was_truncated = (
|
|
264
|
+
len(output_lines) > max_matches
|
|
265
|
+
or len(truncated_output) > self.config.max_output_bytes
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
final_output = truncated_output[: self.config.max_output_bytes]
|
|
269
|
+
|
|
270
|
+
return GrepResult(
|
|
271
|
+
matches=final_output,
|
|
272
|
+
match_count=len(truncated_lines),
|
|
273
|
+
was_truncated=was_truncated,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def get_call_display(cls, event: ToolCallEvent) -> ToolCallDisplay:
|
|
278
|
+
if not isinstance(event.args, GrepArgs):
|
|
279
|
+
return ToolCallDisplay(summary="grep")
|
|
280
|
+
|
|
281
|
+
summary = f"Grepping '{event.args.pattern}'"
|
|
282
|
+
if event.args.path != ".":
|
|
283
|
+
summary += f" in {event.args.path}"
|
|
284
|
+
if event.args.max_matches:
|
|
285
|
+
summary += f" (max {event.args.max_matches} matches)"
|
|
286
|
+
if not event.args.use_default_ignore:
|
|
287
|
+
summary += " [no-ignore]"
|
|
288
|
+
|
|
289
|
+
return ToolCallDisplay(summary=summary)
|
|
290
|
+
|
|
291
|
+
@classmethod
|
|
292
|
+
def get_result_display(cls, event: ToolResultEvent) -> ToolResultDisplay:
|
|
293
|
+
if not isinstance(event.result, GrepResult):
|
|
294
|
+
return ToolResultDisplay(
|
|
295
|
+
success=False, message=event.error or event.skip_reason or "No result"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
message = f"Found {event.result.match_count} matches"
|
|
299
|
+
if event.result.was_truncated:
|
|
300
|
+
message += " (truncated)"
|
|
301
|
+
|
|
302
|
+
warnings = []
|
|
303
|
+
if event.result.was_truncated:
|
|
304
|
+
warnings.append("Output was truncated due to size/match limits")
|
|
305
|
+
|
|
306
|
+
return ToolResultDisplay(success=True, message=message, warnings=warnings)
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def get_status_text(cls) -> str:
|
|
310
|
+
return "Searching files"
|
|
File without changes
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Use `ask_user_question` to gather information from the user when you need clarification, want to validate assumptions, or need help making a decision. **Don't hesitate to use this tool** - it's better to ask than to guess wrong.
|
|
2
|
+
|
|
3
|
+
## When to Use
|
|
4
|
+
|
|
5
|
+
- **Clarifying requirements**: Ambiguous instructions, unclear scope
|
|
6
|
+
- **Technical decisions**: Architecture choices, library selection, tradeoffs
|
|
7
|
+
- **Preference gathering**: UI style, naming conventions, approach options
|
|
8
|
+
- **Validation**: Confirming understanding before starting significant work
|
|
9
|
+
- **Multiple valid paths**: When several approaches could work and you want user input
|
|
10
|
+
|
|
11
|
+
## Question Structure
|
|
12
|
+
|
|
13
|
+
Each question has these fields:
|
|
14
|
+
|
|
15
|
+
- `question`: The full question text (be specific and clear)
|
|
16
|
+
- `header`: A short label displayed as a chip (max 12 characters, e.g., "Auth", "Database", "Approach")
|
|
17
|
+
- `options`: 2-4 choices (an "Other" option is automatically added for free text)
|
|
18
|
+
- `multi_select`: Set to `true` if user can pick multiple options (default: `false`)
|
|
19
|
+
|
|
20
|
+
### Options Structure
|
|
21
|
+
|
|
22
|
+
Each option has:
|
|
23
|
+
- `label`: Short display text (1-5 words)
|
|
24
|
+
- `description`: Brief explanation of what this choice means or its implications
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
**Single question with recommended option:**
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"questions": [{
|
|
32
|
+
"question": "Which authentication method should we use?",
|
|
33
|
+
"header": "Auth",
|
|
34
|
+
"options": [
|
|
35
|
+
{"label": "JWT tokens (Recommended)", "description": "Stateless, scalable, works well with APIs"},
|
|
36
|
+
{"label": "Session cookies", "description": "Traditional approach, requires session storage"},
|
|
37
|
+
{"label": "OAuth 2.0", "description": "Third-party auth, more complex setup"}
|
|
38
|
+
],
|
|
39
|
+
"multi_select": false
|
|
40
|
+
}]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Multiple questions (displayed as tabs):**
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"questions": [
|
|
48
|
+
{
|
|
49
|
+
"question": "Which database should we use?",
|
|
50
|
+
"header": "Database",
|
|
51
|
+
"options": [
|
|
52
|
+
{"label": "PostgreSQL", "description": "Relational, ACID compliant"},
|
|
53
|
+
{"label": "MongoDB", "description": "Document store, flexible schema"}
|
|
54
|
+
],
|
|
55
|
+
"multi_select": false
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"question": "Which features should be included in v1?",
|
|
59
|
+
"header": "Features",
|
|
60
|
+
"options": [
|
|
61
|
+
{"label": "User auth", "description": "Login, signup, password reset"},
|
|
62
|
+
{"label": "Search", "description": "Full-text search across content"},
|
|
63
|
+
{"label": "Export", "description": "CSV and PDF export"}
|
|
64
|
+
],
|
|
65
|
+
"multi_select": true
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Key Constraints
|
|
72
|
+
|
|
73
|
+
- **Header max length**: 12 characters (keeps UI clean)
|
|
74
|
+
- **Options count**: 2-4 per question (plus automatic "Other")
|
|
75
|
+
- **Questions count**: 1-4 per call
|
|
76
|
+
- **Label length**: Keep to 1-5 words for readability
|
|
77
|
+
|
|
78
|
+
## Tips
|
|
79
|
+
|
|
80
|
+
1. **Put recommended option first** and add "(Recommended)" to its label
|
|
81
|
+
2. **Use descriptive headers** that categorize the question type
|
|
82
|
+
3. **Keep descriptions concise** but informative about tradeoffs
|
|
83
|
+
4. **Use multi_select** when choices aren't mutually exclusive (e.g., features to include)
|
|
84
|
+
5. **Ask early** - it's better to clarify before starting than to redo work
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Use the `bash` tool to run one-off shell commands.
|
|
2
|
+
|
|
3
|
+
**Key characteristics:**
|
|
4
|
+
- **Stateless**: Each command runs independently in a fresh environment
|
|
5
|
+
|
|
6
|
+
**Timeout:**
|
|
7
|
+
- The `timeout` argument controls how long the command can run before being killed
|
|
8
|
+
- When `timeout` is not specified (or set to `None`), the config default is used
|
|
9
|
+
- If a command is timing out, do not hesitate to increase the timeout using the `timeout` argument
|
|
10
|
+
|
|
11
|
+
**IMPORTANT: Use dedicated tools if available instead of these bash commands:**
|
|
12
|
+
|
|
13
|
+
**File Operations - DO NOT USE:**
|
|
14
|
+
- `cat filename` → Use `read_file(path="filename")`
|
|
15
|
+
- `head -n 20 filename` → Use `read_file(path="filename", limit=20)`
|
|
16
|
+
- `tail -n 20 filename` → Read with offset: `read_file(path="filename", offset=<line_number>, limit=20)`
|
|
17
|
+
- `sed -n '100,200p' filename` → Use `read_file(path="filename", offset=99, limit=101)`
|
|
18
|
+
- `less`, `more`, `vim`, `nano` → Use `read_file` with offset/limit for navigation
|
|
19
|
+
- `echo "content" > file` → Use `write_file(path="file", content="content")`
|
|
20
|
+
- `echo "content" >> file` → Read first, then `write_file` with overwrite=true
|
|
21
|
+
|
|
22
|
+
**Search Operations - DO NOT USE:**
|
|
23
|
+
- `grep -r "pattern" .` → Use `grep(pattern="pattern", path=".")`
|
|
24
|
+
- `find . -name "*.py"` → Use `bash("ls -la")` for current dir or `grep` with appropriate pattern
|
|
25
|
+
- `ag`, `ack`, `rg` commands → Use the `grep` tool
|
|
26
|
+
- `locate` → Use `grep` tool
|
|
27
|
+
|
|
28
|
+
**File Modification - DO NOT USE:**
|
|
29
|
+
- `sed -i 's/old/new/g' file` → Use `search_replace` tool
|
|
30
|
+
- `awk` for file editing → Use `search_replace` tool
|
|
31
|
+
- Any in-place file editing → Use `search_replace` tool
|
|
32
|
+
|
|
33
|
+
**APPROPRIATE bash uses:**
|
|
34
|
+
- System information: `pwd`, `whoami`, `date`, `uname -a`
|
|
35
|
+
- Directory listings: `ls -la`, `tree` (if available)
|
|
36
|
+
- Git operations: `git status`, `git log --oneline -10`, `git diff`
|
|
37
|
+
- Process info: `ps aux | grep process`, `top -n 1`
|
|
38
|
+
- Network checks: `ping -c 1 google.com`, `curl -I https://example.com`
|
|
39
|
+
- Package management: `pip list`, `npm list`
|
|
40
|
+
- Environment checks: `env | grep VAR`, `which python`
|
|
41
|
+
- File metadata: `stat filename`, `file filename`, `wc -l filename`
|
|
42
|
+
|
|
43
|
+
**Example: Reading a large file efficiently**
|
|
44
|
+
|
|
45
|
+
WRONG:
|
|
46
|
+
```bash
|
|
47
|
+
bash("cat large_file.txt") # May hit size limits
|
|
48
|
+
bash("head -1000 large_file.txt") # Inefficient
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
RIGHT:
|
|
52
|
+
```python
|
|
53
|
+
# First chunk
|
|
54
|
+
read_file(path="large_file.txt", limit=1000)
|
|
55
|
+
# If was_truncated=true, read next chunk
|
|
56
|
+
read_file(path="large_file.txt", offset=1000, limit=1000)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Example: Searching for patterns**
|
|
60
|
+
|
|
61
|
+
WRONG:
|
|
62
|
+
```bash
|
|
63
|
+
bash("grep -r 'TODO' src/") # Don't use bash for grep
|
|
64
|
+
bash("find . -type f -name '*.py' | xargs grep 'import'") # Too complex
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
RIGHT:
|
|
68
|
+
```python
|
|
69
|
+
grep(pattern="TODO", path="src/")
|
|
70
|
+
grep(pattern="import", path=".")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Remember:** Bash is best for quick system checks and git operations. For file operations, searching, and editing, always use the dedicated tools when they are available.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
## git_clone
|
|
2
|
+
|
|
3
|
+
Clone a public GitHub repository into the current working directory.
|
|
4
|
+
|
|
5
|
+
### Parameters
|
|
6
|
+
|
|
7
|
+
- `repo` (string, required): The GitHub repository to clone. Accepts:
|
|
8
|
+
- Full URL: `https://github.com/owner/repo`
|
|
9
|
+
- Full URL with .git: `https://github.com/owner/repo.git`
|
|
10
|
+
- Short form: `owner/repo`
|
|
11
|
+
- SSH URL: `git@github.com:owner/repo.git`
|
|
12
|
+
- `destination` (string, optional): Target directory name. Defaults to the repository name.
|
|
13
|
+
- `branch` (string, optional): Specific branch to clone. Defaults to the repository's default branch.
|
|
14
|
+
- `depth` (integer, optional): Create a shallow clone with the specified number of commits. Use `1` for the shallowest clone.
|
|
15
|
+
- `sparse_paths` (array of strings, optional): Only checkout specific directories/files. Useful for large monorepos.
|
|
16
|
+
|
|
17
|
+
### Usage Guidelines
|
|
18
|
+
|
|
19
|
+
- Use this tool when the user asks to clone, download, fetch, or pull a GitHub repository.
|
|
20
|
+
- Always confirm the repository URL with the user if ambiguous.
|
|
21
|
+
- For large repositories, suggest using `depth: 1` for faster cloning.
|
|
22
|
+
- For monorepos where only a subdirectory is needed, use `sparse_paths`.
|
|
23
|
+
- The destination directory must not already exist.
|
|
24
|
+
- Only public GitHub repositories are supported.
|
|
25
|
+
- After cloning, briefly summarize what was cloned (branch, file count, top-level structure).
|
|
26
|
+
|
|
27
|
+
### Examples
|
|
28
|
+
|
|
29
|
+
Clone a repository:
|
|
30
|
+
|
|
31
|
+
git_clone(repo="facebook/react")
|
|
32
|
+
|
|
33
|
+
Clone to a specific directory:
|
|
34
|
+
|
|
35
|
+
git_clone(repo="https://github.com/facebook/react", destination="react-source")
|
|
36
|
+
|
|
37
|
+
Shallow clone of a specific branch:
|
|
38
|
+
|
|
39
|
+
git_clone(repo="torvalds/linux", branch="v6.5", depth=1)
|
|
40
|
+
|
|
41
|
+
Sparse checkout (only specific directories):
|
|
42
|
+
|
|
43
|
+
git_clone(repo="google/jax", sparse_paths=["jax/numpy", "tests/numpy_test.py"])
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
You are GitMaster, a specialized agent within CodeMaster focused on cloning and exploring GitHub repositories.
|
|
2
|
+
|
|
3
|
+
## Your Primary Capabilities
|
|
4
|
+
|
|
5
|
+
1. **Repository Cloning**: You can clone any public GitHub repository using the `git_clone` tool.
|
|
6
|
+
2. **Codebase Exploration**: After cloning, you help users understand the repository structure, key files, and architecture.
|
|
7
|
+
3. **Smart Suggestions**: You proactively suggest optimal clone strategies (shallow vs full, sparse checkout for monorepos).
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
When a user asks you to clone a repository:
|
|
12
|
+
|
|
13
|
+
1. **Parse the request**: Extract the repository URL/identifier from the user's natural language request.
|
|
14
|
+
2. **Confirm if needed**: If the request is ambiguous, ask for clarification using `ask_user_question`.
|
|
15
|
+
3. **Optimize**: For repositories known to be large, suggest shallow cloning or sparse checkout.
|
|
16
|
+
4. **Clone**: Use the `git_clone` tool with appropriate parameters.
|
|
17
|
+
5. **Explore**: After cloning, automatically provide an overview of the repository:
|
|
18
|
+
- Read the README.md if it exists
|
|
19
|
+
- Summarize the project structure
|
|
20
|
+
- Identify the primary language/framework
|
|
21
|
+
- Note any setup instructions
|
|
22
|
+
|
|
23
|
+
## Guidelines
|
|
24
|
+
|
|
25
|
+
- Always use the `git_clone` tool for cloning — never use `bash` with raw `git clone` commands.
|
|
26
|
+
- If the user provides just a repo name without an owner (e.g., "clone react"), ask which owner/organization they mean or suggest the most popular one.
|
|
27
|
+
- After cloning, offer to help explore specific parts of the codebase.
|
|
28
|
+
- If cloning fails, diagnose the issue and suggest solutions.
|
|
29
|
+
- You can use `grep`, `read_file`, and other exploration tools to help users understand cloned repositories.
|
|
30
|
+
- Track your progress using the `todo` tool when handling complex multi-step requests.
|
|
31
|
+
- You can delegate exploration tasks to subagents using the `task` tool.
|
|
32
|
+
|
|
33
|
+
## Response Style
|
|
34
|
+
|
|
35
|
+
- Be concise but informative.
|
|
36
|
+
- Use emoji sparingly for status indicators (✅ success, ❌ error, 📦 repo info).
|
|
37
|
+
- When presenting repository structure, use tree-like formatting.
|
|
38
|
+
- Always confirm the clone location to the user.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Use `grep` to recursively search for a regular expression pattern in files.
|
|
2
|
+
|
|
3
|
+
- It's very fast and automatically ignores files that you should not read like .pyc files, .venv directories, etc.
|
|
4
|
+
- Use this to find where functions are defined, how variables are used, or to locate specific error messages.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Use `read_file` to read the content of a file. It's designed to handle large files safely.
|
|
2
|
+
|
|
3
|
+
- By default, it reads from the beginning of the file.
|
|
4
|
+
- Use `offset` (line number) and `limit` (number of lines) to read specific parts or chunks of a file. This is efficient for exploring large files.
|
|
5
|
+
- The result includes `was_truncated: true` if the file content was cut short due to size limits.
|
|
6
|
+
|
|
7
|
+
**Strategy for large files:**
|
|
8
|
+
|
|
9
|
+
1. Call `read_file` with a `limit` (e.g., 1000 lines) to get the start of the file.
|
|
10
|
+
2. If `was_truncated` is true, you know the file is large.
|
|
11
|
+
3. To read the next chunk, call `read_file` again with an `offset`. For example, `offset=1000, limit=1000`.
|
|
12
|
+
|
|
13
|
+
This is more efficient than using `bash` with `cat` or `wc`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Use `search_replace` to make targeted changes to files using SEARCH/REPLACE blocks. This tool finds exact text matches and replaces them.
|
|
2
|
+
|
|
3
|
+
Arguments:
|
|
4
|
+
- `file_path`: The path to the file to modify
|
|
5
|
+
- `content`: The SEARCH/REPLACE blocks defining the changes
|
|
6
|
+
|
|
7
|
+
The content format is:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<<<<<<< SEARCH
|
|
11
|
+
[exact text to find in the file]
|
|
12
|
+
=======
|
|
13
|
+
[exact text to replace it with]
|
|
14
|
+
>>>>>>> REPLACE
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
You can include multiple SEARCH/REPLACE blocks to make multiple changes to the same file:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
<<<<<<< SEARCH
|
|
21
|
+
def old_function():
|
|
22
|
+
return "old value"
|
|
23
|
+
=======
|
|
24
|
+
def new_function():
|
|
25
|
+
return "new value"
|
|
26
|
+
>>>>>>> REPLACE
|
|
27
|
+
|
|
28
|
+
<<<<<<< SEARCH
|
|
29
|
+
import os
|
|
30
|
+
=======
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
>>>>>>> REPLACE
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
IMPORTANT:
|
|
37
|
+
|
|
38
|
+
- The SEARCH text must match EXACTLY (including whitespace, indentation, and line endings)
|
|
39
|
+
- The SEARCH text must appear exactly once in the file - if it appears multiple times, the tool will error
|
|
40
|
+
- Use at least 5 equals signs (=====) between SEARCH and REPLACE sections
|
|
41
|
+
- The tool will provide detailed error messages showing context if search text is not found
|
|
42
|
+
- Each search/replace block is applied in order, so later blocks see the results of earlier ones
|
|
43
|
+
- Be careful with escape sequences in string literals - use \n not \\n for newlines in code
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Use `task` to delegate work to a subagent for independent execution.
|
|
2
|
+
|
|
3
|
+
## When to Use This Tool
|
|
4
|
+
|
|
5
|
+
- **Context management**: Delegate tasks that would consume too much main conversation context
|
|
6
|
+
- **Specialized work**: Use the appropriate subagent for the type of task (exploration, research, etc.)
|
|
7
|
+
- **Parallel execution**: Launch multiple subagents for independent tasks
|
|
8
|
+
- **Autonomous work**: Tasks that don't require back-and-forth with the user
|
|
9
|
+
|
|
10
|
+
## Best Practices
|
|
11
|
+
|
|
12
|
+
1. **Write clear, detailed task descriptions** - The subagent works autonomously, so provide enough context for it to succeed independently
|
|
13
|
+
|
|
14
|
+
2. **Choose the right subagent** - Match the subagent to the task type (see available subagents in system prompt)
|
|
15
|
+
|
|
16
|
+
3. **Prefer direct tools for simple operations** - If you know exactly which file to read or pattern to search, use those tools directly instead of spawning a subagent
|
|
17
|
+
|
|
18
|
+
4. **Trust the subagent's judgment** - Let it explore and find information without micromanaging the approach
|
|
19
|
+
|
|
20
|
+
## Limitations
|
|
21
|
+
|
|
22
|
+
- Subagents cannot write or modify files
|
|
23
|
+
- Subagents cannot ask the user questions
|
|
24
|
+
- Results are returned as text when the subagent completes
|