seedcode-cli 6.1.5__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.
- seedcode/__init__.py +14 -0
- seedcode/__main__.py +12 -0
- seedcode/app.py +508 -0
- seedcode/apps/__init__.py +32 -0
- seedcode/apps/discovery.py +241 -0
- seedcode/apps/installer.py +164 -0
- seedcode/apps/launcher.py +156 -0
- seedcode/apps/verifier.py +119 -0
- seedcode/assets/logo.txt +15 -0
- seedcode/cli.py +95 -0
- seedcode/commands/__init__.py +81 -0
- seedcode/commands/about.py +34 -0
- seedcode/commands/agent.py +94 -0
- seedcode/commands/assist.py +201 -0
- seedcode/commands/clear.py +20 -0
- seedcode/commands/desktop.py +104 -0
- seedcode/commands/doctor.py +152 -0
- seedcode/commands/help.py +61 -0
- seedcode/commands/history.py +365 -0
- seedcode/commands/palette.py +100 -0
- seedcode/commands/provider.py +451 -0
- seedcode/commands/theme.py +76 -0
- seedcode/computer/__init__.py +98 -0
- seedcode/computer/browser.py +276 -0
- seedcode/computer/browser_cdp.py +567 -0
- seedcode/computer/browser_engine.py +546 -0
- seedcode/computer/browser_extract.py +301 -0
- seedcode/computer/browser_popups.py +329 -0
- seedcode/computer/browser_selenium.py +209 -0
- seedcode/computer/browser_skills.py +245 -0
- seedcode/computer/catalog.py +200 -0
- seedcode/computer/controller.py +324 -0
- seedcode/computer/dispatcher.py +272 -0
- seedcode/computer/dpi.py +185 -0
- seedcode/computer/engine.py +105 -0
- seedcode/computer/keyboard.py +101 -0
- seedcode/computer/logbook.py +104 -0
- seedcode/computer/mouse.py +48 -0
- seedcode/computer/ocr.py +213 -0
- seedcode/computer/operator_skills.py +577 -0
- seedcode/computer/permissions.py +203 -0
- seedcode/computer/recovery.py +115 -0
- seedcode/computer/registry.py +107 -0
- seedcode/computer/resolver.py +434 -0
- seedcode/computer/screen.py +130 -0
- seedcode/computer/screen_state.py +412 -0
- seedcode/computer/selfguard.py +197 -0
- seedcode/computer/semantic.py +100 -0
- seedcode/computer/skills.py +139 -0
- seedcode/computer/state.py +199 -0
- seedcode/computer/verifier.py +177 -0
- seedcode/computer/vision.py +327 -0
- seedcode/computer/windows.py +217 -0
- seedcode/config/__init__.py +8 -0
- seedcode/config/defaults.py +22 -0
- seedcode/config/manager.py +62 -0
- seedcode/core/__init__.py +31 -0
- seedcode/core/agent.py +534 -0
- seedcode/core/chat.py +128 -0
- seedcode/core/client.py +9 -0
- seedcode/core/errors.py +199 -0
- seedcode/core/identity.py +66 -0
- seedcode/core/identity_store.py +119 -0
- seedcode/core/lifecycle.py +240 -0
- seedcode/core/limits.py +35 -0
- seedcode/core/models.py +347 -0
- seedcode/core/project.py +96 -0
- seedcode/core/providers/__init__.py +58 -0
- seedcode/core/providers/aerolink.py +324 -0
- seedcode/core/providers/base.py +230 -0
- seedcode/core/providers/freemodel.py +931 -0
- seedcode/core/providers/ollama.py +262 -0
- seedcode/core/providers/openrouter.py +393 -0
- seedcode/core/streaming.py +21 -0
- seedcode/memory/__init__.py +8 -0
- seedcode/memory/manager.py +47 -0
- seedcode/memory/storage.py +38 -0
- seedcode/memory/store.py +257 -0
- seedcode/tools/__init__.py +35 -0
- seedcode/tools/base.py +179 -0
- seedcode/tools/desktop.py +371 -0
- seedcode/tools/filesystem.py +309 -0
- seedcode/tools/git.py +72 -0
- seedcode/tools/patch.py +170 -0
- seedcode/tools/permissions.py +288 -0
- seedcode/tools/search.py +137 -0
- seedcode/tools/terminal.py +200 -0
- seedcode/tools/textio.py +59 -0
- seedcode/ui/__init__.py +164 -0
- seedcode/ui/badges.py +64 -0
- seedcode/ui/banner.py +78 -0
- seedcode/ui/dashboard.py +197 -0
- seedcode/ui/dialog.py +62 -0
- seedcode/ui/fuzzy.py +128 -0
- seedcode/ui/layout.py +54 -0
- seedcode/ui/menu.py +61 -0
- seedcode/ui/palette.py +40 -0
- seedcode/ui/progress.py +41 -0
- seedcode/ui/prompts.py +16 -0
- seedcode/ui/renderer.py +36 -0
- seedcode/ui/searchbox.py +70 -0
- seedcode/ui/selector.py +514 -0
- seedcode/ui/statusbar.py +38 -0
- seedcode/ui/textbox.py +61 -0
- seedcode/ui/theme.py +204 -0
- seedcode/ui/tree.py +91 -0
- seedcode/utils/__init__.py +22 -0
- seedcode/utils/helpers.py +97 -0
- seedcode/utils/logger.py +65 -0
- seedcode_cli-6.1.5.dist-info/METADATA +368 -0
- seedcode_cli-6.1.5.dist-info/RECORD +114 -0
- seedcode_cli-6.1.5.dist-info/WHEEL +4 -0
- seedcode_cli-6.1.5.dist-info/entry_points.txt +2 -0
- seedcode_cli-6.1.5.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Default-browser URL handler — the low-level navigation driver.
|
|
2
|
+
|
|
3
|
+
Seed Code drives the **user's own default browser** — the one already signed
|
|
4
|
+
in, themed, and trusted — rather than spinning up a throwaway Selenium
|
|
5
|
+
instance. This module is the thin part of that: handing a URL to the OS
|
|
6
|
+
(``webbrowser`` / ``os.startfile``), which honours whatever Chromium- or
|
|
7
|
+
Gecko-based browser the user set as default (Chrome, Edge, Firefox, Brave,
|
|
8
|
+
Opera, Vivaldi, …). No WebDriver, no driver downloads.
|
|
9
|
+
|
|
10
|
+
**Workflows do not live here.** Anything a user would recognise as a task —
|
|
11
|
+
"play this song", "search YouTube", "switch tabs" — belongs to
|
|
12
|
+
:mod:`.browser_engine`, which owns the full procedure including popup
|
|
13
|
+
dismissal, result selection, and verification, and is exposed to the AI as
|
|
14
|
+
skills in :mod:`.browser_skills`. This module is one of the mechanisms that
|
|
15
|
+
engine uses.
|
|
16
|
+
|
|
17
|
+
Selenium remains available as an *optional* fallback (see
|
|
18
|
+
:mod:`.browser_selenium`) for the rare task needing a scripted throwaway
|
|
19
|
+
profile; it is never the primary mechanism and is not on the AI's surface.
|
|
20
|
+
|
|
21
|
+
State (the last URL we opened, the browser family) is tracked so the verifier
|
|
22
|
+
and StateManager can confirm outcomes. This module is offline and holds no AI
|
|
23
|
+
code.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
import webbrowser
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
from urllib.parse import quote_plus
|
|
33
|
+
|
|
34
|
+
# The last URL we asked the OS to open — the best-effort "current URL" the
|
|
35
|
+
# verifier checks, since reading the live address bar requires UIA and may miss.
|
|
36
|
+
_last_url: str | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BrowserError(Exception):
|
|
40
|
+
"""A default-browser action failed; the message is fed back to the model."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(slots=True)
|
|
44
|
+
class BrowserIdentity:
|
|
45
|
+
"""The resolved default browser."""
|
|
46
|
+
|
|
47
|
+
name: str # human name, e.g. "Google Chrome"
|
|
48
|
+
family: str # normalized family: chrome|edge|firefox|brave|opera|other
|
|
49
|
+
prog_id: str # the raw Windows ProgId (empty off-Windows)
|
|
50
|
+
|
|
51
|
+
def describe(self) -> str:
|
|
52
|
+
return self.name
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Windows ProgId → (display name, family). Matched by prefix so versioned
|
|
56
|
+
# Firefox ProgIds (``FirefoxURL-<hash>``) and channel variants still resolve.
|
|
57
|
+
_PROGID_MAP: tuple[tuple[str, str, str], ...] = (
|
|
58
|
+
("ChromeHTML", "Google Chrome", "chrome"),
|
|
59
|
+
("MSEdgeHTM", "Microsoft Edge", "edge"),
|
|
60
|
+
("MSEdgeMHT", "Microsoft Edge", "edge"),
|
|
61
|
+
("FirefoxURL", "Mozilla Firefox", "firefox"),
|
|
62
|
+
("BraveHTML", "Brave", "brave"),
|
|
63
|
+
("BraveFile", "Brave", "brave"),
|
|
64
|
+
("OperaStable", "Opera", "opera"),
|
|
65
|
+
("Opera", "Opera", "opera"),
|
|
66
|
+
("VivaldiHTM", "Vivaldi", "other"),
|
|
67
|
+
("IE.HTTP", "Internet Explorer", "other"),
|
|
68
|
+
("AppXq", "Microsoft Edge", "edge"), # Edge legacy AppX ProgIds
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
SEARCH_ENGINES = {
|
|
72
|
+
"google": "https://www.google.com/search?q={q}",
|
|
73
|
+
"bing": "https://www.bing.com/search?q={q}",
|
|
74
|
+
"duckduckgo": "https://duckduckgo.com/?q={q}",
|
|
75
|
+
"youtube": "https://www.youtube.com/results?search_query={q}",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_available() -> tuple[bool, str]:
|
|
80
|
+
"""Whether default-browser control can run here.
|
|
81
|
+
|
|
82
|
+
A default browser effectively always exists, so this is about the platform
|
|
83
|
+
having a usable URL handler. Always true in practice; kept as a tuple to
|
|
84
|
+
mirror the other drivers' availability contract.
|
|
85
|
+
"""
|
|
86
|
+
return True, ""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def default_browser() -> BrowserIdentity:
|
|
90
|
+
"""Resolve the user's current default browser."""
|
|
91
|
+
if sys.platform == "win32":
|
|
92
|
+
prog_id = _win_default_progid()
|
|
93
|
+
if prog_id:
|
|
94
|
+
for prefix, name, family in _PROGID_MAP:
|
|
95
|
+
if prog_id.lower().startswith(prefix.lower()):
|
|
96
|
+
return BrowserIdentity(name=name, family=family, prog_id=prog_id)
|
|
97
|
+
return BrowserIdentity(name=prog_id, family="other", prog_id=prog_id)
|
|
98
|
+
# Non-Windows or unknown: report what the stdlib will actually use.
|
|
99
|
+
try:
|
|
100
|
+
controller = webbrowser.get()
|
|
101
|
+
name = getattr(controller, "name", "") or type(controller).__name__
|
|
102
|
+
except Exception:
|
|
103
|
+
name = "system default"
|
|
104
|
+
return BrowserIdentity(name=name or "system default", family="other", prog_id="")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _win_default_progid() -> str:
|
|
108
|
+
"""Read the HTTPS URL-association ProgId from the registry (empty on error)."""
|
|
109
|
+
try:
|
|
110
|
+
import winreg
|
|
111
|
+
|
|
112
|
+
key_path = (
|
|
113
|
+
r"Software\Microsoft\Windows\Shell\Associations"
|
|
114
|
+
r"\UrlAssociations\https\UserChoice"
|
|
115
|
+
)
|
|
116
|
+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) as key:
|
|
117
|
+
prog_id, _type = winreg.QueryValueEx(key, "ProgId")
|
|
118
|
+
return str(prog_id or "")
|
|
119
|
+
except (OSError, ValueError, ImportError):
|
|
120
|
+
return ""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _normalize_url(url: str) -> str:
|
|
124
|
+
url = (url or "").strip()
|
|
125
|
+
if not url:
|
|
126
|
+
return "about:blank"
|
|
127
|
+
if not url.startswith(("http://", "https://", "file://", "about:", "ftp://")):
|
|
128
|
+
url = "https://" + url
|
|
129
|
+
return url
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def navigate(url: str, *, new_window: bool = False) -> str:
|
|
133
|
+
"""Open ``url`` in the user's default browser (a new tab by default)."""
|
|
134
|
+
global _last_url
|
|
135
|
+
target = _normalize_url(url)
|
|
136
|
+
browser = default_browser()
|
|
137
|
+
opened = _open(target, new_window=new_window)
|
|
138
|
+
if not opened:
|
|
139
|
+
raise BrowserError(
|
|
140
|
+
f"Could not hand '{target}' to the default browser ({browser.name})."
|
|
141
|
+
)
|
|
142
|
+
_last_url = target
|
|
143
|
+
return f"Opened {target} in {browser.name} (default browser)."
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _open(target: str, *, new_window: bool) -> bool:
|
|
147
|
+
"""Hand a URL to the OS default handler. Returns whether it was accepted."""
|
|
148
|
+
# webbrowser.open honours the OS default and returns success; new=2 asks
|
|
149
|
+
# for a new tab, new=1 a new window.
|
|
150
|
+
try:
|
|
151
|
+
if webbrowser.open(target, new=(1 if new_window else 2)):
|
|
152
|
+
return True
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
# Fallback: ShellExecute the URL directly (Windows) — same default handler.
|
|
156
|
+
if sys.platform == "win32":
|
|
157
|
+
try:
|
|
158
|
+
os.startfile(target) # noqa: S606 — deliberate shell-open of a URL
|
|
159
|
+
return True
|
|
160
|
+
except OSError:
|
|
161
|
+
return False
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def open_new_tab(url: str) -> str:
|
|
166
|
+
"""Open a URL in a new tab of the default browser."""
|
|
167
|
+
return navigate(url, new_window=False)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def search(query: str, engine: str = "google") -> str:
|
|
171
|
+
"""Run a web search in the default browser using ``engine``."""
|
|
172
|
+
query = (query or "").strip()
|
|
173
|
+
if not query:
|
|
174
|
+
raise BrowserError("A search query is required.")
|
|
175
|
+
template = SEARCH_ENGINES.get(engine.lower().strip(), SEARCH_ENGINES["google"])
|
|
176
|
+
return navigate(template.format(q=quote_plus(query)))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_page_info() -> str:
|
|
180
|
+
"""Best-effort current page: the active browser window title + last URL.
|
|
181
|
+
|
|
182
|
+
Reading the live URL from the address bar needs UIA and is not always
|
|
183
|
+
exposed; the last URL we opened is a reliable floor the verifier can use.
|
|
184
|
+
"""
|
|
185
|
+
title = _active_browser_title()
|
|
186
|
+
lines = []
|
|
187
|
+
if title:
|
|
188
|
+
lines.append(f"Title: {title}")
|
|
189
|
+
if _last_url:
|
|
190
|
+
lines.append(f"URL: {_last_url}")
|
|
191
|
+
if not lines:
|
|
192
|
+
return "No browser page information available."
|
|
193
|
+
return "\n".join(lines)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _active_browser_title() -> str:
|
|
197
|
+
"""Title of a visible browser window, if one is open."""
|
|
198
|
+
try:
|
|
199
|
+
from . import windows as windows_driver
|
|
200
|
+
|
|
201
|
+
browser = default_browser()
|
|
202
|
+
# Chromium/Gecko windows end with the browser's name, e.g.
|
|
203
|
+
# "YouTube — Google Chrome". Match on family/name tokens.
|
|
204
|
+
tokens = {browser.name.lower(), browser.family.lower(),
|
|
205
|
+
"chrome", "edge", "firefox", "brave", "opera", "mozilla"}
|
|
206
|
+
for w in windows_driver.list_windows():
|
|
207
|
+
low = (w.title or "").lower()
|
|
208
|
+
if any(tok and tok in low for tok in tokens):
|
|
209
|
+
return w.title
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
return ""
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def current_url() -> str | None:
|
|
216
|
+
"""The last URL Seed Code opened in the default browser (state tracking)."""
|
|
217
|
+
return _last_url
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def get_current_browser() -> str:
|
|
221
|
+
"""The default browser's display name."""
|
|
222
|
+
return default_browser().name
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def close_browser() -> str:
|
|
226
|
+
"""Close a visible browser window (best-effort, graceful)."""
|
|
227
|
+
global _last_url
|
|
228
|
+
title = _active_browser_title()
|
|
229
|
+
if not title:
|
|
230
|
+
_last_url = None
|
|
231
|
+
return "No browser window found to close."
|
|
232
|
+
try:
|
|
233
|
+
from . import windows as windows_driver
|
|
234
|
+
|
|
235
|
+
windows_driver.close_window(title)
|
|
236
|
+
except Exception as exc:
|
|
237
|
+
raise BrowserError(f"Could not close the browser window: {exc}")
|
|
238
|
+
_last_url = None
|
|
239
|
+
return f"Closed browser window '{title}'."
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# --- optional Selenium fallback (DOM-level control) --------------------------
|
|
243
|
+
# These exist for the rare task that needs a scripted, throwaway browser
|
|
244
|
+
# profile. They are NOT the primary path and are not reachable from the AI:
|
|
245
|
+
# web automation goes through the Browser Engine's skills (youtube_play,
|
|
246
|
+
# open_url, …), which drive the user's real, signed-in browser.
|
|
247
|
+
|
|
248
|
+
def _selenium():
|
|
249
|
+
from . import browser_selenium
|
|
250
|
+
|
|
251
|
+
ok, reason = browser_selenium.is_available()
|
|
252
|
+
if not ok:
|
|
253
|
+
raise BrowserError(
|
|
254
|
+
f"DOM-level browser control needs the optional Selenium extra ({reason}). "
|
|
255
|
+
"For essentially every task, prefer the browser skills instead "
|
|
256
|
+
"(youtube_play, google_search, open_url, …) — they drive the user's "
|
|
257
|
+
"own browser, handle popups, and verify the result, with no extra "
|
|
258
|
+
"install. ui_click is not available on browser windows."
|
|
259
|
+
)
|
|
260
|
+
return browser_selenium
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def click_element(selector: str, selector_type: str = "css") -> str:
|
|
264
|
+
return _selenium().click_element(selector, selector_type)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def type_in_element(selector: str, text: str, selector_type: str = "css") -> str:
|
|
268
|
+
return _selenium().type_in_element(selector, text, selector_type)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def find_elements(selector: str, selector_type: str = "css") -> str:
|
|
272
|
+
return _selenium().find_elements(selector, selector_type)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def execute_script(script: str) -> str:
|
|
276
|
+
return _selenium().execute_script(script)
|