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.
- minimalcode/__init__.py +3 -0
- minimalcode/__main__.py +3 -0
- minimalcode/agent.py +1084 -0
- minimalcode/cli.py +1520 -0
- minimalcode/clipboard.py +212 -0
- minimalcode/config.py +1065 -0
- minimalcode/defaults.json +491 -0
- minimalcode/errors.py +31 -0
- minimalcode/highlight/__init__.py +47 -0
- minimalcode/highlight/code.py +261 -0
- minimalcode/highlight/color.py +58 -0
- minimalcode/highlight/markdown.py +710 -0
- minimalcode/highlight/palette.py +73 -0
- minimalcode/highlight/sanitize.py +175 -0
- minimalcode/highlight/thinking.py +71 -0
- minimalcode/httpclient.py +278 -0
- minimalcode/importer.py +253 -0
- minimalcode/prompt.py +195 -0
- minimalcode/provider/__init__.py +28 -0
- minimalcode/provider/anthropic.py +805 -0
- minimalcode/provider/anthropic_env.py +161 -0
- minimalcode/provider/chat.py +394 -0
- minimalcode/provider/client.py +243 -0
- minimalcode/provider/common.py +176 -0
- minimalcode/provider/deepseek_web.py +1158 -0
- minimalcode/provider/factory.py +63 -0
- minimalcode/provider/responses.py +692 -0
- minimalcode/provider/sha3_wasm_bg.wasm +0 -0
- minimalcode/provider/transport.py +241 -0
- minimalcode/provider/types.py +54 -0
- minimalcode/sandbox/__init__.py +64 -0
- minimalcode/sandbox/audit.py +120 -0
- minimalcode/sandbox/backends.py +343 -0
- minimalcode/sandbox/execution.py +435 -0
- minimalcode/sandbox/network.py +793 -0
- minimalcode/sandbox/policy.py +253 -0
- minimalcode/sandbox/runner.py +347 -0
- minimalcode/sandbox/sensitive.py +286 -0
- minimalcode/session.py +465 -0
- minimalcode/shell.py +333 -0
- minimalcode/subagent.py +120 -0
- minimalcode/tools/__init__.py +46 -0
- minimalcode/tools/files.py +1139 -0
- minimalcode/tools/process.py +164 -0
- minimalcode/tools/remote.py +74 -0
- minimalcode/tools/runtime.py +219 -0
- minimalcode/tools/schemas.py +418 -0
- minimalcode/tools/search.py +521 -0
- minimalcode/tools/search_tools.py +583 -0
- minimalcode/tools/validation.py +99 -0
- minimalcode/vision.py +231 -0
- minimalcode/web.py +238 -0
- minimalcode-1.5.2.dist-info/METADATA +646 -0
- minimalcode-1.5.2.dist-info/RECORD +58 -0
- minimalcode-1.5.2.dist-info/WHEEL +5 -0
- minimalcode-1.5.2.dist-info/entry_points.txt +2 -0
- minimalcode-1.5.2.dist-info/licenses/LICENSE +201 -0
- 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
|