pcli-agent 0.1.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.
Files changed (130) hide show
  1. pcli/__init__.py +1 -0
  2. pcli/__main__.py +4 -0
  3. pcli/agent/__init__.py +0 -0
  4. pcli/agent/activity.py +116 -0
  5. pcli/agent/compaction.py +205 -0
  6. pcli/agent/context_pruning.py +88 -0
  7. pcli/agent/headless.py +209 -0
  8. pcli/agent/loop.py +442 -0
  9. pcli/agent/prompt.py +371 -0
  10. pcli/agent/runtime.py +240 -0
  11. pcli/browser/__init__.py +0 -0
  12. pcli/browser/session.py +135 -0
  13. pcli/cli.py +757 -0
  14. pcli/config/__init__.py +0 -0
  15. pcli/config/paths.py +95 -0
  16. pcli/config/settings.py +435 -0
  17. pcli/cost/__init__.py +0 -0
  18. pcli/cost/context.py +275 -0
  19. pcli/cost/context_detect.py +183 -0
  20. pcli/cost/pricing_table.py +141 -0
  21. pcli/cost/tracker.py +126 -0
  22. pcli/llm/__init__.py +0 -0
  23. pcli/llm/client.py +285 -0
  24. pcli/llm/errors.py +37 -0
  25. pcli/llm/models.py +100 -0
  26. pcli/llm/streaming.py +108 -0
  27. pcli/memory/__init__.py +0 -0
  28. pcli/memory/extraction.py +106 -0
  29. pcli/memory/models.py +103 -0
  30. pcli/memory/store.py +88 -0
  31. pcli/permissions/__init__.py +0 -0
  32. pcli/permissions/guardrails.py +219 -0
  33. pcli/permissions/manager.py +215 -0
  34. pcli/permissions/policy.py +70 -0
  35. pcli/sandbox/__init__.py +0 -0
  36. pcli/sandbox/base.py +50 -0
  37. pcli/sandbox/docker_backend.py +107 -0
  38. pcli/sandbox/limits.py +63 -0
  39. pcli/sandbox/null_backend.py +92 -0
  40. pcli/sandbox/selector.py +75 -0
  41. pcli/sandbox/subprocess_backend.py +376 -0
  42. pcli/scheduler/__init__.py +0 -0
  43. pcli/scheduler/daemon.py +194 -0
  44. pcli/scheduler/models.py +97 -0
  45. pcli/scheduler/runner.py +84 -0
  46. pcli/scheduler/store.py +75 -0
  47. pcli/scheduler/triggers.py +84 -0
  48. pcli/session/__init__.py +0 -0
  49. pcli/session/audit.py +122 -0
  50. pcli/session/directory_check.py +28 -0
  51. pcli/session/export.py +57 -0
  52. pcli/session/importer.py +92 -0
  53. pcli/session/models.py +168 -0
  54. pcli/session/store.py +127 -0
  55. pcli/telegram/__init__.py +0 -0
  56. pcli/telegram/bot.py +266 -0
  57. pcli/telegram/daemon.py +1197 -0
  58. pcli/telegram/permissions.py +131 -0
  59. pcli/telegram/sender.py +58 -0
  60. pcli/tools/__init__.py +0 -0
  61. pcli/tools/_nested_agent.py +204 -0
  62. pcli/tools/agent_tools.py +264 -0
  63. pcli/tools/agent_tools_store.py +69 -0
  64. pcli/tools/artifacts.py +47 -0
  65. pcli/tools/base.py +185 -0
  66. pcli/tools/builtin/__init__.py +0 -0
  67. pcli/tools/builtin/agent_tool_register_tool.py +100 -0
  68. pcli/tools/builtin/artifact_tool.py +212 -0
  69. pcli/tools/builtin/ask_tool.py +77 -0
  70. pcli/tools/builtin/browser_tool.py +253 -0
  71. pcli/tools/builtin/decision_tool.py +73 -0
  72. pcli/tools/builtin/describe_tool.py +389 -0
  73. pcli/tools/builtin/diff_tools.py +225 -0
  74. pcli/tools/builtin/fs_tools.py +371 -0
  75. pcli/tools/builtin/grep_tool.py +88 -0
  76. pcli/tools/builtin/memory_tool.py +108 -0
  77. pcli/tools/builtin/network_tools.py +107 -0
  78. pcli/tools/builtin/pip_tool.py +106 -0
  79. pcli/tools/builtin/shell_tool.py +240 -0
  80. pcli/tools/builtin/subagent_tool.py +146 -0
  81. pcli/tools/builtin/todo_tool.py +122 -0
  82. pcli/tools/builtin/toolbox_register_tool.py +76 -0
  83. pcli/tools/builtin/web_tools.py +322 -0
  84. pcli/tools/pydiscovery/__init__.py +0 -0
  85. pcli/tools/pydiscovery/cache.py +51 -0
  86. pcli/tools/pydiscovery/index.py +48 -0
  87. pcli/tools/pydiscovery/invoke.py +181 -0
  88. pcli/tools/pydiscovery/search.py +117 -0
  89. pcli/tools/registry.py +138 -0
  90. pcli/tools/toolbox/__init__.py +0 -0
  91. pcli/tools/toolbox/introspect.py +48 -0
  92. pcli/tools/toolbox/manager.py +336 -0
  93. pcli/tools/toolbox/plugin_base.py +51 -0
  94. pcli/tools/toolbox/plugins/__init__.py +6 -0
  95. pcli/tools/toolbox/plugins/httpd.py +99 -0
  96. pcli/tools/toolbox/plugins/kafka.py +162 -0
  97. pcli/tools/toolbox/plugins/kubectl.py +211 -0
  98. pcli/tools/toolbox/plugins/sge.py +146 -0
  99. pcli/tools/toolbox/store.py +65 -0
  100. pcli/tools/toolbox/synthesize.py +100 -0
  101. pcli/tui/__init__.py +0 -0
  102. pcli/tui/app.py +37 -0
  103. pcli/tui/screens/__init__.py +0 -0
  104. pcli/tui/screens/ask_question_modal.py +54 -0
  105. pcli/tui/screens/chat.py +2070 -0
  106. pcli/tui/screens/confirm_modal.py +39 -0
  107. pcli/tui/screens/models.py +43 -0
  108. pcli/tui/screens/permission_modal.py +71 -0
  109. pcli/tui/screens/sessions.py +162 -0
  110. pcli/tui/screens/subagent_activity_modal.py +71 -0
  111. pcli/tui/shell_passthrough.py +56 -0
  112. pcli/tui/styles/pcli.tcss +241 -0
  113. pcli/tui/themes.py +84 -0
  114. pcli/tui/widgets/__init__.py +0 -0
  115. pcli/tui/widgets/chat_input.py +240 -0
  116. pcli/tui/widgets/command_suggestions.py +33 -0
  117. pcli/tui/widgets/message_view.py +328 -0
  118. pcli/tui/widgets/paste_input.py +99 -0
  119. pcli/tui/widgets/paste_marker.py +69 -0
  120. pcli/tui/widgets/status_bar.py +133 -0
  121. pcli/tui/widgets/status_pane.py +58 -0
  122. pcli/util/__init__.py +0 -0
  123. pcli/util/ids.py +15 -0
  124. pcli/util/logging.py +18 -0
  125. pcli/util/text.py +10 -0
  126. pcli_agent-0.1.0.dist-info/METADATA +259 -0
  127. pcli_agent-0.1.0.dist-info/RECORD +130 -0
  128. pcli_agent-0.1.0.dist-info/WHEEL +4 -0
  129. pcli_agent-0.1.0.dist-info/entry_points.txt +2 -0
  130. pcli_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,135 @@
1
+ """BrowserSession: a lazily-launched Playwright browser, wrapped for the
2
+ browser_* tools (tools/builtin/browser_tool.py).
3
+
4
+ Playwright (`pip install -e ".[browser]"`, then a one-time `playwright
5
+ install chromium`) is an optional dependency - this module must import
6
+ cleanly even when it isn't installed, so the actual `import playwright...`
7
+ only happens inside _ensure_page (first real use), not at module or
8
+ BrowserSession-construction time. A BrowserSession is cheap to construct
9
+ and hold onto even when nothing ever calls into it (see agent/runtime.py,
10
+ which always attaches one to AgentRuntime) - the browser process itself is
11
+ only started the first time a browser_* tool is actually called.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from pathlib import Path
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ from pcli.config.paths import browser_profiles_dir
20
+
21
+ if TYPE_CHECKING:
22
+ # Optional dependency (the "browser" extra) - not installed in a plain
23
+ # dev environment, so mypy can't resolve this even though it's
24
+ # type-checking-only and never actually imported at runtime unless a
25
+ # browser_* tool is actually called (see _ensure_page below).
26
+ from playwright.async_api import Page # type: ignore[import-not-found]
27
+
28
+ _INSTALL_HINT = (
29
+ 'Browser tools need the optional "browser" extra: run `pip install -e ".[browser]"`, then '
30
+ "`playwright install chromium` once to download the browser itself."
31
+ )
32
+
33
+
34
+ class BrowserUnavailableError(Exception):
35
+ """Raised when a browser_* tool is used but Playwright isn't installed,
36
+ or Chromium hasn't been downloaded yet (`playwright install chromium`)."""
37
+
38
+
39
+ class BrowserSession:
40
+ """Wraps one Playwright persistent BrowserContext and its single active
41
+ Page. One instance is shared for the life of an AgentRuntime (agent/
42
+ runtime.py) and threaded through every ToolContext built from it, so
43
+ every browser_* call in a session reuses the same browser
44
+ process/tab - navigating then clicking then reading stays in the same
45
+ login/cookie state, the way a person's own browser session would. The
46
+ profile directory (browser_profiles_dir) persists that state further,
47
+ across separate pcli invocations too (e.g. separate scheduled `pcli
48
+ run` runs), not just within one running session."""
49
+
50
+ def __init__(self, *, profile: str = "default", headless: bool = False) -> None:
51
+ self._profile = profile
52
+ self._headless = headless
53
+ self._playwright: Any | None = None
54
+ self._context: Any | None = None
55
+ self._page: Page | None = None
56
+
57
+ @property
58
+ def is_started(self) -> bool:
59
+ return self._page is not None
60
+
61
+ @property
62
+ def headless(self) -> bool:
63
+ return self._headless
64
+
65
+ @property
66
+ def current_url(self) -> str | None:
67
+ return self._page.url if self._page is not None else None
68
+
69
+ async def _ensure_page(self) -> Page:
70
+ if self._page is not None:
71
+ return self._page
72
+ try:
73
+ from playwright.async_api import async_playwright
74
+ except ImportError as exc:
75
+ raise BrowserUnavailableError(_INSTALL_HINT) from exc
76
+
77
+ self._playwright = await async_playwright().start()
78
+ try:
79
+ self._context = await self._playwright.chromium.launch_persistent_context(
80
+ str(browser_profiles_dir(self._profile)),
81
+ headless=self._headless,
82
+ )
83
+ except Exception as exc:
84
+ await self._playwright.stop()
85
+ self._playwright = None
86
+ if "Executable doesn't exist" in str(exc):
87
+ raise BrowserUnavailableError(
88
+ "Chromium isn't downloaded yet - run `playwright install chromium` once."
89
+ ) from exc
90
+ raise
91
+ self._page = self._context.pages[0] if self._context.pages else await self._context.new_page()
92
+ return self._page
93
+
94
+ async def close(self) -> None:
95
+ """Safe to call even if the browser was never started (nothing to
96
+ close) - mirrors kill_all_background_jobs' own no-op-when-unused
97
+ shape (sandbox/subprocess_backend.py), called the same way from
98
+ ChatScreen.on_unmount / `pcli run`'s finally block."""
99
+ if self._context is not None:
100
+ await self._context.close()
101
+ self._context = None
102
+ self._page = None
103
+ if self._playwright is not None:
104
+ await self._playwright.stop()
105
+ self._playwright = None
106
+
107
+ async def navigate(self, url: str) -> str:
108
+ page = await self._ensure_page()
109
+ await page.goto(url, wait_until="load")
110
+ return page.url
111
+
112
+ async def click(self, selector: str) -> None:
113
+ page = await self._ensure_page()
114
+ await page.locator(selector).first.click()
115
+
116
+ async def type_text(self, selector: str, text: str) -> None:
117
+ page = await self._ensure_page()
118
+ await page.locator(selector).first.fill(text)
119
+
120
+ async def press_key(self, key: str) -> None:
121
+ page = await self._ensure_page()
122
+ await page.keyboard.press(key)
123
+
124
+ async def wait_for(self, selector: str, timeout_s: float) -> None:
125
+ page = await self._ensure_page()
126
+ await page.locator(selector).first.wait_for(timeout=timeout_s * 1000)
127
+
128
+ async def read_page(self) -> str:
129
+ page = await self._ensure_page()
130
+ text: str = await page.inner_text("body")
131
+ return text
132
+
133
+ async def screenshot(self, path: Path) -> None:
134
+ page = await self._ensure_page()
135
+ await page.screenshot(path=str(path))