petekit 0.1.0a0__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.
Files changed (39) hide show
  1. petekit-0.1.0a0/LICENSE +22 -0
  2. petekit-0.1.0a0/PKG-INFO +16 -0
  3. petekit-0.1.0a0/README.md +155 -0
  4. petekit-0.1.0a0/petekit/image/__init__.py +9 -0
  5. petekit-0.1.0a0/petekit/image/camera_observer.py +96 -0
  6. petekit-0.1.0a0/petekit/image/image_buffer_manager.py +65 -0
  7. petekit-0.1.0a0/petekit/image/multi_camera_observer.py +133 -0
  8. petekit-0.1.0a0/petekit/image/screenshooter.py +40 -0
  9. petekit-0.1.0a0/petekit/petekit.egg-info/PKG-INFO +16 -0
  10. petekit-0.1.0a0/petekit/petekit.egg-info/SOURCES.txt +37 -0
  11. petekit-0.1.0a0/petekit/petekit.egg-info/dependency_links.txt +1 -0
  12. petekit-0.1.0a0/petekit/petekit.egg-info/requires.txt +11 -0
  13. petekit-0.1.0a0/petekit/petekit.egg-info/top_level.txt +5 -0
  14. petekit-0.1.0a0/petekit/stream/__init__.py +22 -0
  15. petekit-0.1.0a0/petekit/stream/basher.py +235 -0
  16. petekit-0.1.0a0/petekit/stream/connector.py +156 -0
  17. petekit-0.1.0a0/petekit/stream/pattern_matchers/fsa_stream.py +45 -0
  18. petekit-0.1.0a0/petekit/stream/pattern_matchers/grammar_stream.py +38 -0
  19. petekit-0.1.0a0/petekit/stream/pattern_matchers/heavy_hitter_stream.py +48 -0
  20. petekit-0.1.0a0/petekit/stream/pattern_matchers/multiline_regex_stream.py +37 -0
  21. petekit-0.1.0a0/petekit/stream/pattern_matchers/regex_stream.py +155 -0
  22. petekit-0.1.0a0/petekit/stream/sandboxed_basher.py +128 -0
  23. petekit-0.1.0a0/petekit/stream/stream_buffer_manager.py +234 -0
  24. petekit-0.1.0a0/petekit/stream/stream_forwarder.py +33 -0
  25. petekit-0.1.0a0/petekit/stream/stream_observer.py +172 -0
  26. petekit-0.1.0a0/petekit/text/__init__.py +9 -0
  27. petekit-0.1.0a0/petekit/text/buffer_manager.py +465 -0
  28. petekit-0.1.0a0/petekit/text/text_editor.py +186 -0
  29. petekit-0.1.0a0/petekit/text/web_navigator.py +272 -0
  30. petekit-0.1.0a0/petekit/tools/__init__.py +5 -0
  31. petekit-0.1.0a0/petekit/tools/psh.py +304 -0
  32. petekit-0.1.0a0/petekit/utils/__init__.py +5 -0
  33. petekit-0.1.0a0/petekit/utils/camera_driver/__init__.py +9 -0
  34. petekit-0.1.0a0/petekit/utils/camera_driver/camera_driver.py +50 -0
  35. petekit-0.1.0a0/petekit/utils/camera_driver/cv2_camera_driver.py +145 -0
  36. petekit-0.1.0a0/petekit/utils/camera_driver/v4l2_camera_driver.py +147 -0
  37. petekit-0.1.0a0/petekit/utils/three_merge.py +246 -0
  38. petekit-0.1.0a0/pyproject.toml +23 -0
  39. petekit-0.1.0a0/setup.cfg +4 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+ ---------
3
+
4
+ Copyright (c) 2026 Schäfer List Systems GmbH
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: petekit
3
+ Version: 0.1.0a0
4
+ Summary: PeteOS Kit — agentic building blocks for streams, text, and images
5
+ Requires-Python: >=3.10
6
+ License-File: LICENSE
7
+ Requires-Dist: peteos
8
+ Requires-Dist: diff-match-patch
9
+ Provides-Extra: image
10
+ Requires-Dist: opencv-python>=4.8.0; extra == "image"
11
+ Requires-Dist: numpy>=1.24.0; extra == "image"
12
+ Requires-Dist: mss; extra == "image"
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: pytest-asyncio; extra == "dev"
16
+ Dynamic: license-file
@@ -0,0 +1,155 @@
1
+ # PeteOS Kit
2
+
3
+ A collection of PeteOS agentic building blocks for streams, text, and images.
4
+
5
+ PeteOS Kit provides ready-to-use agentic objects you can inherit, compose, and deploy without building from scratch.
6
+
7
+ ## Subpackages
8
+
9
+ - **`peteos_kit.stream`** — stream buffering, pattern matching, shell execution, and network connections
10
+ - **`peteos_kit.text`** — buffer management, text editing, and web navigation
11
+ - **`peteos_kit.image`** — image buffers, camera observation, and screen capture
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from peteos import AgenticObject
17
+ from peteos_kit import RegexStreamObserver, StreamForwarder
18
+
19
+ # Create a composed agent
20
+ class LogMonitor(RegexStreamObserver, StreamForwarder, AgenticObject):
21
+ """You monitor log streams for patterns and forward interesting entries."""
22
+
23
+ # Use it
24
+ monitor = LogMonitor()
25
+ result = await monitor.invoke_agent("Set up a pattern for ERROR messages in my syslog stream.")
26
+ print(result)
27
+ ```
28
+
29
+ ## Key Components
30
+
31
+ ```mermaid
32
+ ---
33
+ title: PeteOS Kit — Agentic Class Inheritance Hierarchy
34
+ ---
35
+ classDiagram
36
+ direction TB
37
+
38
+ class BufferManager {
39
+ <<AgenticObject>>
40
+ }
41
+
42
+ class StreamBufferManager {
43
+ <<AgenticObject>>
44
+ }
45
+
46
+ class StreamObserver {
47
+ <<AgenticObject>>
48
+ }
49
+
50
+ class StreamForwarder {
51
+ <<AgenticObject>>
52
+ }
53
+
54
+ class RegexStreamObserver {
55
+ <<AgenticObject>>
56
+ }
57
+
58
+ class Basher {
59
+ <<AgenticObject>>
60
+ }
61
+
62
+ class SandboxedBasher {
63
+ <<AgenticObject>>
64
+ }
65
+
66
+ class Connector {
67
+ <<AgenticObject>>
68
+ }
69
+
70
+ class TextEditor {
71
+ <<AgenticObject>>
72
+ }
73
+
74
+ class WebNavigator {
75
+ <<AgenticObject>>
76
+ }
77
+
78
+ class NumPyBufferManager {
79
+ <<AgenticObject>>
80
+ }
81
+
82
+ class ImageBufferManager {
83
+ <<AgenticObject>>
84
+ }
85
+
86
+ class CameraObserver {
87
+ <<AgenticObject>>
88
+ }
89
+
90
+ class Screenshooter {
91
+ <<AgenticObject>>
92
+ }
93
+
94
+ %% Base hierarchy
95
+ BufferManager <|-- StreamBufferManager
96
+ StreamBufferManager <|-- StreamObserver
97
+
98
+ %% Action & shell agents on streams
99
+ StreamBufferManager <|-- StreamForwarder
100
+ StreamBufferManager <|-- Basher
101
+ StreamBufferManager <|-- Connector
102
+ Basher <|-- SandboxedBasher
103
+
104
+ %% Pattern matchers
105
+ StreamObserver <|-- RegexStreamObserver
106
+
107
+ %% Text agents
108
+ BufferManager <|-- TextEditor
109
+ BufferManager <|-- WebNavigator
110
+
111
+ %% Image agents
112
+ NumPyBufferManager <|-- ImageBufferManager
113
+ ImageBufferManager <|-- CameraObserver
114
+ ImageBufferManager <|-- Screenshooter
115
+ ```
116
+
117
+ - **`BufferManager`** — in-memory text buffers with search, diff, and edit
118
+ - **`StreamBufferManager`** — rolling, append-only stream buffers with rule-based routing
119
+ - **`StreamObserver`** — observes streams and surfaces unexpected entries to the agent
120
+ - **`RegexStreamObserver`** — regex-based pattern matchers as conditions for stream rules
121
+ - **`TextEditor`** — file editing backed by a multi-file buffer
122
+ - **`WebNavigator`** — web navigation backed by a buffer
123
+ - **`ImageBufferManager`** — numpy-backed image buffers
124
+ - **`CameraObserver`** — multi-camera observation with buffer management
125
+ - **`Screenshooter`** — screen capture into image buffers
126
+ - **`Basher`** / **`SandboxedBasher`** — shell execution with buffer integration
127
+
128
+ ## Installation
129
+
130
+ ```bash
131
+ python -m venv .venv
132
+ source .venv/bin/activate
133
+ pip install peteos_kit
134
+ ```
135
+
136
+ For image processing support:
137
+
138
+ ```bash
139
+ pip install peteos_kit[image]
140
+ ```
141
+
142
+ ## Configuration
143
+
144
+ PeteOS Kit requires a working `peteos.json` in your project root or config directory. See the [PeteOS configuration docs](https://docs.peteos.ai/0.3.x/docs/config/) for details.
145
+
146
+ ## Resources
147
+
148
+ - [PeteOS](https://peteos.ai) — the underlying framework
149
+ - [PeteOS on GitHub](https://github.com/Schafer-List-Systems/peteos) — PeteOS framework source
150
+ - [PeteOS Docs](https://docs.peteos.ai/0.3.x/docs/) — concepts, reference, and best practices
151
+ - [PeteOS Kit on GitHub](https://github.com/Schafer-List-Systems/peteos_kit) — source and issues
152
+
153
+ ## Licensing
154
+
155
+ This project is MIT-licensed. See the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,9 @@
1
+ from petekit.image.image_buffer_manager import ImageBufferManager
2
+ from petekit.image.camera_observer import CameraObserver
3
+ from petekit.image.screenshooter import Screenshooter
4
+
5
+ __all__ = [
6
+ "ImageBufferManager",
7
+ "CameraObserver",
8
+ "Screenshooter",
9
+ ]
@@ -0,0 +1,96 @@
1
+ """CameraObserver - webcam access via a CameraDriver, backed by ImageBufferManager."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import cv2
6
+
7
+ from peteos import AgenticObject
8
+ from peteos.oap.decorators import tool
9
+
10
+ from petekit.utils.camera_driver import V4L2CameraDriver
11
+ from .image_buffer_manager import ImageBufferManager
12
+
13
+
14
+ class CameraObserver(ImageBufferManager, AgenticObject):
15
+ """You are an observer with access to cameras.
16
+
17
+ Only one camera can be open at a time. grab_image stores frames in ImageBufferManager.
18
+ Use read_np_buffer to send a captured frame to the LLM for reasoning.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ driver: CameraDriver | None = None,
24
+ scaling: int | None = None,
25
+ ) -> None:
26
+ super().__init__()
27
+ self._driver: CameraDriver = driver or V4L2CameraDriver()
28
+ self._scaling: int | None = scaling
29
+ self._active_camera_id: int | None = None
30
+
31
+ def _resize_if_needed(self, frame: cv2.Mat) -> cv2.Mat:
32
+ """Resize frame if scaling is configured."""
33
+ if self._scaling is None or self._scaling <= 0:
34
+ return frame
35
+ h, w = frame.shape[:2]
36
+ if w > h:
37
+ new_w = self._scaling
38
+ new_h = round(h * self._scaling / w)
39
+ else:
40
+ new_h = self._scaling
41
+ new_w = round(w * self._scaling / h)
42
+ return cv2.resize(frame, (new_w, new_h))
43
+
44
+ @tool(description="List all available cameras.")
45
+ def list_cameras(self) -> str:
46
+ cameras = self._driver.list_cameras()
47
+ if not cameras:
48
+ return "No cameras found."
49
+ return "Available cameras:\n" + "\n".join(
50
+ f" Camera ID: {cid}, Driver: {self._driver.driver_name}, Description: {name}"
51
+ for cid, name in cameras
52
+ )
53
+
54
+ @tool(description="Open a camera by ID. Must be called before grab_image.")
55
+ def open_camera(self, camera_id: int) -> str:
56
+ if self._active_camera_id is not None:
57
+ return "ERROR: A camera is already open. Call close_camera first."
58
+
59
+ ok = self._driver.open(camera_id)
60
+ if not ok:
61
+ return f"ERROR: Camera {camera_id} could not be opened."
62
+
63
+ self._active_camera_id = camera_id
64
+
65
+ ok, frame, _ = self._driver.grab_frame()
66
+ if not ok or frame is None:
67
+ self._driver.close()
68
+ self._active_camera_id = None
69
+ return f"ERROR: Camera {camera_id} warm-up failed."
70
+
71
+ return f"OK: Camera {camera_id} (driver: {self._driver.driver_name}) opened."
72
+
73
+ @tool(description="Close the currently open camera.")
74
+ def close_camera(self) -> str:
75
+ if self._active_camera_id is None:
76
+ return "No camera is currently open."
77
+
78
+ self._driver.close()
79
+ self._active_camera_id = None
80
+ return "OK: Camera closed."
81
+
82
+ @tool(description="Capture a single image from the open camera and store it in the ImageBufferManager. Pass buffer_name to override the default name 'image:camera:<id>'.")
83
+ def grab_image(self, buffer_name: str | None = None) -> str:
84
+ if self._active_camera_id is None:
85
+ return "ERROR: No camera is open."
86
+
87
+ ok, frame, _ = self._driver.grab_frame()
88
+ if not ok or frame is None:
89
+ return "ERROR: Failed to capture image."
90
+
91
+ frame = self._resize_if_needed(frame)
92
+
93
+ name = buffer_name if buffer_name is not None else f"image:camera:{self._active_camera_id}"
94
+ self._store_np_buffer(name, frame)
95
+
96
+ return f"OK: Image captured ({frame.shape[1]}x{frame.shape[0]}), stored as '{name}'."
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import cv2
6
+
7
+ from peteos import AgenticObject, tool
8
+
9
+ from petekit.numpy_buffer_manager import NumPyBufferManager
10
+
11
+
12
+ @dataclass
13
+ class ImageRegion:
14
+ """Pixel-based region of an image. Coordinates are 0-based and inclusive. (x1, y1) is the top-left corner, (x2, y2) is the bottom-right corner."""
15
+ x1: int
16
+ y1: int
17
+ x2: int
18
+ y2: int
19
+
20
+
21
+ class ImageBufferManager(NumPyBufferManager, AgenticObject):
22
+ """
23
+ - For image-like numpy buffers, you can call read_np_buffer to have a look at them.
24
+ - The read_np_buffer tool will transform image coordinates into numpy coordinates for you.
25
+ - Image-like buffer names are usually prefixed with "image:"
26
+ """
27
+
28
+ @tool(description="Read a NumPy buffer as an image and send it to the LLM for reasoning. Pass an ImageRegion for pixel-based region cropping (0-based, inclusive): (x1, y1) is the top-left corner, (x2, y2) is the bottom-right corner. Omit the region to read the full image.")
29
+ async def read_np_buffer(self, name: str, runner, region: ImageRegion | None = None) -> str:
30
+ """Read a buffer (or region) and send it to the LLM. Returns a hint; the LLM sees the image directly."""
31
+ if name not in self._buffers:
32
+ return f"Error: no buffer named '{name}'. Use copy_np_buffer or an inheriting agent to create it."
33
+ buf = self._buffers[name]
34
+ array = buf.array
35
+
36
+ if region is None:
37
+ region_array = array
38
+ else:
39
+ x1, y1, x2, y2 = region.x1, region.y1, region.x2, region.y2
40
+ if x1 > x2 or y1 > y2:
41
+ return f"Error: region x1={x1} > x2={x2} or y1={y1} > y2={y2}. Buffer shape is {array.shape}."
42
+ if x2 >= array.shape[1] or y2 >= array.shape[0]:
43
+ return f"Error: x2={x2} or y2={y2} exceeds buffer shape {array.shape}."
44
+ region_array = array[y1:y2+1, x1:x2+1]
45
+
46
+ total_pixels = region_array.shape[0] * region_array.shape[1]
47
+ if total_pixels > NumPyBufferManager._MAX_PIXELS:
48
+ actual_w, actual_h = array.shape[1], array.shape[0]
49
+ return (
50
+ f"Requested region for buffer '{name}' has {total_pixels} pixels, exceeding the {NumPyBufferManager._MAX_PIXELS}-pixel limit "
51
+ f"(image is {actual_w}x{actual_h}={actual_w*actual_h} pixels). "
52
+ f"Use a smaller ImageRegion to stay under the limit."
53
+ )
54
+
55
+ encode_ok, png_bytes = cv2.imencode(".png", region_array)
56
+ if not encode_ok:
57
+ return f"Error: cv2.imencode failed for buffer '{name}'."
58
+ image_data = png_bytes.tobytes()
59
+
60
+ await self._send_media(
61
+ data=image_data,
62
+ mime_type="image/png",
63
+ runner=runner,
64
+ )
65
+ return f"Buffer '{name}' ({region_array.shape}) sent to LLM for reasoning."
@@ -0,0 +1,133 @@
1
+ """MultiCameraObserver - manages multiple camera drivers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import cv2
8
+
9
+ from peteos.agentic_objects.camera_driver import CameraDriver
10
+ from peteos.oap.agentic_object import AgenticObject
11
+ from peteos.oap.decorators import tool
12
+
13
+ if TYPE_CHECKING:
14
+ from peteos.session import Session
15
+
16
+
17
+ class MultiCameraObserver(AgenticObject):
18
+ """Observer with access to multiple camera drivers.
19
+
20
+ Registers drivers by name. When listing cameras, returns
21
+ the driver name, camera ID, and description for all drivers.
22
+ Only one camera (from any driver) can be open at a time.
23
+ """
24
+
25
+ def __init__(self, scaling: int | None = None) -> None:
26
+ super().__init__()
27
+ self._drivers: dict[str, CameraDriver] = {}
28
+ self._scaling: int | None = scaling
29
+ self._active_driver: CameraDriver | None = None
30
+ self._active_camera_id: int | None = None
31
+ self._cached_frame: bytes | None = None
32
+ self._cached_shape: tuple[int, int] | None = None
33
+
34
+ def register_driver(self, driver: CameraDriver) -> None:
35
+ """Register a camera driver by name."""
36
+ self._drivers[driver.driver_name] = driver
37
+
38
+ @tool(description="List all available cameras across all registered drivers.")
39
+ def list_cameras(self) -> str:
40
+ cameras: list[tuple[str, int, str]] = []
41
+ for driver in self._drivers.values():
42
+ for cid, desc in driver.list_cameras():
43
+ cameras.append((driver.driver_name, cid, desc))
44
+ if not cameras:
45
+ return "No cameras found."
46
+ lines = []
47
+ for driver_name, cid, desc in cameras:
48
+ lines.append(
49
+ f" Driver: {driver_name}, Camera ID: {cid}, Description: {desc}"
50
+ )
51
+ return "Available cameras:\n" + "\n".join(lines)
52
+
53
+ @tool(description="Open a camera from a specific driver.")
54
+ def open_camera(self, driver_name: str, camera_id: int) -> str:
55
+ if self._active_driver is not None:
56
+ return "ERROR: A camera is already open. Call close_camera first."
57
+
58
+ driver = self._drivers.get(driver_name)
59
+ if driver is None:
60
+ return f"ERROR: Unknown driver '{driver_name}'. Register it first."
61
+
62
+ if not driver.open(camera_id):
63
+ return f"ERROR: Camera {camera_id} on driver '{driver_name}' could not be opened."
64
+
65
+ self._active_driver = driver
66
+ self._active_camera_id = camera_id
67
+ self._cached_frame = None
68
+ self._cached_shape = None
69
+
70
+ # Warm up: grab a frame.
71
+ ok, frame, _ = driver.grab_frame()
72
+ if not ok or frame is None:
73
+ driver.close()
74
+ self._active_driver = None
75
+ self._active_camera_id = None
76
+ self._cached_frame = None
77
+ self._cached_shape = None
78
+ return f"ERROR: Camera warm-up failed on '{driver_name}'."
79
+
80
+ return f"OK: Camera {camera_id} opened (driver: {driver_name})."
81
+
82
+ @tool(description="Close the currently open camera.")
83
+ def close_camera(self) -> str:
84
+ if self._active_driver is None:
85
+ return "No camera is currently open."
86
+
87
+ self._active_driver.close()
88
+ self._active_driver = None
89
+ self._active_camera_id = None
90
+ self._cached_frame = None
91
+ self._cached_shape = None
92
+ return "OK: Camera closed."
93
+
94
+ @tool(description="Capture a single image from the open camera. Caches the frame in memory.")
95
+ def grab_image(self) -> str:
96
+ if self._active_driver is None:
97
+ return "ERROR: No camera is open."
98
+
99
+ ok, frame, (raw_w, raw_h) = self._active_driver.grab_frame()
100
+ if not ok or frame is None:
101
+ return "ERROR: Failed to capture image."
102
+
103
+ # Resize if scaling is configured.
104
+ if self._scaling is not None and self._scaling > 0:
105
+ h, w = frame.shape[:2]
106
+ if w > h:
107
+ new_w = self._scaling
108
+ new_h = round(h * self._scaling / w)
109
+ else:
110
+ new_h = self._scaling
111
+ new_w = round(w * self._scaling / h)
112
+ frame = cv2.resize(frame, (new_w, new_h))
113
+
114
+ # Encode as PNG.
115
+ _, encoded = cv2.imencode(".png", frame)
116
+ self._cached_frame = encoded.tobytes()
117
+ self._cached_shape = (frame.shape[1], frame.shape[0])
118
+ return f"OK: Image captured ({self._cached_shape[0]}x{self._cached_shape[1]}), cached in memory."
119
+
120
+ @tool(description="Read the cached image from the last grab_image call and send it to the session.")
121
+ async def read_cached_image(self, session: "Session | None" = None) -> str:
122
+ if self._cached_frame is None:
123
+ return "ERROR: No image has been captured yet. Call grab_image first."
124
+
125
+ if session is None:
126
+ return "ERROR: Session not available."
127
+
128
+ await self._send_media(
129
+ data=self._cached_frame,
130
+ mime_type="image/png",
131
+ session=session,
132
+ )
133
+ return "OK: Image sent to session."
@@ -0,0 +1,40 @@
1
+ """Screenshooter - capture the screen and store images in the ImageBufferManager."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import cv2
6
+ import mss
7
+ import numpy as np
8
+
9
+ from peteos import AgenticObject, tool
10
+
11
+ from .image_buffer_manager import ImageBufferManager, ImageRegion
12
+
13
+
14
+ class Screenshooter(ImageBufferManager, AgenticObject):
15
+ """
16
+ - You can call the take_screenshoot tool to take a screenshoot and store it into a numby buffer.
17
+ """
18
+
19
+ @tool(description="")
20
+ def take_screenshot(self, name: str, region: ImageRegion | None = None) -> str:
21
+ """
22
+ Take a screenshot and store it into a numby buffer.
23
+ Pass region as a dict with keys x1, y1 (top-left corner) and x2, y2 (bottom-right corner) to capture only that pixel region.
24
+ Omit region entirely to capture the full screen.
25
+ When calling read_np_buffer, use the exact buffer name from the return message.
26
+ """
27
+ with mss.MSS() as s:
28
+ if region is None:
29
+ monitor = s.monitors[0]
30
+ else:
31
+ monitor = (region.x1, region.y1, region.x2, region.y2)
32
+ screenshot = s.grab(monitor)
33
+
34
+ array_bgr = cv2.cvtColor(np.asarray(screenshot), cv2.COLOR_BGRA2BGR)
35
+
36
+ buffer_name = name if name.startswith("image:") else f"image:{name}"
37
+ self._store_np_buffer(buffer_name, array_bgr)
38
+
39
+ h, w = array_bgr.shape[:2]
40
+ return f"Screenshot stored in buffer '{buffer_name}' ({w}x{h} pixels). Use read_np_buffer to send it to the LLM."
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: petekit
3
+ Version: 0.1.0a0
4
+ Summary: PeteOS Kit — agentic building blocks for streams, text, and images
5
+ Requires-Python: >=3.10
6
+ License-File: LICENSE
7
+ Requires-Dist: peteos
8
+ Requires-Dist: diff-match-patch
9
+ Provides-Extra: image
10
+ Requires-Dist: opencv-python>=4.8.0; extra == "image"
11
+ Requires-Dist: numpy>=1.24.0; extra == "image"
12
+ Requires-Dist: mss; extra == "image"
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: pytest-asyncio; extra == "dev"
16
+ Dynamic: license-file
@@ -0,0 +1,37 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ petekit/image/__init__.py
5
+ petekit/image/camera_observer.py
6
+ petekit/image/image_buffer_manager.py
7
+ petekit/image/multi_camera_observer.py
8
+ petekit/image/screenshooter.py
9
+ petekit/petekit.egg-info/PKG-INFO
10
+ petekit/petekit.egg-info/SOURCES.txt
11
+ petekit/petekit.egg-info/dependency_links.txt
12
+ petekit/petekit.egg-info/requires.txt
13
+ petekit/petekit.egg-info/top_level.txt
14
+ petekit/stream/__init__.py
15
+ petekit/stream/basher.py
16
+ petekit/stream/connector.py
17
+ petekit/stream/sandboxed_basher.py
18
+ petekit/stream/stream_buffer_manager.py
19
+ petekit/stream/stream_forwarder.py
20
+ petekit/stream/stream_observer.py
21
+ petekit/stream/pattern_matchers/fsa_stream.py
22
+ petekit/stream/pattern_matchers/grammar_stream.py
23
+ petekit/stream/pattern_matchers/heavy_hitter_stream.py
24
+ petekit/stream/pattern_matchers/multiline_regex_stream.py
25
+ petekit/stream/pattern_matchers/regex_stream.py
26
+ petekit/text/__init__.py
27
+ petekit/text/buffer_manager.py
28
+ petekit/text/text_editor.py
29
+ petekit/text/web_navigator.py
30
+ petekit/tools/__init__.py
31
+ petekit/tools/psh.py
32
+ petekit/utils/__init__.py
33
+ petekit/utils/three_merge.py
34
+ petekit/utils/camera_driver/__init__.py
35
+ petekit/utils/camera_driver/camera_driver.py
36
+ petekit/utils/camera_driver/cv2_camera_driver.py
37
+ petekit/utils/camera_driver/v4l2_camera_driver.py
@@ -0,0 +1,11 @@
1
+ peteos
2
+ diff-match-patch
3
+
4
+ [dev]
5
+ pytest
6
+ pytest-asyncio
7
+
8
+ [image]
9
+ opencv-python>=4.8.0
10
+ numpy>=1.24.0
11
+ mss
@@ -0,0 +1,5 @@
1
+ image
2
+ stream
3
+ text
4
+ tools
5
+ utils
@@ -0,0 +1,22 @@
1
+ from petekit.stream.stream_buffer_manager import StreamBufferManager, StreamBufferRules, Rule
2
+ from petekit.stream.stream_observer import StreamObserver
3
+ from petekit.stream.stream_forwarder import StreamForwarder
4
+ from petekit.stream.pattern_matchers.regex_stream import RegexStreamObserver, RegexPattern
5
+ from petekit.stream.basher import Basher, BashHandle
6
+ from petekit.stream.connector import Connector, ConnectionHandle
7
+ from petekit.stream.sandboxed_basher import SandboxedBasher
8
+
9
+ __all__ = [
10
+ "StreamBufferManager",
11
+ "StreamBufferRules",
12
+ "Rule",
13
+ "StreamObserver",
14
+ "StreamForwarder",
15
+ "RegexStreamObserver",
16
+ "RegexPattern",
17
+ "Basher",
18
+ "BashHandle",
19
+ "Connector",
20
+ "ConnectionHandle",
21
+ "SandboxedBasher",
22
+ ]