crabcode 0.1.6__py3-none-win_amd64.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.
Files changed (205) hide show
  1. crabcode-0.1.6.dist-info/METADATA +63 -0
  2. crabcode-0.1.6.dist-info/RECORD +205 -0
  3. crabcode-0.1.6.dist-info/WHEEL +4 -0
  4. crabcode-0.1.6.dist-info/entry_points.txt +2 -0
  5. crabcode_cli/__init__.py +7 -0
  6. crabcode_cli/__main__.py +4 -0
  7. crabcode_cli/_native/crabcode-computer-use-host.exe +0 -0
  8. crabcode_cli/_native/runtime.json +6 -0
  9. crabcode_cli/app.py +776 -0
  10. crabcode_cli/banner.py +39 -0
  11. crabcode_cli/computer_use.py +286 -0
  12. crabcode_cli/model_manager.py +649 -0
  13. crabcode_cli/model_picker.py +168 -0
  14. crabcode_cli/pipe.py +148 -0
  15. crabcode_cli/render/__init__.py +0 -0
  16. crabcode_cli/render/messages.py +19 -0
  17. crabcode_cli/repl.py +4841 -0
  18. crabcode_cli/session_picker.py +239 -0
  19. crabcode_core/__init__.py +7 -0
  20. crabcode_core/_version.py +3 -0
  21. crabcode_core/_windows_process.py +68 -0
  22. crabcode_core/agent_manager.py +1731 -0
  23. crabcode_core/api/__init__.py +20 -0
  24. crabcode_core/api/anthropic_adapter.py +648 -0
  25. crabcode_core/api/azure_adapter.py +69 -0
  26. crabcode_core/api/base.py +233 -0
  27. crabcode_core/api/codex_adapter.py +1327 -0
  28. crabcode_core/api/error_logging.py +132 -0
  29. crabcode_core/api/gemini_adapter.py +26 -0
  30. crabcode_core/api/model_info.py +257 -0
  31. crabcode_core/api/network.py +62 -0
  32. crabcode_core/api/ollama_adapter.py +27 -0
  33. crabcode_core/api/openai_adapter.py +323 -0
  34. crabcode_core/api/registry.py +58 -0
  35. crabcode_core/command_line.py +18 -0
  36. crabcode_core/compact/__init__.py +17 -0
  37. crabcode_core/compact/compact.py +514 -0
  38. crabcode_core/compact/context_tokens.py +165 -0
  39. crabcode_core/computer_use_mode.py +38 -0
  40. crabcode_core/computer_use_observation.py +144 -0
  41. crabcode_core/config/__init__.py +3 -0
  42. crabcode_core/config/claudemd.py +61 -0
  43. crabcode_core/config/manager.py +237 -0
  44. crabcode_core/config/model_catalog.py +256 -0
  45. crabcode_core/events.py +4896 -0
  46. crabcode_core/file_lock.py +81 -0
  47. crabcode_core/filesystem.py +22 -0
  48. crabcode_core/goal.py +149 -0
  49. crabcode_core/hooks/__init__.py +5 -0
  50. crabcode_core/hooks/manager.py +290 -0
  51. crabcode_core/image_io.py +111 -0
  52. crabcode_core/io_worker.py +211 -0
  53. crabcode_core/logging_utils.py +168 -0
  54. crabcode_core/lsp/__init__.py +6 -0
  55. crabcode_core/lsp/client.py +786 -0
  56. crabcode_core/lsp/diagnostics.py +150 -0
  57. crabcode_core/lsp/manager.py +583 -0
  58. crabcode_core/mcp/__init__.py +4 -0
  59. crabcode_core/mcp/client.py +169 -0
  60. crabcode_core/mcp/config.py +57 -0
  61. crabcode_core/path_validation.py +34 -0
  62. crabcode_core/paths.py +24 -0
  63. crabcode_core/peer/__init__.py +10 -0
  64. crabcode_core/peer/runtime.py +518 -0
  65. crabcode_core/permissions/__init__.py +7 -0
  66. crabcode_core/permissions/ai_reviewer.py +184 -0
  67. crabcode_core/permissions/manager.py +187 -0
  68. crabcode_core/plan/__init__.py +5 -0
  69. crabcode_core/plan/executor.py +270 -0
  70. crabcode_core/plan/types.py +169 -0
  71. crabcode_core/prompts/__init__.py +0 -0
  72. crabcode_core/prompts/blocks.py +10 -0
  73. crabcode_core/prompts/context.py +229 -0
  74. crabcode_core/prompts/library.py +92 -0
  75. crabcode_core/prompts/profile.py +79 -0
  76. crabcode_core/prompts/system.py +356 -0
  77. crabcode_core/prompts/templates.py +104 -0
  78. crabcode_core/query/__init__.py +0 -0
  79. crabcode_core/query/computer_use_history.py +83 -0
  80. crabcode_core/query/loop.py +2446 -0
  81. crabcode_core/query/retry.py +116 -0
  82. crabcode_core/schedule/__init__.py +13 -0
  83. crabcode_core/schedule/manager.py +631 -0
  84. crabcode_core/schedule/models.py +204 -0
  85. crabcode_core/schedule/store.py +558 -0
  86. crabcode_core/schedule/timing.py +287 -0
  87. crabcode_core/session/__init__.py +12 -0
  88. crabcode_core/session/export.py +141 -0
  89. crabcode_core/session/meta_db.py +684 -0
  90. crabcode_core/session/storage.py +2032 -0
  91. crabcode_core/session/title_gen.py +52 -0
  92. crabcode_core/skills/__init__.py +6 -0
  93. crabcode_core/skills/context.py +23 -0
  94. crabcode_core/skills/loader.py +214 -0
  95. crabcode_core/skills/matcher.py +203 -0
  96. crabcode_core/snapshot/__init__.py +21 -0
  97. crabcode_core/snapshot/snapshot.py +436 -0
  98. crabcode_core/snapshot/tracker.py +359 -0
  99. crabcode_core/subprocess_utils.py +260 -0
  100. crabcode_core/team/__init__.py +27 -0
  101. crabcode_core/team/bridge.py +66 -0
  102. crabcode_core/team/inbox.py +237 -0
  103. crabcode_core/team/manager.py +1660 -0
  104. crabcode_core/team/message_bus.py +821 -0
  105. crabcode_core/team/models.py +191 -0
  106. crabcode_core/team/recovery.py +192 -0
  107. crabcode_core/text_io.py +52 -0
  108. crabcode_core/tools/__init__.py +112 -0
  109. crabcode_core/tools/_input_helpers.py +39 -0
  110. crabcode_core/tools/_input_schema.py +130 -0
  111. crabcode_core/tools/agent.py +413 -0
  112. crabcode_core/tools/apply_patch.py +587 -0
  113. crabcode_core/tools/ask_user.py +122 -0
  114. crabcode_core/tools/bash.py +154 -0
  115. crabcode_core/tools/browser.py +678 -0
  116. crabcode_core/tools/checklist.py +241 -0
  117. crabcode_core/tools/checkpoint.py +78 -0
  118. crabcode_core/tools/computer_use.py +599 -0
  119. crabcode_core/tools/diff_utils.py +123 -0
  120. crabcode_core/tools/file_edit.py +240 -0
  121. crabcode_core/tools/file_read.py +187 -0
  122. crabcode_core/tools/file_write.py +154 -0
  123. crabcode_core/tools/glob.py +123 -0
  124. crabcode_core/tools/goal.py +134 -0
  125. crabcode_core/tools/grep.py +247 -0
  126. crabcode_core/tools/image.py +173 -0
  127. crabcode_core/tools/image_generate.py +115 -0
  128. crabcode_core/tools/lint.py +388 -0
  129. crabcode_core/tools/loading.py +187 -0
  130. crabcode_core/tools/memory.py +456 -0
  131. crabcode_core/tools/monitor.py +677 -0
  132. crabcode_core/tools/peer.py +103 -0
  133. crabcode_core/tools/revert.py +90 -0
  134. crabcode_core/tools/schedule.py +683 -0
  135. crabcode_core/tools/skill.py +108 -0
  136. crabcode_core/tools/switch_mode.py +213 -0
  137. crabcode_core/tools/team.py +329 -0
  138. crabcode_core/tools/tool_search.py +64 -0
  139. crabcode_core/tools/web_search.py +444 -0
  140. crabcode_core/turn_details.py +64 -0
  141. crabcode_core/types/__init__.py +95 -0
  142. crabcode_core/types/config.py +434 -0
  143. crabcode_core/types/event.py +334 -0
  144. crabcode_core/types/message.py +263 -0
  145. crabcode_core/types/tool.py +208 -0
  146. crabcode_core/usage.py +289 -0
  147. crabcode_core/utf8_sanitize.py +29 -0
  148. crabcode_debugger/__init__.py +5 -0
  149. crabcode_debugger/adapters.py +332 -0
  150. crabcode_debugger/dap.py +304 -0
  151. crabcode_debugger/memory.py +1806 -0
  152. crabcode_debugger/process.py +707 -0
  153. crabcode_debugger/runtime.py +58 -0
  154. crabcode_debugger/sessions.py +387 -0
  155. crabcode_debugger/tool.py +731 -0
  156. crabcode_gateway/__init__.py +18 -0
  157. crabcode_gateway/acp/__init__.py +13 -0
  158. crabcode_gateway/acp/agent.py +1581 -0
  159. crabcode_gateway/acp/session.py +114 -0
  160. crabcode_gateway/acp/transport.py +39 -0
  161. crabcode_gateway/acp/types.py +90 -0
  162. crabcode_gateway/adapter.py +30 -0
  163. crabcode_gateway/auth.py +139 -0
  164. crabcode_gateway/computer_use.py +364 -0
  165. crabcode_gateway/document_engine.py +637 -0
  166. crabcode_gateway/document_worker.py +301 -0
  167. crabcode_gateway/event_bus.py +655 -0
  168. crabcode_gateway/grpc/__init__.py +0 -0
  169. crabcode_gateway/grpc/crabcode_pb2.py +70 -0
  170. crabcode_gateway/grpc/crabcode_pb2_grpc.py +474 -0
  171. crabcode_gateway/grpc/proto/crabcode.proto +160 -0
  172. crabcode_gateway/grpc/server.py +765 -0
  173. crabcode_gateway/middleware.py +344 -0
  174. crabcode_gateway/protocol.py +17 -0
  175. crabcode_gateway/routes/__init__.py +0 -0
  176. crabcode_gateway/routes/agent.py +392 -0
  177. crabcode_gateway/routes/auth.py +150 -0
  178. crabcode_gateway/routes/computer_use.py +101 -0
  179. crabcode_gateway/routes/config.py +1698 -0
  180. crabcode_gateway/routes/document.py +1810 -0
  181. crabcode_gateway/routes/event.py +3801 -0
  182. crabcode_gateway/routes/git_review.py +232 -0
  183. crabcode_gateway/routes/health.py +14 -0
  184. crabcode_gateway/routes/peer.py +78 -0
  185. crabcode_gateway/routes/permission.py +121 -0
  186. crabcode_gateway/routes/schedule.py +300 -0
  187. crabcode_gateway/routes/session.py +1496 -0
  188. crabcode_gateway/routes/snapshot.py +211 -0
  189. crabcode_gateway/routes/tasks.py +318 -0
  190. crabcode_gateway/routes/team.py +392 -0
  191. crabcode_gateway/routes/usage.py +49 -0
  192. crabcode_gateway/routes/workspace.py +299 -0
  193. crabcode_gateway/safe_file.py +63 -0
  194. crabcode_gateway/schemas.py +1820 -0
  195. crabcode_gateway/server.py +514 -0
  196. crabcode_gateway/session_registry.py +36 -0
  197. crabcode_gateway/task_registry.py +708 -0
  198. crabcode_gateway/turn_details.py +60 -0
  199. crabcode_search/__init__.py +11 -0
  200. crabcode_search/background.py +368 -0
  201. crabcode_search/chunker.py +517 -0
  202. crabcode_search/embedder.py +497 -0
  203. crabcode_search/indexer.py +215 -0
  204. crabcode_search/store.py +222 -0
  205. crabcode_search/tool.py +268 -0
@@ -0,0 +1,268 @@
1
+ """CodebaseSearchTool — semantic code search as a CrabCode extra tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ import json
8
+
9
+ # Must be set before ANY native library (FAISS / PyTorch) loads libomp.
10
+ os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "TRUE")
11
+ os.environ.setdefault("OMP_NUM_THREADS", "1")
12
+ os.environ.setdefault("MKL_NUM_THREADS", "1")
13
+
14
+ import asyncio
15
+ from typing import Any
16
+
17
+ from crabcode_core.tools._input_helpers import (
18
+ first_non_empty_str,
19
+ latest_user_text_for_agent_fallback,
20
+ )
21
+ from crabcode_core.types.tool import Tool, ToolContext, ToolResult
22
+
23
+ from crabcode_search.background import STATUS_FILE_NAME
24
+ from crabcode_search.embedder import create_embedder
25
+ from crabcode_search.indexer import CodebaseIndexer
26
+
27
+
28
+ class CodebaseSearchTool(Tool):
29
+ """Semantic codebase search powered by embeddings and FAISS."""
30
+
31
+ name = "CodebaseSearch"
32
+ description = "Search the codebase using natural language queries. Returns semantically relevant code snippets with file paths and line numbers. Use this when you need to find code by meaning rather than exact text. For exact text/regex matching, use Grep instead."
33
+ is_read_only = True
34
+ is_concurrency_safe = True
35
+ input_schema = {
36
+ "type": "object",
37
+ "properties": {
38
+ "query": {
39
+ "type": "string",
40
+ "minLength": 1,
41
+ "description": "Natural language search query about the codebase.",
42
+ },
43
+ "target_directory": {
44
+ "type": "string",
45
+ "description": "Optional subdirectory to limit search scope.",
46
+ },
47
+ "num_results": {
48
+ "type": "integer",
49
+ "description": "Number of results to return (default 10).",
50
+ "default": 10,
51
+ },
52
+ },
53
+ "required": ["query"],
54
+ }
55
+
56
+ def __init__(self) -> None:
57
+ self._indexer: CodebaseIndexer | None = None
58
+ self._tool_config: dict[str, Any] = {}
59
+ self._init_lock = asyncio.Lock()
60
+ self._status_path: Path | None = None
61
+
62
+ async def setup(self, context: ToolContext) -> None:
63
+ await super().setup(context)
64
+ self._tool_config = context.tool_config
65
+ self._status_path = Path(context.cwd) / ".crabcode" / "search" / STATUS_FILE_NAME
66
+
67
+ status = self._read_background_status()
68
+ if status:
69
+ state = status.get("state")
70
+ if state == "ready":
71
+ chunks = status.get("chunks")
72
+ message = "Background index ready"
73
+ if chunks is not None:
74
+ message += f" ({chunks} chunks)"
75
+ self.emit_event("ready", {"message": message})
76
+ return
77
+ if state in {"starting", "preloading", "loading_index", "indexing"}:
78
+ done = status.get("done")
79
+ total = status.get("total")
80
+ percent = None
81
+ if isinstance(done, int) and isinstance(total, int) and total > 0:
82
+ percent = done / total
83
+ self.emit_event(
84
+ "progress",
85
+ {
86
+ "message": f"Background {state}...",
87
+ "percent": percent,
88
+ },
89
+ )
90
+ return
91
+
92
+ await self._ensure_initialized()
93
+
94
+ async def _ensure_initialized(self) -> None:
95
+ if self._indexer is not None:
96
+ return
97
+
98
+ async with self._init_lock:
99
+ if self._indexer is not None:
100
+ return
101
+
102
+ context = self._setup_context
103
+ if context is None:
104
+ return
105
+
106
+ config = self._tool_config
107
+ embedder = create_embedder(
108
+ backend=config.get("embedder", "ollama"),
109
+ model=config.get("model"),
110
+ dimension=config.get("dimension"),
111
+ batch_size=config.get("batch_size"),
112
+ device=config.get("device"),
113
+ )
114
+ await embedder.preload()
115
+ self._indexer = CodebaseIndexer(context.cwd, embedder=embedder)
116
+
117
+ threads = config.get("threads")
118
+ if threads is not None:
119
+ threads = int(threads)
120
+ os.environ["OMP_NUM_THREADS"] = str(threads)
121
+ os.environ["MKL_NUM_THREADS"] = str(threads)
122
+ try:
123
+ import torch
124
+ torch.set_num_threads(threads)
125
+ except Exception:
126
+ pass
127
+
128
+ all_files = self._indexer._scan_files()
129
+ self._indexer.total_files = len(all_files)
130
+ if self._indexer.store.count > 0 and not self._indexer._detect_changes(
131
+ all_files
132
+ ):
133
+ self.emit_event("ready", {
134
+ "message": f"Index loaded ({self._indexer.store.count} chunks)",
135
+ })
136
+ return
137
+
138
+ self.emit_event("progress", {"message": "Starting indexing...", "percent": 0})
139
+ self._background_task = asyncio.create_task(self._build_index())
140
+
141
+ def _read_background_status(self) -> dict[str, Any] | None:
142
+ if self._status_path is None or not self._status_path.exists():
143
+ return None
144
+ try:
145
+ data = json.loads(self._status_path.read_text(encoding="utf-8"))
146
+ except (OSError, json.JSONDecodeError):
147
+ return None
148
+ return data if isinstance(data, dict) else None
149
+
150
+ def _normalize_target_directory(self, value: Any, cwd: str) -> str | None:
151
+ if not isinstance(value, str) or not value.strip():
152
+ return None
153
+
154
+ raw = Path(value.strip()).expanduser()
155
+ try:
156
+ if raw.is_absolute():
157
+ rel = raw.resolve().relative_to(Path(cwd).resolve())
158
+ else:
159
+ rel = raw
160
+ except (OSError, ValueError):
161
+ return value.strip().replace("\\\\", "/").strip("/") or None
162
+
163
+ normalized = rel.as_posix().strip("/")
164
+ return normalized or None
165
+
166
+ async def _build_index(self) -> None:
167
+ assert self._indexer is not None
168
+ async for progress in self._indexer.build_or_update():
169
+ if progress.total > 0:
170
+ pct = progress.done / progress.total
171
+ else:
172
+ pct = 1.0
173
+ self.emit_event("progress", {
174
+ "message": f"Indexing: {progress.done}/{progress.total} files",
175
+ "percent": pct,
176
+ })
177
+ self.emit_event("ready", {
178
+ "message": f"Index ready ({self._indexer.store.count} chunks, {self._indexer.total_files} files)",
179
+ })
180
+
181
+ async def get_prompt(self, **kwargs: Any) -> str:
182
+ return (
183
+ "Search the codebase using natural language queries. "
184
+ "Returns semantically relevant code snippets with file paths and line numbers. "
185
+ "Use this when you need to find code by meaning rather than exact text. "
186
+ "For example: 'where is authentication handled', 'how does the build system work', "
187
+ "'find the payment processing logic'. "
188
+ "For exact text/regex matching, use Grep instead. "
189
+ "For finding files by name, use Glob instead."
190
+ )
191
+
192
+ async def call(
193
+ self,
194
+ tool_input: dict[str, Any],
195
+ context: ToolContext,
196
+ ) -> ToolResult:
197
+ if self._indexer is None:
198
+ await self._ensure_initialized()
199
+ if self._indexer is None:
200
+ return ToolResult(
201
+ result_for_model="CodebaseSearch is not initialized. Use Grep or Glob instead.",
202
+ is_error=True,
203
+ )
204
+
205
+ query = first_non_empty_str(
206
+ tool_input,
207
+ ("query", "search", "q", "question", "prompt", "text", "natural_language_query"),
208
+ )
209
+ if not query:
210
+ query = latest_user_text_for_agent_fallback(context.messages)
211
+ if not query:
212
+ return ToolResult(
213
+ result_for_model=(
214
+ "query is required. Pass a non-empty search string (field \"query\"); "
215
+ "aliases: search, q, question, prompt, text, natural_language_query."
216
+ ),
217
+ is_error=True,
218
+ )
219
+
220
+ num_results = tool_input.get("num_results", 10)
221
+ target_dir = self._normalize_target_directory(tool_input.get("target_directory"), context.cwd)
222
+
223
+ building = self._background_task and not self._background_task.done()
224
+
225
+ if not building:
226
+ await self._indexer.incremental_update()
227
+
228
+ query_vec = await self._indexer.embedder.embed([query], task_type="CODE_RETRIEVAL_QUERY")
229
+ results = await self._indexer.store.search(query_vec[0], top_k=num_results * 2)
230
+
231
+ if target_dir:
232
+ prefix = os.path.normcase(target_dir.replace("\\", "/")).replace("\\", "/").rstrip("/") + "/"
233
+ results = [
234
+ r for r in results
235
+ if os.path.normcase(r.chunk.file_path.replace("\\", "/")).replace("\\", "/").startswith(prefix)
236
+ ]
237
+
238
+ results = results[:num_results]
239
+
240
+ if not results:
241
+ msg = "No results found."
242
+ if building:
243
+ progress = self._indexer.progress
244
+ msg += (
245
+ f"\n\n[Note: Index still building ({progress.done}/{progress.total} files). "
246
+ "Results may be incomplete. Use Grep for precise searches.]"
247
+ )
248
+ return ToolResult(result_for_model=msg)
249
+
250
+ output_parts: list[str] = []
251
+ for i, r in enumerate(results, 1):
252
+ output_parts.append(
253
+ f"[{i}] {r.chunk.file_path}:{r.chunk.start_line}-{r.chunk.end_line} "
254
+ f"(score: {r.score:.3f})\n"
255
+ f" {r.chunk.signature}\n"
256
+ f"```\n{r.chunk.content}\n```"
257
+ )
258
+
259
+ output = "\n\n".join(output_parts)
260
+
261
+ if building:
262
+ progress = self._indexer.progress
263
+ output += (
264
+ f"\n\n[Note: Index still building ({progress.done}/{progress.total} files). "
265
+ "Results may be incomplete. Use Grep for precise searches.]"
266
+ )
267
+
268
+ return ToolResult(result_for_model=output)