pascal-agent 0.3.0__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.
pascal/uia.py ADDED
@@ -0,0 +1,316 @@
1
+ """Windows UI Automation adapter -- access native app controls via Accessibility API.
2
+
3
+ Uses pywinauto with UIA backend. Falls back gracefully on non-Windows platforms.
4
+ Ref system: each snapshot assigns short IDs (e1, e2, ...) to controls so the LLM
5
+ can refer to them in subsequent actions without needing coordinates.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import time
12
+ from dataclasses import dataclass
13
+ from typing import Any
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class UIElement:
20
+ """Lightweight representation of a UIA control for LLM consumption."""
21
+ ref: str
22
+ control_type: str
23
+ name: str
24
+ value: str
25
+ is_enabled: bool
26
+ is_focused: bool
27
+
28
+ def to_text(self, indent: int = 0) -> str:
29
+ prefix = " " * indent
30
+ parts = [f"{prefix}[{self.ref}] {self.control_type}"]
31
+ if self.name:
32
+ parts.append(f"'{self.name}'")
33
+ if self.value:
34
+ display_val = self.value[:100] + ("..." if len(self.value) > 100 else "")
35
+ parts.append(f'value="{display_val}"')
36
+ flags = []
37
+ if self.is_focused:
38
+ flags.append("focused")
39
+ if not self.is_enabled:
40
+ flags.append("disabled")
41
+ if flags:
42
+ parts.append(f"[{', '.join(flags)}]")
43
+ return " ".join(parts)
44
+
45
+
46
+ _INTERACTIVE_TYPES = frozenset({
47
+ "Button", "Edit", "MenuItem", "ListItem", "ComboBox",
48
+ "CheckBox", "RadioButton", "Hyperlink", "Slider", "Spinner",
49
+ "Tab", "TabItem", "TreeItem", "DataItem",
50
+ })
51
+
52
+
53
+ class UIAutomationAdapter:
54
+ """Windows UI Automation API wrapper using pywinauto.
55
+
56
+ Public methods (7): snapshot, find, click, type_text, get_text, focus_window, wait_for.
57
+ """
58
+
59
+ def __init__(self) -> None:
60
+ self._ref_map: dict[str, Any] = {}
61
+ self._ref_counter: int = 0
62
+ self._desktop = None
63
+
64
+ def _get_desktop(self):
65
+ if self._desktop is None:
66
+ from pywinauto import Desktop
67
+ self._desktop = Desktop(backend="uia")
68
+ return self._desktop
69
+
70
+ # -- Exploration -----------------------------------------------
71
+
72
+ def snapshot(
73
+ self,
74
+ window_title: str | None = None,
75
+ *,
76
+ max_depth: int = 3,
77
+ interactive_only: bool = False,
78
+ ) -> list[UIElement]:
79
+ """Take an accessibility tree snapshot of a window."""
80
+ self.clear_refs()
81
+ win = self._find_window(window_title=window_title)
82
+ if win is None:
83
+ return []
84
+ elements: list[UIElement] = []
85
+ self._walk(win, elements, depth=0, max_depth=max_depth,
86
+ interactive_only=interactive_only)
87
+ return elements
88
+
89
+ def find(
90
+ self,
91
+ *,
92
+ name: str | None = None,
93
+ control_type: str | None = None,
94
+ window_title: str | None = None,
95
+ ) -> list[UIElement]:
96
+ """Search for controls matching criteria (AND combination)."""
97
+ win = self._find_window(window_title=window_title)
98
+ if win is None:
99
+ if window_title:
100
+ return []
101
+ desktop = self._get_desktop()
102
+ results: list[UIElement] = []
103
+ for w in desktop.windows():
104
+ try:
105
+ if not w.is_visible():
106
+ continue
107
+ results.extend(self._search_window(w, name=name, control_type=control_type))
108
+ except Exception:
109
+ continue
110
+ return results
111
+ return self._search_window(win, name=name, control_type=control_type)
112
+
113
+ # -- Actions ---------------------------------------------------
114
+
115
+ def click(self, ref: str) -> dict:
116
+ """Click a control by ref ID."""
117
+ wrapper = self._resolve_ref(ref)
118
+ try:
119
+ wrapper.click_input()
120
+ return {"ok": True, "action": "click", "ref": ref}
121
+ except Exception as e:
122
+ return {"ok": False, "error": str(e), "ref": ref}
123
+
124
+ def type_text(self, ref: str, text: str, *, clear_first: bool = False) -> dict:
125
+ """Type text into an Edit control."""
126
+ wrapper = self._resolve_ref(ref)
127
+ try:
128
+ if clear_first:
129
+ wrapper.set_edit_text("")
130
+ wrapper.type_keys(text, with_spaces=True, pause=0.02)
131
+ return {"ok": True, "action": "type_text", "ref": ref, "chars": len(text)}
132
+ except Exception as e:
133
+ return {"ok": False, "error": str(e), "ref": ref}
134
+
135
+ def get_text(self, ref: str) -> str:
136
+ """Get the current text value of a control."""
137
+ wrapper = self._resolve_ref(ref)
138
+ try:
139
+ for method in ("window_text", "get_value", "texts"):
140
+ try:
141
+ fn = getattr(wrapper, method, None)
142
+ if fn is None:
143
+ continue
144
+ result = fn()
145
+ if isinstance(result, list):
146
+ return "\n".join(str(t) for t in result if t)
147
+ if result:
148
+ return str(result)
149
+ except Exception:
150
+ continue
151
+ return ""
152
+ except Exception:
153
+ return ""
154
+
155
+ # -- Window management -----------------------------------------
156
+
157
+ def focus_window(self, window_title: str | None = None) -> dict:
158
+ """Bring a window to the foreground."""
159
+ win = self._find_window(window_title=window_title)
160
+ if win is None:
161
+ return {"ok": False, "error": f"Window not found: {window_title}"}
162
+ try:
163
+ win.set_focus()
164
+ return {"ok": True, "action": "focus_window", "title": win.window_text()}
165
+ except Exception as e:
166
+ return {"ok": False, "error": str(e)}
167
+
168
+ def wait_for(
169
+ self,
170
+ *,
171
+ name: str | None = None,
172
+ control_type: str | None = None,
173
+ window_title: str | None = None,
174
+ timeout: float = 10.0,
175
+ ) -> UIElement | None:
176
+ """Wait for a control to appear. For dialog popup handling."""
177
+ deadline = time.monotonic() + timeout
178
+ while time.monotonic() < deadline:
179
+ results = self.find(name=name, control_type=control_type, window_title=window_title)
180
+ if results:
181
+ return results[0]
182
+ time.sleep(0.3)
183
+ return None
184
+
185
+ # -- Internal --------------------------------------------------
186
+
187
+ def _assign_ref(self, wrapper) -> str:
188
+ self._ref_counter += 1
189
+ ref = f"e{self._ref_counter}"
190
+ self._ref_map[ref] = wrapper
191
+ return ref
192
+
193
+ def _resolve_ref(self, ref: str):
194
+ if ref not in self._ref_map:
195
+ raise KeyError(f"Unknown ref: {ref}. Run snapshot first.")
196
+ wrapper = self._ref_map[ref]
197
+ try:
198
+ wrapper.is_enabled()
199
+ except Exception:
200
+ raise KeyError(f"Stale ref: {ref}. Run snapshot again.")
201
+ return wrapper
202
+
203
+ def clear_refs(self) -> None:
204
+ self._ref_map.clear()
205
+ self._ref_counter = 0
206
+
207
+ def _find_window(self, window_title: str | None = None):
208
+ desktop = self._get_desktop()
209
+ if window_title:
210
+ title_lower = window_title.lower()
211
+ for win in desktop.windows():
212
+ try:
213
+ wt = win.window_text() or ""
214
+ if title_lower in wt.lower():
215
+ return win
216
+ except Exception:
217
+ continue
218
+ return None
219
+ # No filter: return the foreground window
220
+ import ctypes
221
+ fg_hwnd = ctypes.windll.user32.GetForegroundWindow()
222
+ if fg_hwnd:
223
+ for win in desktop.windows():
224
+ try:
225
+ if win.handle == fg_hwnd:
226
+ return win
227
+ except Exception:
228
+ continue
229
+ return None
230
+
231
+ def _walk(self, wrapper, elements: list[UIElement], depth: int, max_depth: int, interactive_only: bool) -> None:
232
+ if depth > max_depth:
233
+ return
234
+ try:
235
+ ctrl_type = wrapper.element_info.control_type or "Unknown"
236
+ if interactive_only and ctrl_type not in _INTERACTIVE_TYPES and depth > 0:
237
+ for child in wrapper.children():
238
+ self._walk(child, elements, depth + 1, max_depth, interactive_only)
239
+ return
240
+
241
+ name = ""
242
+ try:
243
+ name = wrapper.window_text() or ""
244
+ except Exception:
245
+ pass
246
+
247
+ value = ""
248
+ try:
249
+ if ctrl_type in ("Edit", "ComboBox", "Spinner"):
250
+ for method in ("get_value", "window_text"):
251
+ fn = getattr(wrapper, method, None)
252
+ if fn:
253
+ v = fn()
254
+ if v:
255
+ value = str(v)
256
+ break
257
+ except Exception:
258
+ pass
259
+
260
+ ref = self._assign_ref(wrapper)
261
+ elements.append(UIElement(
262
+ ref=ref, control_type=ctrl_type, name=name, value=value,
263
+ is_enabled=wrapper.is_enabled() if hasattr(wrapper, "is_enabled") else True,
264
+ is_focused=wrapper.has_focus() if hasattr(wrapper, "has_focus") else False,
265
+ ))
266
+
267
+ for child in wrapper.children():
268
+ self._walk(child, elements, depth + 1, max_depth, interactive_only)
269
+ except Exception as e:
270
+ logger.debug("UIA walk error at depth %d: %s", depth, e)
271
+
272
+ def _search_window(self, win, *, name: str | None = None, control_type: str | None = None) -> list[UIElement]:
273
+ results: list[UIElement] = []
274
+ criteria: dict[str, Any] = {}
275
+ if control_type:
276
+ criteria["control_type"] = control_type
277
+ if name:
278
+ import re
279
+ criteria["title_re"] = f".*{re.escape(name)}.*"
280
+ try:
281
+ found = win.descendants(**criteria) if criteria else win.descendants()
282
+ for ctrl in found[:50]:
283
+ try:
284
+ ct = ctrl.element_info.control_type or "Unknown"
285
+ cn = ""
286
+ try:
287
+ cn = ctrl.window_text() or ""
288
+ except Exception:
289
+ pass
290
+ value = ""
291
+ try:
292
+ if ct in ("Edit", "ComboBox"):
293
+ v = ctrl.get_value() if hasattr(ctrl, "get_value") else ""
294
+ value = str(v) if v else ""
295
+ except Exception:
296
+ pass
297
+ ref = self._assign_ref(ctrl)
298
+ results.append(UIElement(
299
+ ref=ref, control_type=ct, name=cn, value=value,
300
+ is_enabled=ctrl.is_enabled(), is_focused=False,
301
+ ))
302
+ except Exception:
303
+ continue
304
+ except Exception as e:
305
+ logger.debug("UIA search error: %s", e)
306
+ return results
307
+
308
+
309
+ def render_snapshot(elements: list[UIElement], window_title: str = "") -> str:
310
+ lines = []
311
+ if window_title:
312
+ lines.append(f'Window: "{window_title}"')
313
+ lines.append("─" * 40)
314
+ for elem in elements:
315
+ lines.append(elem.to_text())
316
+ return "\n".join(lines)
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: pascal-agent
3
+ Version: 0.3.0
4
+ Summary: Pascal — autonomous AI employee runtime with 4-layer safety
5
+ Project-URL: Homepage, https://gitlab.com/laum0621/pascal
6
+ Project-URL: Repository, https://gitlab.com/laum0621/pascal
7
+ Project-URL: Issues, https://gitlab.com/laum0621/pascal/-/issues
8
+ Author: laum0621
9
+ License: MIT
10
+ Keywords: agent,ai,autonomous,employee,llm,tool-use
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: httpx>=0.27
21
+ Requires-Dist: openai>=1.0
22
+ Provides-Extra: all
23
+ Requires-Dist: aiogram>=3.0; extra == 'all'
24
+ Requires-Dist: anthropic>=0.30; extra == 'all'
25
+ Requires-Dist: mcp; extra == 'all'
26
+ Requires-Dist: pillow; extra == 'all'
27
+ Requires-Dist: pyautogui; extra == 'all'
28
+ Requires-Dist: pyperclip; extra == 'all'
29
+ Requires-Dist: pytest; extra == 'all'
30
+ Requires-Dist: pytest-asyncio; extra == 'all'
31
+ Requires-Dist: pywinauto>=0.6.8; extra == 'all'
32
+ Provides-Extra: anthropic
33
+ Requires-Dist: anthropic>=0.30; extra == 'anthropic'
34
+ Provides-Extra: browser
35
+ Requires-Dist: playwright; extra == 'browser'
36
+ Provides-Extra: clipboard
37
+ Requires-Dist: pyperclip; extra == 'clipboard'
38
+ Provides-Extra: desktop
39
+ Requires-Dist: pillow; extra == 'desktop'
40
+ Requires-Dist: pyautogui; extra == 'desktop'
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest; extra == 'dev'
43
+ Requires-Dist: pytest-asyncio; extra == 'dev'
44
+ Provides-Extra: mcp
45
+ Requires-Dist: mcp; extra == 'mcp'
46
+ Provides-Extra: ocr
47
+ Requires-Dist: pytesseract; extra == 'ocr'
48
+ Provides-Extra: telegram
49
+ Requires-Dist: aiogram>=3.0; extra == 'telegram'
50
+ Provides-Extra: uia
51
+ Requires-Dist: pywinauto>=0.6.8; extra == 'uia'
52
+ Description-Content-Type: text/markdown
53
+
54
+ # Pascal — Autonomous AI Employee
55
+
56
+ An autonomous AI agent that works like a real employee: receives tasks, plans, executes, and reports back.
57
+
58
+ ## Getting Started
59
+
60
+ ### Install
61
+
62
+ ```bash
63
+ pip install pascal-agent
64
+ ```
65
+
66
+ Or with all optional features:
67
+ ```bash
68
+ pip install "pascal-agent[all]"
69
+ ```
70
+
71
+ ### Run
72
+
73
+ ```bash
74
+ pascal
75
+ ```
76
+
77
+ That's it. On first run, Pascal auto-detects your LLM provider or walks you through setup.
78
+
79
+ ### Manual setup (optional)
80
+
81
+ ```bash
82
+ # Interactive setup
83
+ pascal setup
84
+
85
+ # Or set individually
86
+ pascal config set provider openai
87
+ pascal config set model gpt-5.4-mini
88
+ pascal config
89
+ ```
90
+
91
+ **Provider setup:**
92
+
93
+ | Provider | Auth | How |
94
+ |----------|------|-----|
95
+ | OpenAI | API key | `export OPENAI_API_KEY=sk-...` |
96
+ | Anthropic | API key | `export ANTHROPIC_API_KEY=sk-ant-...` |
97
+ | Codex | ChatGPT Pro OAuth | `codex auth login` (free with Pro subscription) |
98
+
99
+ ## Usage
100
+
101
+ ```bash
102
+ # Interactive mode (default)
103
+ pascal
104
+ > Summarize the files in this directory
105
+ > Read README.md and explain the architecture
106
+ > exit
107
+
108
+ # One-shot task
109
+ pascal "Write a Python script that downloads weather data"
110
+
111
+ # Set a mission (persistent context)
112
+ pascal --mission "You are a data analyst for the marketing team"
113
+
114
+ # Check current state
115
+ pascal --status
116
+
117
+ # Always-on daemon with Telegram
118
+ pascal --daemon
119
+
120
+ # Resume a paused task
121
+ pascal --resume task_abc123
122
+ ```
123
+
124
+ ## Configuration
125
+
126
+ ### Config file (`~/.pascal/pascal.toml`)
127
+
128
+ ```toml
129
+ [pascal]
130
+ model = "gpt-5.4-mini"
131
+ provider = "openai" # openai | anthropic | codex
132
+ db_path = "~/.pascal/state.db"
133
+ max_effect = "E2" # E0=read E1=analyze E2=write E3=push E4=merge E5=delete
134
+ max_tool_rounds = 10 # max tool calls per LLM turn
135
+ ```
136
+
137
+ ### CLI config commands
138
+
139
+ ```bash
140
+ pascal config # Show all settings
141
+ pascal config set model gpt-5.4-mini # Set a value
142
+ pascal config get provider # Get a value
143
+ ```
144
+
145
+ ### Environment variables (override config file)
146
+
147
+ ```bash
148
+ PASCAL_MODEL=gpt-5.4-mini
149
+ PASCAL_PROVIDER=openai
150
+ PASCAL_MAX_EFFECT=E2
151
+ ```
152
+
153
+ API keys can be set as environment variables or saved to `~/.pascal/.env` (auto-loaded).
154
+
155
+ ### Optional integrations
156
+
157
+ **Telegram bot** (`~/.pascal/telegram.json`):
158
+ ```json
159
+ {"bot_token": "123:ABC...", "owner_chat_id": 12345}
160
+ ```
161
+
162
+ **MCP tool servers** (`~/.pascal/mcp.json`):
163
+ ```json
164
+ [{"name": "chrome", "command": "npx", "args": ["chrome-devtools-mcp@latest"]}]
165
+ ```
166
+
167
+ **Custom skills** (`~/.pascal/skills/my-skill.md`):
168
+ ```yaml
169
+ ---
170
+ name: my-skill
171
+ description: What this skill does
172
+ ---
173
+ Instructions for Pascal when this skill is activated...
174
+ ```
175
+
176
+ ## How It Works
177
+
178
+ ```
179
+ pascal "Do something"
180
+ |
181
+ v
182
+ +---------------------------+
183
+ | Desk compiles state | SQLite -> text prompt
184
+ | LLM decides next action | 22 action types via function calling
185
+ | Execute through safety | Effect Ladder + Trust Scanner + Sandbox
186
+ | Record to audit ledger | Hash-chained, append-only
187
+ | Repeat |
188
+ +---------------------------+
189
+ |
190
+ v
191
+ Task complete / wait / escalate
192
+ ```
193
+
194
+ **22 actions**: think, execute, plan, delegate, pick_task, create_task, create_subtask, complete_task, fail_task, pause_task, block_task, handle_notification, dismiss_notification, add_todo, complete_todo, memorize, add_rule, remove_rule, set_context, wait, escalate
195
+
196
+ **3 LLM providers**: OpenAI, Anthropic Claude, Codex (ChatGPT Pro)
197
+
198
+ **4-layer safety**: Effect Ladder (E0-E5) | Trust Scanner | Sandbox (Docker/Restricted) | TrustMap + Audit Ledger
199
+
200
+ ## Daemon Mode
201
+
202
+ Always-on operation with Telegram integration:
203
+
204
+ ```bash
205
+ # Setup Telegram first
206
+ echo '{"bot_token": "YOUR_TOKEN", "owner_chat_id": YOUR_ID}' > ~/.pascal/telegram.json
207
+
208
+ # Start daemon
209
+ pascal --daemon
210
+ ```
211
+
212
+ Features:
213
+ - Telegram DM for tasks and approvals
214
+ - Adaptive heartbeat (5min active, 30min idle)
215
+ - Auto-restart on crash
216
+ - STOP/PAUSE control (`~/.pascal/STOP` or `~/.pascal/PAUSE` file)
217
+
218
+ ## Development
219
+
220
+ ```bash
221
+ git clone https://gitlab.com/laum0621/pascal.git
222
+ cd pascal
223
+ pip install -e ".[dev]"
224
+
225
+ # Tests (245 pass)
226
+ pytest
227
+
228
+ # Lint (0 errors)
229
+ ruff check src/ tests/
230
+
231
+ # Type check (0 errors)
232
+ mypy src/pascal/
233
+ ```
234
+
235
+ ## Project Structure
236
+
237
+ ```
238
+ src/pascal/
239
+ loop.py ........... Core tool-use loop (LoopRunner)
240
+ actions.py ........ 22 action handlers (ActionContext)
241
+ state.py .......... SQLite persistence (9 tables, FTS5)
242
+ desk.py ........... State -> LLM prompt compiler
243
+ tools.py .......... Built-in tools (file, desktop, UIA, clipboard)
244
+ effect.py ......... Effect Ladder (E0-E5, hard regex rules)
245
+ trust.py .......... Input scanner (injection, credentials, destructive)
246
+ capability.py ..... Domain trust map (asymmetric learning)
247
+ sandbox.py ........ Docker + Restricted sandbox
248
+ receipts.py ....... Hash-chained audit ledger
249
+ scheduler.py ...... Cron tick + self-evolution
250
+ daemon.py ......... Always-on mode (Telegram + loop + scheduler)
251
+ config.py ......... Config loader (CLI > env > TOML > defaults)
252
+ schemas.py ........ Tool JSON schemas for LLM function calling
253
+ prompt.py ......... System prompt
254
+ types.py .......... Shared DTOs
255
+ llm/ .............. OpenAI, Anthropic, Codex providers
256
+ channels/ ......... Telegram adapter
257
+ uia.py ............ Windows UI Automation
258
+ ```
259
+
260
+ ## License
261
+
262
+ MIT
@@ -0,0 +1,33 @@
1
+ pascal/__init__.py,sha256=qoVW956gL56Q-xB9SincPJv7E-SJbFLOWCJZOD1P6xI,99
2
+ pascal/__main__.py,sha256=_MvAcOD5REu2hGesPP1f-RozuJ0ZFUncO-d5ZIWfOd8,31261
3
+ pascal/actions.py,sha256=KWaP24ibmu_gJrnEoYU7yiZ8zr_gWXifkNHyVuLApmk,45575
4
+ pascal/capability.py,sha256=2K0sqrKwX0TB8BYC-jGZSfffaWhOEfvyunQOJOLAkAI,6976
5
+ pascal/clipboard.py,sha256=8X_uzQv51crOgzaWUzbDi86YSK3Q4HC34oW6EaztIa8,1055
6
+ pascal/config.py,sha256=UpaMpxuBvfugeyYrpOXfCJ2Vl_BFEJh9EWDvn_XwoN4,4555
7
+ pascal/daemon.py,sha256=OsTLb2tFX72M6JM5gN8mwnjPFGNAgyFJVSgOTsL4Ids,8245
8
+ pascal/desk.py,sha256=phQgVRlEaLMyKzftdzLGlySL8F7UrnHRu7dm73iX0w0,28530
9
+ pascal/effect.py,sha256=1hRDksjfFGpYkaWpKGOC4VDNXYky-wPPsEZhlTYC00Q,6827
10
+ pascal/loop.py,sha256=mZaWsCTro3QRwBTpp3zwDxq_V1PJyEWuQvEywWW8puQ,42991
11
+ pascal/mcp.py,sha256=e9v0HhJdS5hK0fN1eUlQ0mM4V0AI8463QMiBsBNomT8,7726
12
+ pascal/prompt.py,sha256=X-pAaXqZ-qRqy0unt0BjUNF3be1zR701-_Nenb5o_H4,8204
13
+ pascal/receipts.py,sha256=7Ctuszf59jY6pYXhvzht9ymPdCbsk9R_pe7TOiBLvoc,5386
14
+ pascal/sandbox.py,sha256=wF5HtUj84Lw87n6jYJ6Me53f4HhGdzPwfOkiajRW8jc,11593
15
+ pascal/scheduler.py,sha256=soVTVWuRt0-0ogAh9Fkv-vwYUmDesZYcnsgzfCR7HYM,9372
16
+ pascal/schemas.py,sha256=GuSFNeMNK31YJA-sdPU37Ipp1uzfJCuV79IU3AINVz0,6895
17
+ pascal/state.py,sha256=9fi5dXZX0dXfmw4JvpwPaAhTKzhHC8VzON3Qcbjc5fQ,33988
18
+ pascal/tools.py,sha256=iOSSNyI0AvyZ5WFqdMpa79DOij2YKK9UFkl82OhFZYE,25937
19
+ pascal/trust.py,sha256=AXbIO1b2w-LLImf7AIcAdtULbfMxNKcOFJDbba3cMIA,6566
20
+ pascal/types.py,sha256=RA5k_oX07WdvNr4fEHMF5TALW5X0GvlpScoADOU_bVk,11253
21
+ pascal/uia.py,sha256=JIrjhPO94xMW1PdxAi-Qve6nBT7qMWNyfu5p8foz7WQ,11317
22
+ pascal/channels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ pascal/channels/telegram.py,sha256=YVKqcmfXpwbOKQGLd805ZetDL2N9AsyRdUPKjhSBH10,4176
24
+ pascal/eval/__init__.py,sha256=BE0F0R1Uu2ISh4aBs28nvdlGOhir1jaXBut1N2A4zRM,38
25
+ pascal/eval/smoke.py,sha256=A10fFDU3SeIJllO4CfSMROSZ0xpbyEEVDORLuTvi6ck,8163
26
+ pascal/llm/__init__.py,sha256=aYJ4l4q9lT-Zp2sRcw023vt3kGXt8Wl4dQWq9kR74iA,30
27
+ pascal/llm/anthropic.py,sha256=EZxQ6ebs0x9TQ5s2vTGOAGXHFeGc8zM9kUeDMan8xBo,8129
28
+ pascal/llm/codex.py,sha256=zwAmjqhyLL6Iiz8Rn3OSo3i37yrkhBYP1Dc_LJqzJHg,13473
29
+ pascal/llm/openai.py,sha256=hmMCqq6m0YyBm8A6oKGBJQR1S5kZgYtqJS6kbXOzAHg,8038
30
+ pascal_agent-0.3.0.dist-info/METADATA,sha256=0yYpiWrTUeeKKIS6VZOKZG_p5g5aGLoR5D7jwo2W4ec,7188
31
+ pascal_agent-0.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
32
+ pascal_agent-0.3.0.dist-info/entry_points.txt,sha256=EfiOsglBJQ51v56-BCFIQ7LB0JKpuDCS3sRIjLm0zX8,48
33
+ pascal_agent-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pascal = pascal.__main__:main