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,169 @@
1
+ """MCP client — connects to MCP servers and loads tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import os
8
+ import re
9
+ from typing import Any
10
+
11
+ from crabcode_core.logging_utils import get_logger
12
+ from crabcode_core.types.config import McpServerConfig
13
+ from crabcode_core.types.tool import Tool, ToolContext, ToolResult
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ def _normalize_name(name: str) -> str:
19
+ """Normalize a server/tool name to a valid identifier."""
20
+ return re.sub(r'[^a-zA-Z0-9_]', '_', name)
21
+
22
+
23
+ def build_mcp_tool_name(server_name: str, tool_name: str) -> str:
24
+ """Build the prefixed MCP tool name."""
25
+ return f"mcp__{_normalize_name(server_name)}__{_normalize_name(tool_name)}"
26
+
27
+
28
+ class McpToolWrapper(Tool):
29
+ """Wraps an MCP tool as a CrabCode Tool."""
30
+
31
+ def __init__(
32
+ self,
33
+ server_name: str,
34
+ tool_name: str,
35
+ description: str,
36
+ schema: dict[str, Any],
37
+ client: Any,
38
+ ):
39
+ self.name = build_mcp_tool_name(server_name, tool_name)
40
+ self.description = description
41
+ self.input_schema = schema
42
+ self.is_read_only = False
43
+ self.is_concurrency_safe = True
44
+ self.is_enabled = True
45
+
46
+ self._server_name = server_name
47
+ self._original_name = tool_name
48
+ self._client = client
49
+
50
+ async def call(
51
+ self,
52
+ tool_input: dict[str, Any],
53
+ context: ToolContext,
54
+ ) -> ToolResult:
55
+ try:
56
+ result = await self._client.call_tool(self._original_name, tool_input)
57
+
58
+ text_parts: list[str] = []
59
+ if hasattr(result, "content"):
60
+ for block in result.content:
61
+ if hasattr(block, "text"):
62
+ text_parts.append(block.text)
63
+
64
+ output = "\n".join(text_parts) if text_parts else str(result)
65
+ is_error = getattr(result, "isError", False)
66
+
67
+ return ToolResult(
68
+ data=result,
69
+ result_for_model=output,
70
+ is_error=is_error,
71
+ )
72
+ except Exception as e:
73
+ return ToolResult(
74
+ result_for_model=f"MCP tool error: {e}",
75
+ is_error=True,
76
+ )
77
+
78
+
79
+ class McpManager:
80
+ """Manages MCP server connections and tool discovery."""
81
+
82
+ def __init__(self) -> None:
83
+ self._connections: dict[str, Any] = {}
84
+ self._tools: list[McpToolWrapper] = []
85
+
86
+ async def connect(
87
+ self,
88
+ servers: dict[str, McpServerConfig],
89
+ ) -> list[McpToolWrapper]:
90
+ """Connect to all configured MCP servers and fetch tools."""
91
+ all_tools: list[McpToolWrapper] = []
92
+
93
+ for name, config in servers.items():
94
+ if config.disabled:
95
+ continue
96
+
97
+ try:
98
+ tools = await self._connect_server(name, config)
99
+ all_tools.extend(tools)
100
+ except Exception:
101
+ logger.exception("Failed to connect MCP server: %s", name)
102
+
103
+ self._tools = all_tools
104
+ return all_tools
105
+
106
+ async def _connect_server(
107
+ self,
108
+ name: str,
109
+ config: McpServerConfig,
110
+ ) -> list[McpToolWrapper]:
111
+ """Connect to a single MCP server."""
112
+ try:
113
+ from mcp import ClientSession, StdioServerParameters
114
+ from mcp.client.stdio import stdio_client
115
+ except ImportError:
116
+ logger.warning("MCP package not installed; skipping server %s", name)
117
+ return []
118
+
119
+ if not config.command:
120
+ return []
121
+
122
+ env = {**os.environ, **config.env}
123
+ server_params = StdioServerParameters(
124
+ command=config.command[0],
125
+ args=config.command[1:] if len(config.command) > 1 else [],
126
+ env=env,
127
+ )
128
+
129
+ try:
130
+ read_stream, write_stream = await asyncio.wait_for(
131
+ stdio_client(server_params).__aenter__(),
132
+ timeout=30,
133
+ )
134
+ session = ClientSession(read_stream, write_stream)
135
+ initialization = await session.initialize()
136
+ self._connections[name] = session
137
+
138
+ tools_response = await session.list_tools()
139
+ tools: list[McpToolWrapper] = []
140
+ for tool_info in tools_response.tools:
141
+ wrapper = McpToolWrapper(
142
+ server_name=name,
143
+ tool_name=tool_info.name,
144
+ description=tool_info.description or "",
145
+ schema=tool_info.inputSchema or {"type": "object", "properties": {}},
146
+ client=session,
147
+ )
148
+ tools.append(wrapper)
149
+ wrapper.server_instructions = getattr(initialization, "instructions", None) or ""
150
+
151
+ return tools
152
+
153
+ except Exception:
154
+ logger.exception("MCP connection failed for server %s", name)
155
+ return []
156
+
157
+ @property
158
+ def tools(self) -> list[McpToolWrapper]:
159
+ return self._tools
160
+
161
+ async def disconnect_all(self) -> None:
162
+ """Disconnect from all MCP servers."""
163
+ for session in self._connections.values():
164
+ try:
165
+ await session.__aexit__(None, None, None)
166
+ except Exception:
167
+ logger.exception("Failed to disconnect MCP session cleanly")
168
+ self._connections.clear()
169
+ self._tools.clear()
@@ -0,0 +1,57 @@
1
+ """MCP configuration — multi-scope server config loading and merging."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from crabcode_core.logging_utils import get_logger
10
+ from crabcode_core.paths import get_config_home
11
+ from crabcode_core.types.config import McpServerConfig
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ def load_mcp_configs(cwd: str) -> dict[str, McpServerConfig]:
17
+ """Load and merge MCP server configurations from multiple scopes.
18
+
19
+ Scopes (later overrides earlier):
20
+ 1. User ($CRABCODE_HOME/mcp_servers.json; default ~/.crabcode)
21
+ 2. Project (<project>/.crabcode/mcp_servers.json)
22
+ 3. Local (<project>/.crabcode/mcp_servers.local.json)
23
+ """
24
+ merged: dict[str, dict[str, Any]] = {}
25
+ home = Path.home()
26
+
27
+ paths = [
28
+ get_config_home() / "mcp_servers.json",
29
+ home / ".claude" / "mcp_servers.json",
30
+ Path(cwd).resolve() / ".crabcode" / "mcp_servers.json",
31
+ Path(cwd).resolve() / ".claude" / "mcp_servers.json",
32
+ Path(cwd).resolve() / ".crabcode" / "mcp_servers.local.json",
33
+ ]
34
+
35
+ for path in paths:
36
+ if not path.exists():
37
+ continue
38
+ try:
39
+ raw = json.loads(path.read_text(encoding="utf-8", errors="replace"))
40
+ if isinstance(raw, dict):
41
+ servers = raw.get("mcpServers", raw)
42
+ if isinstance(servers, dict):
43
+ for name, config in servers.items():
44
+ if isinstance(config, dict):
45
+ merged[name] = {**merged.get(name, {}), **config}
46
+ except (json.JSONDecodeError, OSError):
47
+ logger.warning("Failed to load MCP config file: %s", path, exc_info=True)
48
+ continue
49
+
50
+ result: dict[str, McpServerConfig] = {}
51
+ for name, raw in merged.items():
52
+ try:
53
+ result[name] = McpServerConfig.model_validate(raw)
54
+ except Exception:
55
+ logger.warning("Skipping invalid MCP server config: %s", name, exc_info=True)
56
+
57
+ return result
@@ -0,0 +1,34 @@
1
+ """Validation helpers for portable filesystem path components."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ _WINDOWS_INVALID_CHARS = frozenset('<>:"|?*')
9
+ _WINDOWS_RESERVED_NAMES = frozenset(
10
+ {"CON", "PRN", "AUX", "NUL"}
11
+ | {f"COM{number}" for number in range(1, 10)}
12
+ | {f"LPT{number}" for number in range(1, 10)}
13
+ )
14
+
15
+
16
+ def validate_path_component(value: str, label: str) -> str:
17
+ """Return a safe, cross-platform filename component or raise ValueError."""
18
+ if not isinstance(value, str) or not value or value in {".", ".."}:
19
+ raise ValueError(f"Invalid {label}")
20
+ if Path(value).is_absolute() or "/" in value or "\\" in value:
21
+ raise ValueError(f"Invalid {label}: path separators are not allowed")
22
+ if any(ord(character) < 32 or ord(character) == 127 for character in value):
23
+ raise ValueError(f"Invalid {label}: control characters are not allowed")
24
+ invalid = sorted(set(value) & _WINDOWS_INVALID_CHARS)
25
+ if invalid:
26
+ raise ValueError(
27
+ f"Invalid {label}: unsupported filename character(s): {''.join(invalid)}"
28
+ )
29
+ if value.endswith((" ", ".")):
30
+ raise ValueError(f"Invalid {label}: trailing spaces and dots are not allowed")
31
+ stem = value.split(".", 1)[0].rstrip(" .").upper()
32
+ if stem in _WINDOWS_RESERVED_NAMES:
33
+ raise ValueError(f"Invalid {label}: reserved Windows device name")
34
+ return value
crabcode_core/paths.py ADDED
@@ -0,0 +1,24 @@
1
+ """Shared locations for CrabCode's user-level configuration and state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+
9
+ def get_config_home() -> Path:
10
+ """Return CRABCODE_HOME, or ~/.crabcode when unset or blank.
11
+
12
+ Require an absolute directory so processes launched in different projects
13
+ share the same home. A leading ``~`` for the current user is supported.
14
+ This function does not create the directory or migrate existing data.
15
+ """
16
+ override = os.environ.get("CRABCODE_HOME", "").strip()
17
+ if not override:
18
+ return Path.home() / ".crabcode"
19
+ path = Path(override)
20
+ if path.parts and path.parts[0] == "~":
21
+ path = Path.home().joinpath(*path.parts[1:])
22
+ if not path.is_absolute():
23
+ raise ValueError("CRABCODE_HOME must be an absolute directory path (or start with ~/)")
24
+ return path
@@ -0,0 +1,10 @@
1
+ """Cross-session peer discovery and messaging."""
2
+
3
+ from crabcode_core.peer.runtime import (
4
+ PeerDelivery,
5
+ PeerMessage,
6
+ PeerRecord,
7
+ PeerRuntime,
8
+ )
9
+
10
+ __all__ = ["PeerDelivery", "PeerMessage", "PeerRecord", "PeerRuntime"]