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,261 @@
1
+ """Fenced-code token highlighting for the Markdown renderer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ _NUMBER_RE = re.compile(
8
+ r"(?<![\w.])-?(?:0[xX][0-9a-fA-F]+|\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)(?!\w)"
9
+ )
10
+ # Kebab-case names (CSS properties, HTML attributes) and the shell ``$VAR`` form
11
+ # share a style because the tokenizer has no per-language grammar to tell them
12
+ # apart; identifiers with a single dash would otherwise read as names.
13
+ _VARIABLE_RE = re.compile(r"\$(?:\w+:)?\w+|\b[A-Za-z_]\w*-\w+\b")
14
+ # One merged set for every language the agent prints. Detecting the language per
15
+ # fence would add grammars and a dependency; a shared set only miscolors a word
16
+ # that means something else in another language, which is cosmetic.
17
+ _KEYWORDS = {
18
+ "and",
19
+ "as",
20
+ "async",
21
+ "await",
22
+ "begin",
23
+ "break",
24
+ "case",
25
+ "catch",
26
+ "class",
27
+ "const",
28
+ "continue",
29
+ "def",
30
+ "do",
31
+ "else",
32
+ "elseif",
33
+ "end",
34
+ "enum",
35
+ "except",
36
+ "export",
37
+ "False",
38
+ "false",
39
+ "finally",
40
+ "for",
41
+ "foreach",
42
+ "from",
43
+ "function",
44
+ "if",
45
+ "import",
46
+ "in",
47
+ "is",
48
+ "lambda",
49
+ "let",
50
+ "match",
51
+ "new",
52
+ "None",
53
+ "not",
54
+ "null",
55
+ "or",
56
+ "param",
57
+ "pass",
58
+ "process",
59
+ "raise",
60
+ "return",
61
+ "switch",
62
+ "throw",
63
+ "True",
64
+ "true",
65
+ "try",
66
+ "undefined",
67
+ "using",
68
+ "var",
69
+ "while",
70
+ "with",
71
+ "yield",
72
+ }
73
+ _KEYWORD_RE = re.compile(r"\b(?:" + "|".join(sorted(_KEYWORDS)) + r")\b")
74
+
75
+
76
+ class CodeHighlightMixin:
77
+ """Line-oriented code tokenizer mixed into the Markdown renderer.
78
+
79
+ The mixin expects the host to provide ``palette``, ``_pending``,
80
+ ``_cursor``, ``_advance``, ``_append``, ``_emit``, ``_end_line``, and the
81
+ ``_code_*`` state initialized by ``MarkdownHighlighter``.
82
+ """
83
+
84
+ palette: dict[str, str]
85
+ _pending: str
86
+ _cursor: int
87
+ _code_comment: bool
88
+ _code_quote: str | None
89
+ _code_escape: bool
90
+ _code_word: list[str]
91
+ _code_last: str
92
+ _line_kind: str
93
+ _at_line_start: bool
94
+ _in_fence: bool
95
+ _fence_char: str
96
+ _fence_length: int
97
+
98
+ def _advance(self, count: int) -> None:
99
+ raise NotImplementedError
100
+
101
+ def _append(
102
+ self,
103
+ segments: list[tuple[str | None, list[str]]],
104
+ text: str,
105
+ style: str | None,
106
+ ) -> None:
107
+ raise NotImplementedError
108
+
109
+ def _emit(
110
+ self,
111
+ segments: list[tuple[str | None, list[str]]],
112
+ end: int,
113
+ style: str | None,
114
+ ) -> None:
115
+ raise NotImplementedError
116
+
117
+ def _end_line(self) -> None:
118
+ raise NotImplementedError
119
+
120
+ def _consume_code_line_start(self, *, final: bool) -> bool:
121
+ """Decide whether the current line closes the fence or starts code."""
122
+ text = self._pending
123
+ start = self._cursor
124
+ line_end = text.find("\n", start)
125
+ line = text[start:] if line_end < 0 else text[start:line_end]
126
+ # The opening side accepts any indentation (models nest fences inside
127
+ # list items), so the closing side must accept it too. Otherwise an
128
+ # indented fence never closes and every later heading, list, or table
129
+ # is rendered as code.
130
+ marker = re.match(rf"^[ \t]*({re.escape(self._fence_char)}+)(.*)$", line)
131
+ if marker:
132
+ marker_text = marker.group(1)
133
+ remainder = marker.group(2).strip()
134
+ if (
135
+ len(marker_text) >= self._fence_length
136
+ and not remainder
137
+ and (line_end >= 0 or final)
138
+ ):
139
+ consumed = len(text) if line_end < 0 else line_end + 1
140
+ # Hide the closing fence too, so no stray ``` follows the code.
141
+ self._advance(consumed - self._cursor)
142
+ self._in_fence = False
143
+ self._end_line()
144
+ return True
145
+ if line_end < 0 and not remainder and not final:
146
+ return False
147
+ elif line_end < 0 and not final and not line.strip(" \t"):
148
+ # Only indentation has arrived so far; the line may still become a
149
+ # closing fence, so wait for the next non-space byte instead of
150
+ # committing the indentation as code content.
151
+ return False
152
+
153
+ self._line_kind = "code"
154
+ self._at_line_start = False
155
+ self._reset_code_line()
156
+ return True
157
+
158
+ def _consume_code(
159
+ self,
160
+ segments: list[tuple[str | None, list[str]]],
161
+ ) -> None:
162
+ """Consume one character of a fenced code line."""
163
+ text = self._pending
164
+ start = self._cursor
165
+ char = text[start]
166
+ if text.startswith("\r\n", start):
167
+ self._flush_code_word(segments)
168
+ self._emit(segments, start + 2, None)
169
+ self._end_line()
170
+ return
171
+ if char == "\n":
172
+ self._flush_code_word(segments)
173
+ self._emit(segments, start + 1, None)
174
+ self._end_line()
175
+ return
176
+
177
+ if self._code_comment:
178
+ self._append(segments, char, self.palette["code_comment"])
179
+ self._advance(1)
180
+ self._code_last = char
181
+ return
182
+
183
+ if self._code_quote is not None:
184
+ self._append(segments, char, self.palette["code_string"])
185
+ self._advance(1)
186
+ if self._code_escape:
187
+ self._code_escape = False
188
+ elif char == "\\":
189
+ self._code_escape = True
190
+ elif char == self._code_quote:
191
+ self._code_quote = None
192
+ self._code_last = char
193
+ return
194
+
195
+ # '#' starts a comment only at a token boundary; '#include' after
196
+ # whitespace qualifies, but a URL fragment should not swallow the line.
197
+ # '//' follows the same rule so `http://x` keeps its path unstyled.
198
+ at_boundary = not self._code_last or self._code_last.isspace()
199
+ comment_start = at_boundary and (char == "#" or text.startswith("//", start))
200
+ if comment_start:
201
+ self._flush_code_word(segments)
202
+ token = "#" if char == "#" else "//"
203
+ self._append(segments, token, self.palette["code_comment"])
204
+ self._advance(len(token))
205
+ self._code_comment = True
206
+ self._code_last = token[-1]
207
+ return
208
+
209
+ if char in "\"'`":
210
+ self._flush_code_word(segments)
211
+ self._append(segments, char, self.palette["code_string"])
212
+ self._advance(1)
213
+ self._code_quote = char
214
+ self._code_escape = False
215
+ self._code_last = char
216
+ return
217
+
218
+ if char.isalnum() or char in "_$:.-":
219
+ # Buffer identifiers so the whole token can be classified at once.
220
+ # The punctuation is part of the token as well: keeping `a.b`,
221
+ # `mod:Class`, and `x-2` together stops them being split into name and
222
+ # number fragments that get two different colors mid-identifier.
223
+ self._code_word.append(char)
224
+ self._advance(1)
225
+ self._code_last = char
226
+ return
227
+
228
+ self._flush_code_word(segments)
229
+ self._append(segments, char, None)
230
+ self._advance(1)
231
+ self._code_last = char
232
+
233
+ def _flush_code_word(
234
+ self,
235
+ segments: list[tuple[str | None, list[str]]],
236
+ ) -> None:
237
+ """Classify and emit the buffered identifier or number token."""
238
+ word = "".join(self._code_word)
239
+ self._code_word.clear()
240
+ if not word:
241
+ return
242
+ # `fullmatch` rather than a substring test: the buffer already holds one
243
+ # complete token, so a partial match would style `1.2.3` or `a-2` as a
244
+ # number or variable and leave the rest of the token uncolored.
245
+ if _KEYWORD_RE.fullmatch(word):
246
+ style = self.palette["code_keyword"]
247
+ elif _NUMBER_RE.fullmatch(word):
248
+ style = self.palette["code_number"]
249
+ elif _VARIABLE_RE.fullmatch(word):
250
+ style = self.palette["code_variable"]
251
+ else:
252
+ style = self.palette["code_name"]
253
+ self._append(segments, word, style)
254
+
255
+ def _reset_code_line(self) -> None:
256
+ """Reset per-line string, comment, and token state."""
257
+ self._code_comment = False
258
+ self._code_quote = None
259
+ self._code_escape = False
260
+ self._code_last = ""
261
+ self._code_word.clear()
@@ -0,0 +1,58 @@
1
+ """Terminal color capability detection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import TextIO
7
+
8
+
9
+ def _supports_color(stream: TextIO) -> bool:
10
+ """Return True when ANSI styling is safe for ``stream``.
11
+
12
+ ``NO_COLOR`` and ``TERM=dumb`` win over TTY detection, matching common CLI
13
+ conventions. Windows console mode is enabled as a side effect.
14
+ """
15
+ if os.environ.get("NO_COLOR") is not None:
16
+ # Presence, not value, follows the NO_COLOR convention: `NO_COLOR=` with an
17
+ # empty value still means "no color".
18
+ return False
19
+ if os.environ.get("TERM", "").lower() == "dumb":
20
+ return False
21
+ try:
22
+ if not stream.isatty():
23
+ return False
24
+ except (AttributeError, OSError, ValueError):
25
+ # Streams that cannot report a terminal (closed pipe, detached buffer)
26
+ # are treated as unstyled rather than failing the write.
27
+ return False
28
+ if os.name == "nt":
29
+ # A console that refuses virtual-terminal mode would print the SGR
30
+ # bytes literally; report unstyled output instead.
31
+ return _enable_windows_ansi(stream)
32
+ return True
33
+
34
+
35
+ def _enable_windows_ansi(stream: TextIO) -> bool:
36
+ """Enable virtual-terminal processing on a Windows console handle.
37
+
38
+ Returns False only when a Windows console refused the mode change; callers
39
+ that write raw escape sequences use that to avoid printing them literally.
40
+ """
41
+ if os.name != "nt":
42
+ return True
43
+ try:
44
+ import ctypes
45
+ import msvcrt
46
+
47
+ handle = msvcrt.get_osfhandle(stream.fileno())
48
+ kernel32 = ctypes.windll.kernel32
49
+ mode = ctypes.c_uint32()
50
+ # 0x0004 is ENABLE_VIRTUAL_TERMINAL_PROCESSING. The current mode is read
51
+ # first and the bit is OR-ed in so other console flags (line input, echo
52
+ # state) survive; SetConsoleMode alone would clear them.
53
+ if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
54
+ return False
55
+ return bool(kernel32.SetConsoleMode(handle, mode.value | 0x0004))
56
+ except (AttributeError, OSError, ValueError):
57
+ # Styling is cosmetic; a console API failure must not break output.
58
+ return False