minimalcode 1.5.2__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.
Files changed (58) hide show
  1. minimalcode/__init__.py +3 -0
  2. minimalcode/__main__.py +3 -0
  3. minimalcode/agent.py +1084 -0
  4. minimalcode/cli.py +1520 -0
  5. minimalcode/clipboard.py +212 -0
  6. minimalcode/config.py +1065 -0
  7. minimalcode/defaults.json +491 -0
  8. minimalcode/errors.py +31 -0
  9. minimalcode/highlight/__init__.py +47 -0
  10. minimalcode/highlight/code.py +261 -0
  11. minimalcode/highlight/color.py +58 -0
  12. minimalcode/highlight/markdown.py +710 -0
  13. minimalcode/highlight/palette.py +73 -0
  14. minimalcode/highlight/sanitize.py +175 -0
  15. minimalcode/highlight/thinking.py +71 -0
  16. minimalcode/httpclient.py +278 -0
  17. minimalcode/importer.py +253 -0
  18. minimalcode/prompt.py +195 -0
  19. minimalcode/provider/__init__.py +28 -0
  20. minimalcode/provider/anthropic.py +805 -0
  21. minimalcode/provider/anthropic_env.py +161 -0
  22. minimalcode/provider/chat.py +394 -0
  23. minimalcode/provider/client.py +243 -0
  24. minimalcode/provider/common.py +176 -0
  25. minimalcode/provider/deepseek_web.py +1158 -0
  26. minimalcode/provider/factory.py +63 -0
  27. minimalcode/provider/responses.py +692 -0
  28. minimalcode/provider/sha3_wasm_bg.wasm +0 -0
  29. minimalcode/provider/transport.py +241 -0
  30. minimalcode/provider/types.py +54 -0
  31. minimalcode/sandbox/__init__.py +64 -0
  32. minimalcode/sandbox/audit.py +120 -0
  33. minimalcode/sandbox/backends.py +343 -0
  34. minimalcode/sandbox/execution.py +435 -0
  35. minimalcode/sandbox/network.py +793 -0
  36. minimalcode/sandbox/policy.py +253 -0
  37. minimalcode/sandbox/runner.py +347 -0
  38. minimalcode/sandbox/sensitive.py +286 -0
  39. minimalcode/session.py +465 -0
  40. minimalcode/shell.py +333 -0
  41. minimalcode/subagent.py +120 -0
  42. minimalcode/tools/__init__.py +46 -0
  43. minimalcode/tools/files.py +1139 -0
  44. minimalcode/tools/process.py +164 -0
  45. minimalcode/tools/remote.py +74 -0
  46. minimalcode/tools/runtime.py +219 -0
  47. minimalcode/tools/schemas.py +418 -0
  48. minimalcode/tools/search.py +521 -0
  49. minimalcode/tools/search_tools.py +583 -0
  50. minimalcode/tools/validation.py +99 -0
  51. minimalcode/vision.py +231 -0
  52. minimalcode/web.py +238 -0
  53. minimalcode-1.5.2.dist-info/METADATA +646 -0
  54. minimalcode-1.5.2.dist-info/RECORD +58 -0
  55. minimalcode-1.5.2.dist-info/WHEEL +5 -0
  56. minimalcode-1.5.2.dist-info/entry_points.txt +2 -0
  57. minimalcode-1.5.2.dist-info/licenses/LICENSE +201 -0
  58. minimalcode-1.5.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,343 @@
1
+ """Pluggable execution backends and backend selection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+
12
+ from ..errors import ConfigError, ToolError
13
+ from .policy import SandboxPolicy
14
+
15
+
16
+ class SandboxUnavailable(ToolError):
17
+ """Raised when the selected backend cannot execute the requested input."""
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class CommandRequest:
22
+ """One prepared invocation handed to a backend's ``wrap`` method."""
23
+
24
+ argv: tuple[str, ...]
25
+ command: str | None
26
+ shell_kind: str
27
+ shell_label: str
28
+ cwd: Path
29
+ temp_dir: Path | None
30
+ policy: SandboxPolicy
31
+
32
+
33
+ class SandboxBackend:
34
+ """Base backend that runs the command unchanged."""
35
+
36
+ name = "base"
37
+ capabilities: frozenset[str] = frozenset()
38
+
39
+ def available(self) -> bool:
40
+ """Return True when the backend's external dependency is usable."""
41
+ return True
42
+
43
+ def wrap(self, request: CommandRequest) -> list[str]:
44
+ """Return the final argv passed to the operating system."""
45
+ return list(request.argv)
46
+
47
+ def temp_path(self, request: CommandRequest) -> str | None:
48
+ """Path the child should use for TMPDIR, or None to disable the override."""
49
+ return str(request.temp_dir) if request.temp_dir is not None else None
50
+
51
+ def cleanup(self, request: CommandRequest) -> None:
52
+ """Release resources that outlive the wrapped client process, if any."""
53
+
54
+
55
+ class LocalSandboxBackend(SandboxBackend):
56
+ """Cross-platform baseline: policy layer plus subprocess limits."""
57
+
58
+ name = "local"
59
+ capabilities = frozenset({"argv", "shell", "resources"})
60
+
61
+
62
+ class BwrapBackend(SandboxBackend):
63
+ """Linux bubblewrap backend with filesystem and network namespaces."""
64
+
65
+ name = "bwrap"
66
+ capabilities = frozenset({"argv", "shell", "filesystem", "network"})
67
+
68
+ def available(self) -> bool:
69
+ if not sys.platform.startswith("linux"):
70
+ return False
71
+ cached = getattr(self, "_available", None)
72
+ if cached is not None:
73
+ return bool(cached)
74
+ executable = shutil.which("bwrap")
75
+ usable = False
76
+ if executable is not None:
77
+ try:
78
+ probe = subprocess.run(
79
+ [
80
+ executable,
81
+ "--unshare-all",
82
+ "--ro-bind",
83
+ "/",
84
+ "/",
85
+ "true",
86
+ ],
87
+ stdin=subprocess.DEVNULL,
88
+ stdout=subprocess.DEVNULL,
89
+ stderr=subprocess.DEVNULL,
90
+ timeout=5,
91
+ check=False,
92
+ )
93
+ usable = probe.returncode == 0
94
+ except (OSError, subprocess.TimeoutExpired):
95
+ usable = False
96
+ self._available = usable
97
+ return usable
98
+
99
+ def wrap(self, request: CommandRequest) -> list[str]:
100
+ argv = [
101
+ shutil.which("bwrap") or "bwrap",
102
+ "--die-with-parent",
103
+ "--new-session",
104
+ "--unshare-all",
105
+ "--ro-bind",
106
+ "/",
107
+ "/",
108
+ "--dev",
109
+ "/dev",
110
+ "--proc",
111
+ "/proc",
112
+ ]
113
+ if not request.policy.isolates_network:
114
+ argv.append("--share-net")
115
+ for root in request.policy.write_roots(request.cwd):
116
+ argv.extend(["--bind", str(root), str(root)])
117
+ if request.temp_dir is not None:
118
+ argv.extend(["--bind", str(request.temp_dir), str(request.temp_dir)])
119
+ argv.extend(["--chdir", str(request.cwd), "--", *request.argv])
120
+ return argv
121
+
122
+
123
+ class FirejailBackend(SandboxBackend):
124
+ """Linux firejail backend for environments where bwrap is unavailable."""
125
+
126
+ name = "firejail"
127
+ capabilities = frozenset({"argv", "shell", "network"})
128
+
129
+ def available(self) -> bool:
130
+ return sys.platform.startswith("linux") and shutil.which("firejail") is not None
131
+
132
+ def wrap(self, request: CommandRequest) -> list[str]:
133
+ argv = [
134
+ shutil.which("firejail") or "firejail",
135
+ "--quiet",
136
+ "--private-tmp",
137
+ "--nosound",
138
+ "--no3d",
139
+ ]
140
+ if request.policy.isolates_network:
141
+ argv.append("--net=none")
142
+ argv.extend(["--", *request.argv])
143
+ return argv
144
+
145
+ def temp_path(self, request: CommandRequest) -> str | None:
146
+ # --private-tmp mounts an empty /tmp, so the host temp path is gone.
147
+ return "/tmp" if request.temp_dir is not None else None
148
+
149
+
150
+ class MacSandboxExecBackend(SandboxBackend):
151
+ """macOS ``sandbox-exec`` backend with an inline network profile."""
152
+
153
+ name = "sandbox-exec"
154
+ capabilities = frozenset({"argv", "shell", "network"})
155
+
156
+ def available(self) -> bool:
157
+ return sys.platform == "darwin" and shutil.which("sandbox-exec") is not None
158
+
159
+ def wrap(self, request: CommandRequest) -> list[str]:
160
+ profile = "(version 1)\n(allow default)\n"
161
+ if request.policy.isolates_network:
162
+ profile += "(deny network*)\n"
163
+ return [
164
+ shutil.which("sandbox-exec") or "sandbox-exec",
165
+ "-p",
166
+ profile,
167
+ *request.argv,
168
+ ]
169
+
170
+
171
+ class DockerBackend(SandboxBackend):
172
+ """Docker backend for shell commands with a mounted workspace."""
173
+
174
+ name = "docker"
175
+ capabilities = frozenset({"shell", "filesystem", "network"})
176
+
177
+ def available(self) -> bool:
178
+ # Docker Desktop on Windows cannot mount drive-letter paths the way this
179
+ # backend builds them (`C:\ws:C:\ws`), so claiming availability there
180
+ # would produce a confusing failure after the policy said it was usable.
181
+ if os.name == "nt":
182
+ return False
183
+ cached = getattr(self, "_available", None)
184
+ if cached is not None:
185
+ return bool(cached)
186
+ executable = shutil.which("docker")
187
+ usable = False
188
+ if executable is not None:
189
+ try:
190
+ probe = subprocess.run(
191
+ [executable, "info", "--format", "{{.ServerVersion}}"],
192
+ stdin=subprocess.DEVNULL,
193
+ capture_output=True,
194
+ timeout=5,
195
+ check=False,
196
+ )
197
+ usable = probe.returncode == 0
198
+ except (OSError, subprocess.TimeoutExpired):
199
+ usable = False
200
+ self._available = usable
201
+ return usable
202
+
203
+ @staticmethod
204
+ def _container_name(request: CommandRequest) -> str | None:
205
+ if request.temp_dir is None:
206
+ return None
207
+ return f"minimalcode-{request.temp_dir.name}"
208
+
209
+ def wrap(self, request: CommandRequest) -> list[str]:
210
+ if not request.command:
211
+ raise SandboxUnavailable(
212
+ "the docker sandbox backend can only run shell commands"
213
+ )
214
+ if request.shell_kind in {"pwsh7", "windows_powershell"}:
215
+ # The command is handed to `/bin/sh` inside the container. Passing a
216
+ # PowerShell command string to it silently mis-executes the command
217
+ # instead of failing, so refuse the combination.
218
+ raise SandboxUnavailable(
219
+ "the docker sandbox backend runs commands with a POSIX shell, "
220
+ f"which cannot execute {request.shell_label or request.shell_kind} "
221
+ "syntax"
222
+ )
223
+ image = request.policy.docker_image
224
+ if not image:
225
+ raise SandboxUnavailable("sandbox.docker_image must not be empty")
226
+ network = "none" if request.policy.isolates_network else "bridge"
227
+ argv = [
228
+ shutil.which("docker") or "docker",
229
+ "run",
230
+ "--rm",
231
+ "--interactive",
232
+ "--network",
233
+ network,
234
+ ]
235
+ name = self._container_name(request)
236
+ if name is not None:
237
+ # `--rm` cleans up after a normal exit, but the container belongs to
238
+ # the daemon; killing the docker client leaves it running. A name is
239
+ # what lets cleanup() stop it after a timeout or Ctrl-C.
240
+ argv.extend(["--name", name])
241
+ user = _docker_user()
242
+ if user is not None:
243
+ # Containers run as root by default, which would leave root-owned
244
+ # files in the mounted workspace.
245
+ argv.extend(["--user", user])
246
+ mounted: set[str] = set()
247
+ for root in request.policy.write_roots(request.cwd):
248
+ text = str(root)
249
+ if text in mounted:
250
+ continue
251
+ mounted.add(text)
252
+ argv.extend(["--volume", f"{text}:{text}"])
253
+ argv.extend(
254
+ [
255
+ "--workdir",
256
+ str(request.cwd),
257
+ image,
258
+ "sh",
259
+ "-lc",
260
+ request.command,
261
+ ]
262
+ )
263
+ return argv
264
+
265
+ def temp_path(self, request: CommandRequest) -> str | None:
266
+ return "/tmp" if request.temp_dir is not None else None
267
+
268
+ def cleanup(self, request: CommandRequest) -> None:
269
+ """Stop and remove a container whose docker client was killed."""
270
+ name = self._container_name(request)
271
+ if name is None:
272
+ return
273
+ executable = shutil.which("docker")
274
+ if executable is None:
275
+ return
276
+ for arguments in (["kill", name], ["rm", "-f", name]):
277
+ try:
278
+ subprocess.run(
279
+ [executable, *arguments],
280
+ stdin=subprocess.DEVNULL,
281
+ stdout=subprocess.DEVNULL,
282
+ stderr=subprocess.DEVNULL,
283
+ timeout=10,
284
+ check=False,
285
+ )
286
+ except (OSError, subprocess.TimeoutExpired):
287
+ continue
288
+
289
+
290
+ def _docker_user() -> str | None:
291
+ """Return the ``uid:gid`` the container should write files as, if known."""
292
+ getuid = getattr(os, "getuid", None)
293
+ getgid = getattr(os, "getgid", None)
294
+ if getuid is None or getgid is None: # pragma: no cover - Windows
295
+ return None
296
+ try:
297
+ return f"{getuid()}:{getgid()}"
298
+ except OSError: # pragma: no cover - defensive
299
+ return None
300
+
301
+
302
+ def _auto_backend() -> SandboxBackend:
303
+ """Pick the first genuinely available isolating backend, else local."""
304
+ candidates: tuple[SandboxBackend, ...] = (
305
+ BwrapBackend(),
306
+ FirejailBackend(),
307
+ MacSandboxExecBackend(),
308
+ )
309
+ for backend in candidates:
310
+ if backend.available():
311
+ return backend
312
+ return LocalSandboxBackend()
313
+
314
+
315
+ def backend_for(name: str) -> SandboxBackend:
316
+ """Instantiate the backend named by configuration."""
317
+ if name == "auto":
318
+ return _auto_backend()
319
+ factories: dict[str, type[SandboxBackend]] = {
320
+ "local": LocalSandboxBackend,
321
+ "bwrap": BwrapBackend,
322
+ "firejail": FirejailBackend,
323
+ "sandbox-exec": MacSandboxExecBackend,
324
+ "docker": DockerBackend,
325
+ }
326
+ factory = factories.get(name)
327
+ if factory is None:
328
+ raise ConfigError(f"unknown sandbox backend: {name}")
329
+ return factory()
330
+
331
+
332
+ def available_backends() -> dict[str, bool]:
333
+ """Return a platform capability report for the CLI."""
334
+ return {
335
+ name: factory().available()
336
+ for name, factory in (
337
+ ("local", LocalSandboxBackend),
338
+ ("bwrap", BwrapBackend),
339
+ ("firejail", FirejailBackend),
340
+ ("sandbox-exec", MacSandboxExecBackend),
341
+ ("docker", DockerBackend),
342
+ )
343
+ }