regcode 0.6.3__tar.gz → 0.8.0__tar.gz
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.
- {regcode-0.6.3 → regcode-0.8.0}/AGENTS.md +1 -9
- {regcode-0.6.3 → regcode-0.8.0}/PKG-INFO +3 -1
- {regcode-0.6.3 → regcode-0.8.0}/pyproject.toml +3 -1
- {regcode-0.6.3 → regcode-0.8.0}/regcode/__init__.py +1 -1
- regcode-0.8.0/regcode/clipboard.py +378 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/config.py +1 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/main.py +376 -27
- regcode-0.8.0/regcode/paste.py +188 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tools/builtins.py +259 -3
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tui.py +353 -27
- regcode-0.8.0/tests/test_clipboard.py +502 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_main.py +12 -9
- {regcode-0.6.3 → regcode-0.8.0}/uv.lock +23 -1
- {regcode-0.6.3 → regcode-0.8.0}/.gitignore +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/.gitlab-ci.yml +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/.python-version +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/.qwen/settings.json +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/GOALS.md +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/README.md +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/config.yaml.template +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/main.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/cli.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/conversation_manager.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/dependencies.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/monty_sandbox.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/permissions.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/sandbox.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tools/__init__.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tools/base.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tools/registry.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tools/review_notes.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tools/web_crawl.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tools/web_search.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/regcode/tracing.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/__init__.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/conftest.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_api.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_cli.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_config.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_context_manager.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_dependencies.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_monty_sandbox.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_osv_cve.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_provider_extra.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_review_notes.py +0 -0
- {regcode-0.6.3 → regcode-0.8.0}/tests/test_web_search.py +0 -0
|
@@ -3,25 +3,17 @@ Always make sure to comply to these rules. Under Project Stack, you will find te
|
|
|
3
3
|
|
|
4
4
|
## Project Stack:
|
|
5
5
|
- `uv`: Dependency management for Python runtime
|
|
6
|
-
- Alembic: Database migrations
|
|
7
|
-
- FastAPI: Backend Logic
|
|
8
|
-
- SQLAlchemy ORM: Python database ORM
|
|
9
|
-
- PostgreSQL/SQLITE: Target databases
|
|
10
6
|
- Pytest: Test suite under `tests`
|
|
11
|
-
- NextJS/React: Frontend Code under `frontend/`
|
|
12
7
|
- GitLab: Code Repository/ CI CD
|
|
13
8
|
- .gitlab-ci.yml: Gitlab Runner Config
|
|
14
9
|
- `ruff`: Linting and Code Quality Control
|
|
15
|
-
- `docker`: Deployment is done via `docker-compose.yml`
|
|
16
10
|
- `pyproject.toml`: Project Setup Config
|
|
17
11
|
|
|
18
12
|
## Coding/Implentation Instructions:
|
|
19
|
-
|
|
20
|
-
- Use `alembic revision -m <revision explanation>` to create new revisions. Making arbitrary strings as versions etc. is PROHIBITED!
|
|
13
|
+
|
|
21
14
|
- ALWAYS run `uv run ruff check --fix` after all changes at the end and fix linting issues
|
|
22
15
|
- ALWAYS run `uv run pytest` to run test suite after all changes and fix issues shown by tests.
|
|
23
16
|
- ALWAYS add test cases if relevant.
|
|
24
|
-
- NEVER use emojis! Use icon packs (for example `lucid-react`) if needed.
|
|
25
17
|
- NEVER commit changes. It is upto user to review your code and commit and push it. Unless user specifically asks for it, commiting code is PROHIBITED!
|
|
26
18
|
|
|
27
19
|
## Project Vision
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: regcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Minimalistic coding agent with Python API and CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: click>=8.0
|
|
7
7
|
Requires-Dist: crawl4ai>=0.4.0
|
|
8
8
|
Requires-Dist: litellm>=1.83.17
|
|
9
9
|
Requires-Dist: mlflow>=3.14.0
|
|
10
|
+
Requires-Dist: pathspec>=1.1.1
|
|
10
11
|
Requires-Dist: pydantic-monty>=0.0.18
|
|
11
12
|
Requires-Dist: pydantic-settings>=2.14.2
|
|
13
|
+
Requires-Dist: pyperclip>=1.9.0
|
|
12
14
|
Requires-Dist: pytest>=8.0
|
|
13
15
|
Requires-Dist: pyyaml>=6.0
|
|
14
16
|
Requires-Dist: ruff>=0.8
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "regcode"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
description = "Minimalistic coding agent with Python API and CLI"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -15,6 +15,8 @@ dependencies = [
|
|
|
15
15
|
"pydantic-monty>=0.0.18",
|
|
16
16
|
"mlflow>=3.14.0",
|
|
17
17
|
"textual>=2.0.0",
|
|
18
|
+
"pathspec>=1.1.1",
|
|
19
|
+
"pyperclip>=1.9.0",
|
|
18
20
|
]
|
|
19
21
|
|
|
20
22
|
[project.scripts]
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Clipboard utilities for image and text detection, with base64 encoding.
|
|
2
|
+
|
|
3
|
+
Supports Wayland (wl-paste) and X11 (xclip/xsel) and WSL clipboard backends.
|
|
4
|
+
Detects image vs text content and provides base64 encoding for images.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import logging
|
|
11
|
+
import mimetypes
|
|
12
|
+
import subprocess
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ClipboardContent:
|
|
21
|
+
"""Represents clipboard content with type information."""
|
|
22
|
+
|
|
23
|
+
content: str # Base64 for images, raw text for text
|
|
24
|
+
content_type: str # "image" or "text"
|
|
25
|
+
mime_type: str # e.g., "image/png", "text/plain"
|
|
26
|
+
is_image: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Image extensions to MIME types mapping
|
|
30
|
+
IMAGE_EXTENSIONS: dict[str, str] = {
|
|
31
|
+
".png": "image/png",
|
|
32
|
+
".jpg": "image/jpeg",
|
|
33
|
+
".jpeg": "image/jpeg",
|
|
34
|
+
".gif": "image/gif",
|
|
35
|
+
".webp": "image/webp",
|
|
36
|
+
".bmp": "image/bmp",
|
|
37
|
+
".tiff": "image/tiff",
|
|
38
|
+
".svg": "image/svg+xml",
|
|
39
|
+
".ico": "image/x-icon",
|
|
40
|
+
".avif": "image/avif",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def detect_image_mime_type(ext: str) -> str:
|
|
45
|
+
"""Detect MIME type from file extension."""
|
|
46
|
+
ext = ext.lower()
|
|
47
|
+
return IMAGE_EXTENSIONS.get(ext, "application/octet-stream")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_clipboard_backend() -> str:
|
|
51
|
+
"""Detect which clipboard backend is available.
|
|
52
|
+
|
|
53
|
+
Returns one of: 'wl-clipboard', 'xclip', 'xsel', 'wsl', or empty string.
|
|
54
|
+
"""
|
|
55
|
+
# Check Wayland first
|
|
56
|
+
try:
|
|
57
|
+
result = subprocess.run(
|
|
58
|
+
["which", "wl-paste"],
|
|
59
|
+
capture_output=True,
|
|
60
|
+
text=True,
|
|
61
|
+
)
|
|
62
|
+
if result.returncode == 0:
|
|
63
|
+
return "wl-clipboard"
|
|
64
|
+
except (FileNotFoundError, OSError):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
# Check X11
|
|
68
|
+
try:
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
["which", "xclip"],
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True,
|
|
73
|
+
)
|
|
74
|
+
if result.returncode == 0:
|
|
75
|
+
return "xclip"
|
|
76
|
+
except (FileNotFoundError, OSError):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
["which", "xsel"],
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True,
|
|
84
|
+
)
|
|
85
|
+
if result.returncode == 0:
|
|
86
|
+
return "xsel"
|
|
87
|
+
except (FileNotFoundError, OSError):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
# Check WSL clipboard integration (clip.exe + powershell.exe)
|
|
91
|
+
try:
|
|
92
|
+
result = subprocess.run(
|
|
93
|
+
["which", "clip.exe"],
|
|
94
|
+
capture_output=True,
|
|
95
|
+
text=True,
|
|
96
|
+
)
|
|
97
|
+
if result.returncode == 0:
|
|
98
|
+
result2 = subprocess.run(
|
|
99
|
+
["which", "powershell.exe"],
|
|
100
|
+
capture_output=True,
|
|
101
|
+
text=True,
|
|
102
|
+
)
|
|
103
|
+
if result2.returncode == 0:
|
|
104
|
+
return "wsl"
|
|
105
|
+
except (FileNotFoundError, OSError):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
return ""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def read_clipboard_text(backend: str) -> str:
|
|
112
|
+
"""Read plain text from clipboard.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
backend: Clipboard backend to use ('wl-clipboard', 'xclip', 'xsel', 'wsl', '')
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Text content from clipboard, or empty string on failure.
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
if backend == "wl-clipboard":
|
|
122
|
+
result = subprocess.run(
|
|
123
|
+
["wl-paste", "-t", "text/plain"],
|
|
124
|
+
capture_output=True,
|
|
125
|
+
text=True,
|
|
126
|
+
timeout=5,
|
|
127
|
+
)
|
|
128
|
+
elif backend == "xclip":
|
|
129
|
+
result = subprocess.run(
|
|
130
|
+
["xclip", "-selection", "clipboard", "-o"],
|
|
131
|
+
capture_output=True,
|
|
132
|
+
text=True,
|
|
133
|
+
timeout=5,
|
|
134
|
+
)
|
|
135
|
+
elif backend == "xsel":
|
|
136
|
+
result = subprocess.run(
|
|
137
|
+
["xsel", "--clipboard", "--output"],
|
|
138
|
+
capture_output=True,
|
|
139
|
+
text=True,
|
|
140
|
+
timeout=5,
|
|
141
|
+
)
|
|
142
|
+
elif backend == "wsl":
|
|
143
|
+
# WSL clipboard: use PowerShell to read clipboard
|
|
144
|
+
ps_script = (
|
|
145
|
+
"[Convert]::ToBase64String("
|
|
146
|
+
"[Text.Encoding]::UTF8.GetBytes((Get-Clipboard -Raw)))"
|
|
147
|
+
)
|
|
148
|
+
result = subprocess.run(
|
|
149
|
+
[
|
|
150
|
+
"powershell.exe",
|
|
151
|
+
"-noprofile",
|
|
152
|
+
"-command",
|
|
153
|
+
ps_script,
|
|
154
|
+
],
|
|
155
|
+
capture_output=True,
|
|
156
|
+
text=True,
|
|
157
|
+
timeout=5,
|
|
158
|
+
)
|
|
159
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
160
|
+
try:
|
|
161
|
+
base64_data = result.stdout.strip()
|
|
162
|
+
decoded_bytes = base64.b64decode(base64_data)
|
|
163
|
+
text = decoded_bytes.decode("utf-8")
|
|
164
|
+
# Normalize Windows line endings to Unix for terminal display
|
|
165
|
+
text = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
166
|
+
return text.strip()
|
|
167
|
+
except Exception:
|
|
168
|
+
return ""
|
|
169
|
+
return ""
|
|
170
|
+
else:
|
|
171
|
+
return ""
|
|
172
|
+
|
|
173
|
+
if result.returncode == 0:
|
|
174
|
+
text = result.stdout
|
|
175
|
+
# Normalize Windows line endings to Unix for terminal display
|
|
176
|
+
text = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
177
|
+
return text.strip()
|
|
178
|
+
else:
|
|
179
|
+
logger.debug("Failed to read text from clipboard: %s", result.stderr)
|
|
180
|
+
return ""
|
|
181
|
+
except (subprocess.TimeoutExpired, OSError, subprocess.SubprocessError) as e:
|
|
182
|
+
logger.debug("Error reading clipboard text: %s", e)
|
|
183
|
+
return ""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def read_clipboard_image(backend: str) -> Optional[bytes]:
|
|
187
|
+
"""Read raw image bytes from clipboard.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
backend: Clipboard backend to use
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Raw image bytes, or None if clipboard doesn't contain an image.
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
if backend == "wl-clipboard":
|
|
197
|
+
result = subprocess.run(
|
|
198
|
+
["wl-paste", "-t", "image/png"],
|
|
199
|
+
capture_output=True,
|
|
200
|
+
timeout=5,
|
|
201
|
+
)
|
|
202
|
+
if result.returncode == 0 and len(result.stdout) > 0:
|
|
203
|
+
return result.stdout
|
|
204
|
+
elif backend == "xclip":
|
|
205
|
+
# xclip doesn't support direct image output, need to check with xwd
|
|
206
|
+
# This is a fallback - primary Wayland support is through wl-clipboard
|
|
207
|
+
result = subprocess.run(
|
|
208
|
+
["wl-paste", "-t", "image/png"],
|
|
209
|
+
capture_output=True,
|
|
210
|
+
timeout=5,
|
|
211
|
+
)
|
|
212
|
+
if result.returncode == 0 and len(result.stdout) > 0:
|
|
213
|
+
return result.stdout
|
|
214
|
+
elif backend == "wsl":
|
|
215
|
+
# WSL: load Windows Forms assembly and read clipboard image
|
|
216
|
+
ps_script = (
|
|
217
|
+
"Add-Type -AssemblyName System.Windows.Forms; "
|
|
218
|
+
"$ms = New-Object IO.MemoryStream; "
|
|
219
|
+
"[System.Windows.Forms.Clipboard]::GetImage().Save($ms, "
|
|
220
|
+
"[System.Drawing.Imaging.ImageFormat]::Png); "
|
|
221
|
+
"[Convert]::ToBase64String($ms.ToArray())"
|
|
222
|
+
)
|
|
223
|
+
result = subprocess.run(
|
|
224
|
+
[
|
|
225
|
+
"powershell.exe",
|
|
226
|
+
"-NoProfile",
|
|
227
|
+
"-NonInteractive",
|
|
228
|
+
"-Command",
|
|
229
|
+
ps_script,
|
|
230
|
+
],
|
|
231
|
+
capture_output=True,
|
|
232
|
+
text=True,
|
|
233
|
+
timeout=10,
|
|
234
|
+
)
|
|
235
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
236
|
+
try:
|
|
237
|
+
# PowerShell wraps base64 with newlines, remove all whitespace
|
|
238
|
+
clean = result.stdout.replace("\n", "").replace("\r", "").strip()
|
|
239
|
+
return base64.b64decode(clean)
|
|
240
|
+
except Exception:
|
|
241
|
+
logger.debug("Failed to decode WSL clipboard image base64")
|
|
242
|
+
return None
|
|
243
|
+
except (subprocess.TimeoutExpired, OSError, subprocess.SubprocessError) as e:
|
|
244
|
+
logger.debug("Error reading clipboard image: %s", e)
|
|
245
|
+
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def clipboard_is_image(backend: str) -> bool:
|
|
250
|
+
"""Check if clipboard contains an image.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
backend: Clipboard backend to use
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
True if clipboard contains an image, False otherwise.
|
|
257
|
+
"""
|
|
258
|
+
try:
|
|
259
|
+
if backend == "wl-clipboard":
|
|
260
|
+
result = subprocess.run(
|
|
261
|
+
["wl-paste", "-t", "image/png"],
|
|
262
|
+
capture_output=True,
|
|
263
|
+
timeout=5,
|
|
264
|
+
)
|
|
265
|
+
if result.returncode == 0 and len(result.stdout) > 0:
|
|
266
|
+
# Verify it's actually a PNG
|
|
267
|
+
if result.stdout[:8] == b"\x89PNG\r\n\x1a\n":
|
|
268
|
+
return True
|
|
269
|
+
elif backend in ("xclip", "xsel"):
|
|
270
|
+
# For X11, check if we can extract image
|
|
271
|
+
return read_clipboard_image(backend) is not None
|
|
272
|
+
elif backend == "wsl":
|
|
273
|
+
# For WSL, try to get image via PowerShell
|
|
274
|
+
return read_clipboard_image(backend) is not None
|
|
275
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def get_image_mime_type(data: bytes) -> str:
|
|
282
|
+
"""Detect MIME type from image data bytes.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
data: Raw image bytes
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
MIME type string (e.g., "image/png")
|
|
289
|
+
"""
|
|
290
|
+
# Check PNG signature
|
|
291
|
+
if data[:8] == b"\x89PNG\r\n\x1a\n":
|
|
292
|
+
return "image/png"
|
|
293
|
+
|
|
294
|
+
# Check JPEG signature
|
|
295
|
+
if data[:2] == b"\xff\xd8":
|
|
296
|
+
return "image/jpeg"
|
|
297
|
+
|
|
298
|
+
# Check GIF signature
|
|
299
|
+
if data[:4] in (b"GIF87a", b"GIF89a"):
|
|
300
|
+
return "image/gif"
|
|
301
|
+
|
|
302
|
+
# Check WebP signature
|
|
303
|
+
if data[:4] == b"RIFF" and data[8:12] == b"WEBP":
|
|
304
|
+
return "image/webp"
|
|
305
|
+
|
|
306
|
+
# Fallback: try to detect from magic bytes
|
|
307
|
+
mime_type = mimetypes.guess_type("image.tmp")
|
|
308
|
+
if mime_type and mime_type[0]:
|
|
309
|
+
return mime_type[0]
|
|
310
|
+
|
|
311
|
+
return "application/octet-stream"
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def encode_image_to_base64(data: bytes, mime_type: str = "") -> str:
|
|
315
|
+
"""Encode image bytes to base64 data URL.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
data: Raw image bytes
|
|
319
|
+
mime_type: MIME type of the image
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Base64 data URL string (e.g., "data:image/png;base64,...")
|
|
323
|
+
"""
|
|
324
|
+
if not mime_type:
|
|
325
|
+
mime_type = get_image_mime_type(data)
|
|
326
|
+
|
|
327
|
+
base64_data = base64.b64encode(data).decode("utf-8")
|
|
328
|
+
return f"data:{mime_type};base64,{base64_data}"
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def get_clipboard_content() -> ClipboardContent:
|
|
332
|
+
"""Get clipboard content, auto-detecting image vs text.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
ClipboardContent with detected type and base64-encoded data for images.
|
|
336
|
+
"""
|
|
337
|
+
backend = get_clipboard_backend()
|
|
338
|
+
if not backend:
|
|
339
|
+
return ClipboardContent(
|
|
340
|
+
content="",
|
|
341
|
+
content_type="text",
|
|
342
|
+
mime_type="text/plain",
|
|
343
|
+
is_image=False,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Check if clipboard contains an image
|
|
347
|
+
if clipboard_is_image(backend):
|
|
348
|
+
image_data = read_clipboard_image(backend)
|
|
349
|
+
if image_data is not None:
|
|
350
|
+
mime_type = get_image_mime_type(image_data)
|
|
351
|
+
base64_data = encode_image_to_base64(image_data, mime_type)
|
|
352
|
+
return ClipboardContent(
|
|
353
|
+
content=base64_data,
|
|
354
|
+
content_type="image",
|
|
355
|
+
mime_type=mime_type,
|
|
356
|
+
is_image=True,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Fall back to text
|
|
360
|
+
text_content = read_clipboard_text(backend)
|
|
361
|
+
return ClipboardContent(
|
|
362
|
+
content=text_content,
|
|
363
|
+
content_type="text",
|
|
364
|
+
mime_type="text/plain",
|
|
365
|
+
is_image=False,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def read_multiline_text(backend: str) -> str:
|
|
370
|
+
"""Read potentially multiline text from clipboard.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
backend: Clipboard backend to use
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Multiline text content from clipboard.
|
|
377
|
+
"""
|
|
378
|
+
return read_clipboard_text(backend)
|
|
@@ -125,6 +125,7 @@ class Config(BaseModel):
|
|
|
125
125
|
tool_budget: int = 20
|
|
126
126
|
permissions: list[AgentPermission] = [AgentPermission.READ, AgentPermission.EXECUTE]
|
|
127
127
|
toolset_config: ToolSetConfig = ToolSetConfig()
|
|
128
|
+
debug_mode: bool = False
|
|
128
129
|
|
|
129
130
|
@classmethod
|
|
130
131
|
def load(cls, path: str | None = None) -> "Config":
|