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,256 @@
1
+ """Safe local mutations for the named model and group catalog."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import codecs
6
+ import json
7
+ import os
8
+ import stat
9
+ import tempfile
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import Any, Literal
13
+
14
+ from crabcode_core.config.manager import ConfigManager, SETTING_SOURCES
15
+ from crabcode_core.filesystem import replace_with_retry
16
+ from crabcode_core.text_io import normalize_newlines, read_utf8_text
17
+ from crabcode_core.types.config import ApiConfig
18
+
19
+
20
+ CatalogKind = Literal["model", "group"]
21
+ WritableSettingsSource = Literal["userSettings", "projectSettings", "localSettings"]
22
+
23
+ WRITABLE_SETTINGS_SOURCES: tuple[WritableSettingsSource, ...] = (
24
+ "projectSettings",
25
+ "localSettings",
26
+ "userSettings",
27
+ )
28
+
29
+ SETTINGS_SOURCE_LABELS: dict[str, str] = {
30
+ "userSettings": "User · global",
31
+ "projectSettings": "Project · shared",
32
+ "localSettings": "Project · local",
33
+ "flagSettings": "CLI flags · read-only",
34
+ "policySettings": "Managed policy · read-only",
35
+ }
36
+
37
+
38
+ class ModelCatalogError(ValueError):
39
+ """A catalog mutation could not be completed safely."""
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class CatalogDefinition:
44
+ kind: CatalogKind
45
+ name: str
46
+ source: str
47
+ path: Path
48
+ config: dict[str, Any]
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class CatalogMutationResult:
53
+ action: Literal["add", "delete"]
54
+ kind: CatalogKind
55
+ name: str
56
+ source: WritableSettingsSource
57
+ path: Path
58
+ cleared_default: bool = False
59
+
60
+
61
+ def validate_catalog_name(name: str) -> str:
62
+ """Validate the simple names accepted by the Gateway settings editor."""
63
+ value = name.strip()
64
+ if not value:
65
+ raise ModelCatalogError("Name is required.")
66
+ if value in {".", ".."} or any(char in value for char in "/\\"):
67
+ raise ModelCatalogError("Name must be a simple configuration name without / or \\.")
68
+ if len(value) > 120:
69
+ raise ModelCatalogError("Name must be 120 characters or fewer.")
70
+ return value
71
+
72
+
73
+ def settings_source_path(cwd: str, source: WritableSettingsSource) -> Path:
74
+ manager = ConfigManager(cwd=cwd)
75
+ path_value = manager.settings_file_paths.get(source)
76
+ if not path_value:
77
+ raise ModelCatalogError(f"Settings source is unavailable: {source}")
78
+ path = Path(path_value)
79
+ if source in {"projectSettings", "localSettings"}:
80
+ expected_parent = (Path(cwd).resolve() / ".crabcode").resolve()
81
+ try:
82
+ path.parent.resolve().relative_to(expected_parent)
83
+ except ValueError as exc:
84
+ raise ModelCatalogError("Project settings path is outside the workspace.") from exc
85
+ return path
86
+
87
+
88
+ def list_catalog_definitions(
89
+ cwd: str,
90
+ kind: CatalogKind | None = None,
91
+ *,
92
+ writable_only: bool = False,
93
+ ) -> list[CatalogDefinition]:
94
+ """Return raw definitions, retaining the layer each definition belongs to."""
95
+ manager = ConfigManager(cwd=cwd)
96
+ sources = (
97
+ list(WRITABLE_SETTINGS_SOURCES)
98
+ if writable_only
99
+ else list(reversed(SETTING_SOURCES))
100
+ )
101
+ definitions: list[CatalogDefinition] = []
102
+ kinds: tuple[CatalogKind, ...] = (kind,) if kind else ("model", "group")
103
+ for source in sources:
104
+ raw = manager.get_settings_for_source(source)
105
+ path_value = manager.settings_file_paths.get(source)
106
+ if raw is None or not path_value:
107
+ continue
108
+ for entry_kind in kinds:
109
+ values = raw.get("models" if entry_kind == "model" else "groups")
110
+ if not isinstance(values, dict):
111
+ continue
112
+ for name, config in values.items():
113
+ if not isinstance(config, dict):
114
+ continue
115
+ definitions.append(
116
+ CatalogDefinition(
117
+ kind=entry_kind,
118
+ name=str(name),
119
+ source=source,
120
+ path=Path(path_value),
121
+ config=dict(config),
122
+ )
123
+ )
124
+ return definitions
125
+
126
+
127
+ def catalog_names(cwd: str, kind: CatalogKind) -> set[str]:
128
+ return {definition.name for definition in list_catalog_definitions(cwd, kind)}
129
+
130
+
131
+ def referencing_models(cwd: str, group_name: str) -> list[str]:
132
+ """List effective named models that still point at a group."""
133
+ settings = ConfigManager(cwd=cwd).load()
134
+ return sorted(name for name, config in settings.models.items() if config.group == group_name)
135
+
136
+
137
+ def add_catalog_entry(
138
+ cwd: str,
139
+ source: WritableSettingsSource,
140
+ kind: CatalogKind,
141
+ name: str,
142
+ config: dict[str, Any],
143
+ ) -> CatalogMutationResult:
144
+ """Add a new named entry without overwriting any existing layer."""
145
+ name = validate_catalog_name(name)
146
+ if name in catalog_names(cwd, kind):
147
+ raise ModelCatalogError(
148
+ f'{kind.title()} "{name}" already exists in the merged configuration.'
149
+ )
150
+ if not isinstance(config, dict):
151
+ raise ModelCatalogError("Configuration must be a JSON object.")
152
+ if kind == "group" and "group" in config:
153
+ raise ModelCatalogError("A group configuration cannot inherit from another group.")
154
+ try:
155
+ ApiConfig.model_validate(config)
156
+ except Exception as exc:
157
+ raise ModelCatalogError(f"Invalid {kind} configuration: {exc}") from exc
158
+
159
+ path = settings_source_path(cwd, source)
160
+ current = _read_settings_object(path)
161
+ key = "models" if kind == "model" else "groups"
162
+ values = current.get(key)
163
+ if values is None:
164
+ values = {}
165
+ current[key] = values
166
+ if not isinstance(values, dict):
167
+ raise ModelCatalogError(f'"{key}" in {path} must be a JSON object.')
168
+ if name in values:
169
+ raise ModelCatalogError(f'{kind.title()} "{name}" already exists in {path}.')
170
+ values[name] = dict(config)
171
+ _atomic_write_settings(path, current)
172
+ return CatalogMutationResult("add", kind, name, source, path)
173
+
174
+
175
+ def delete_catalog_entry(
176
+ cwd: str,
177
+ source: WritableSettingsSource,
178
+ kind: CatalogKind,
179
+ name: str,
180
+ ) -> CatalogMutationResult:
181
+ """Delete exactly one raw definition from one writable settings layer."""
182
+ name = validate_catalog_name(name)
183
+ path = settings_source_path(cwd, source)
184
+ current = _read_settings_object(path)
185
+ key = "models" if kind == "model" else "groups"
186
+ values = current.get(key)
187
+ if not isinstance(values, dict) or name not in values:
188
+ raise ModelCatalogError(f'{kind.title()} "{name}" is not defined in {path}.')
189
+ values.pop(name)
190
+ if not values:
191
+ current.pop(key, None)
192
+
193
+ cleared_default = kind == "model" and current.get("default_model") == name
194
+ if cleared_default:
195
+ # An explicit null prevents a lower-precedence default from silently
196
+ # becoming active when its selected model may no longer exist.
197
+ current["default_model"] = None
198
+
199
+ _atomic_write_settings(path, current)
200
+ return CatalogMutationResult(
201
+ "delete", kind, name, source, path, cleared_default=cleared_default
202
+ )
203
+
204
+
205
+ def _read_settings_object(path: Path) -> dict[str, Any]:
206
+ if not path.exists():
207
+ return {}
208
+ try:
209
+ value = json.loads(read_utf8_text(path).text)
210
+ except (json.JSONDecodeError, OSError, UnicodeError) as exc:
211
+ raise ModelCatalogError(f"Settings file is not valid UTF-8 JSON: {path}") from exc
212
+ if not isinstance(value, dict):
213
+ raise ModelCatalogError(f"Settings file must contain a JSON object: {path}")
214
+ return value
215
+
216
+
217
+ def _atomic_write_settings(path: Path, value: dict[str, Any]) -> None:
218
+ payload = json.dumps(value, indent=2, ensure_ascii=False) + "\n"
219
+ newline = "\n"
220
+ has_bom = False
221
+ if path.exists():
222
+ try:
223
+ source = read_utf8_text(path)
224
+ except (OSError, UnicodeError) as exc:
225
+ raise ModelCatalogError(f"Could not read settings file: {path}") from exc
226
+ newline = source.newline or newline
227
+ has_bom = source.has_bom
228
+ payload = normalize_newlines(payload, newline)
229
+ raw_payload = payload.encode("utf-8")
230
+ if has_bom:
231
+ raw_payload = codecs.BOM_UTF8 + raw_payload
232
+
233
+ temporary: Path | None = None
234
+ try:
235
+ path.parent.mkdir(parents=True, exist_ok=True)
236
+ with tempfile.NamedTemporaryFile(
237
+ "wb",
238
+ dir=path.parent,
239
+ prefix=f".{path.name}.",
240
+ suffix=".tmp",
241
+ delete=False,
242
+ ) as handle:
243
+ temporary = Path(handle.name)
244
+ handle.write(raw_payload)
245
+ handle.flush()
246
+ os.fsync(handle.fileno())
247
+ if path.exists():
248
+ temporary.chmod(stat.S_IMODE(path.stat().st_mode))
249
+ replace_with_retry(temporary, path)
250
+ except OSError as exc:
251
+ if temporary is not None:
252
+ try:
253
+ temporary.unlink(missing_ok=True)
254
+ except OSError:
255
+ pass
256
+ raise ModelCatalogError(f"Settings file is not writable: {path}") from exc