diff --git a/CHANGELOG.md b/CHANGELOG.md index b1a87a1a..29de4dfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,15 @@ Status of the `main` branch. Changes prior to the next official version change will appear here. * General: - - Add `serena init` command + - Added `serena init` and `serena setup` commands + - **Major**: added commands for hooks and documentation on recommended setup. Consider setting up the [recommended hooks](https://oraios.github.io/serena/02-usage/030_clients.html) ! + - Reworked installation instructions, switching to releases on pypi for distribution. Please update your mcp startup commands! - Add minimal usage data collection on startup (only Serena version, language backend, OS, dashboard enabled status; no personally identifiable information) * Language Servers: - - Added Crystal language support (uses [Crystalline](https://github.com/elbywan/crystalline) language server) + - Added Crystal language support (uses [Crystalline](https://github.com/elbywan/crystalline) language server) + +* JetBrains: - `JetBrainsFindReferencingSymbolTool`: Include context lines (when using plugin version 2023.2.15+) * Dashboard: @@ -17,7 +21,7 @@ Status of the `main` branch. Changes prior to the next official version change w * Fixes: - Fix reactivation of the same project restarting language servers #1280 - - Fix git commit id in version + - Fix git commit id in version string - Fix manual server shutdown triggered by GUI tool/dashboard not cleaning everything up. # 1.0.0 diff --git a/README.md b/README.md index b63f3e78..033eaac8 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ approaches that rely on low-level concepts like line numbers or primitive search Practically, this means that your agent operates **faster, more efficiently and more reliably**, especially in larger and more complex codebases. +> [!IMPORTANT] +> Don't install Serena via an MCP or plugin marketplace! They contain outdated and suboptimal installation commands. Instead, follow our [Quick Start](#quick-start) instructions. + ## How Serena Works Serena provides the necessary [tools](https://oraios.github.io/serena/01-about/035_tools.html) for coding workflows, diff --git a/docs/02-usage/020_running.md b/docs/02-usage/020_running.md index d6b0b81c..468b1d32 100644 --- a/docs/02-usage/020_running.md +++ b/docs/02-usage/020_running.md @@ -180,14 +180,18 @@ Explore the full set of commands and options using the CLI itself! Depending on your requirements, you may want to run Serena in different ways. When applying one of these approaches, replace `serena` in commands mentioned throughout the documentation -with the respective command and options. +with the respective command and options. The same applies to `serena-hooks` commands. ### Using uvx to Run the Latest Source Version - + `uvx` is part of `uv`. It can be used to run the latest version of Serena directly from the repository, without an explicit local installation. uvx -p 3.13 --from git+https://github.com/oraios/serena serena +This was previously the main way of running Serena. +Since this has the downside that every new commit in the repository will trigger a (potentially slow) re-synchronization, an [installation](010_installation) of Serena should usually be preferred. +If you should experience timeouts when connecting the MCP server, consider switching. +If, however, the synchronisation is fast enough for you, this is still a good option. ### Running from Cloned Source diff --git a/docs/02-usage/030_clients.md b/docs/02-usage/030_clients.md index be2a8021..1d6cea12 100644 --- a/docs/02-usage/030_clients.md +++ b/docs/02-usage/030_clients.md @@ -134,14 +134,13 @@ set ENABLE_TOOL_SEARCH=true && claude in Windows CMD to launch Claude Code. **Hooks.** -Claude Code's agent can sometimes drift away from using MCP tools during long sessions, falling back -to built-in tools or forgetting to load Serena's instructions. Serena provides hook commands that -automatically remind the agent to use Serena's tools when appropriate. +Due to recent changes (especially dynamic tool loading) in Claude Code, the agent will often fail to make proper use +of Serena's tools, either by failing to load them in the beginning or by forgetting the instructions in a long session +(a behavior known as agent drift). To counteract this, we provide reminder hooks. We **strongly recommend** setting +up the hooks as below (or a variation thereof) for optimal performance of Serena in Claude Code. :::{note} -Hooks are an **alpha feature** that we provide to overcome context drift in several agent harnesses -(where agents, especially those powered by Anthropic models, may forget to load and use MCP tools or -fail to read MCP instructions). +While recommended, hooks are an **alpha feature**. Provide feedback via the [GitHub issue tracker](https://github.com/serenadeai/serenade/issues) if you encounter any issues. ::: To set up hooks, add the following to your Claude Code settings file @@ -235,15 +234,11 @@ In both cases, proceed to enter Serena as the name, then select either global or You should be able to see Serena in the tools overview in the AI Chat window. **Hooks.** -VSCode's Copilot agent can sometimes over-rely on basic tools like `read_file` and `grep_search` instead of -using Serena's more efficient symbolic tools. You can use Serena's hook commands to automatically correct -for this — the hooks will simply remind the agent to use Serena's tools when appropriate. +Due to recent changes (especially dynamic tool loading) in VSCode, the agent will often fail to make proper use +of Serena's tools, either by failing to load them in the beginning or by forgetting the instructions in a long session +(a behaviour known as agent drift). To counteract this, we provide reminder hooks. We **strongly recommend** setting +up the hooks as below (or a variation thereof) for optimal performance of Serena in VSCode. -:::{note} -Hooks are an **alpha feature** that we provide to overcome context drift in several agent harnesses -(where agents, especially those powered by Anthropic models, may forget to load and use MCP tools or -fail to read MCP instructions). -::: The hooks will: @@ -301,6 +296,10 @@ The Codex app does not start a session in the project's directory, so when using asking Codex to "Activate the current dir as project using serena" at the start of each session (though Codex might do this automatically). +**Hooks.** The Codex hook system is less refined than of VSCode or Claude Code, but Codex also displays less drift +and is better in using Serena's tools out of the box. If you use the Codex app, you can consider setting up the +SessionStart hook (see above) to remind the agent to activate the project directory. + ## Claude Desktop On Windows and macOS, there are official [Claude Desktop applications by Anthropic](https://claude.ai/download); for Linux, there is an [open-source diff --git a/news/20260409.html b/news/20260409.html new file mode 100644 index 00000000..ed1241d4 --- /dev/null +++ b/news/20260409.html @@ -0,0 +1,22 @@ +
+

Serena v1.1: Hooks and New Release Mechanism

+

April 9, 2026

+

+ Installation. Serena has matured and we are switching to a PyPI-based release and installation mechanism. + The recommended uv-tool-based installation decreases startup times and facilitates CLI usage. + See updated installation instructions + for more details. +

+

+ Improving Tool Usage via Hooks. To address issues with agents not using Serena's tools (agent drift), we now provide the `serena-hooks` commands and a recommended + setup for Claude Code and VSCode. + Please read the updated documentation for more details. + We strongly recommend using the hooks, since recent updates in Claude Code and VSCode resulted in + reduced performance, which our hook commands compensate for. +

+ +

+ Community. We opened a Discord server for our community. +

+ +
\ No newline at end of file diff --git a/news/news.json b/news/news.json deleted file mode 100644 index 11eb1d97..00000000 --- a/news/news.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "20260111": "
\n

Extended Symbol Information, Type Hierarchy and Compact Overviews

\n

January 11, 2026

\n

\n Recent commits and a new plugin release provide major new features!\n

\n \n

\n For more detailed information, see our changelog.\n

\n
", - "20260303": "
\n

Nested and Global Memories

\n

March 03, 2026

\n

\n Serena's memory system has been significantly extended in functionality.\n It keeps the simplicity that made it popular but now supports structuring memories\n into topics and sharing them across projects.\n

\n

\n For more detailed information, see the documentation.\n

\n
", - "20260321": "
\n

Progressive Shortening and General Improvements in Tool Responses

\n

March 21, 2026

\n

\n Tools that may deliver too long results now output informative shorter\n messages instead of just stating \"result is too long\". The mechanism uses\n progressive shortening so that the contained information is optimized\n for the length limit.\n

\n

\n In addition, several tool responses have been optimized without losing\n content, making Serena even more token efficient in general.\n

\n
", - "20260330": "
\n

Querying of External Projects

\n

March 30, 2026

\n

\n We added a new tool for querying external projects that are\n known to Serena: From the current project, use Serena's tools on another project to retrieve information.\n This is useful in situations where the current project builds on or is otherwise related\n to other projects.\n

\n

\n Find out more about this feature in our documentation.\n

\n
", - "20260404": "
\n

Serena v1.0 Is Here!

\n

April 3, 2026

\n

\n After a full year of work since the first version of Serena, we are excited to announce the release of Serena v1.0!\n

\n

\n Since our last update, we have now added support for exciting new retrieval and refactoring tools, especially for users of the JetBrains backend.\n

\n \n

\n We are far from done. Many new features will be coming your way – for both the LSP and JetBrains backends.\n

\n

\n Thank you to our contributors, users and customers!\n

\n
" -} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index dc5b8f29..435d88f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "tiktoken==0.12.0", "anthropic==0.59.0", "beautifulsoup4==4.14.2", - "pywebview", + "oraios-pywebview==6.2", "pystray==0.19.5", # Transitive deps pinned for security (dependabot alerts). # Exact pins because uvx installs from git, ignoring the lock file. @@ -50,8 +50,6 @@ dependencies = [ "pythonnet==3.1.0-rc0 ; sys_platform == 'win32'", ] -[tool.uv.sources] -pywebview = { git = "https://github.com/r0x0r/pywebview", rev = "345596c1f4fd59fd26c7aa02d4176c5362bcbabe" } [[tool.uv.index]] name = "testpypi" diff --git a/scripts/build_news_json.py b/scripts/build_news_json.py index 10a86b67..8359604a 100644 --- a/scripts/build_news_json.py +++ b/scripts/build_news_json.py @@ -9,6 +9,7 @@ This reads all .html files from the `news/` directory and creates """ import json +import os import sys from pathlib import Path @@ -41,4 +42,14 @@ def build_news_json() -> None: if __name__ == "__main__": + deploy = False + for arg in sys.argv[1:]: + if arg == "--deploy": + deploy = True + build_news_json() + + if deploy: + user = os.getenv("HADES_USER") + assert user, "HADES_USER environment variable must be set to deploy news.json to Hades" + os.system(f"scp news/news.json {user}@hades:/var/www/html/oraios-software/serena_news.json") diff --git a/src/serena/agent.py b/src/serena/agent.py index 57ed1926..b3feec7d 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -894,6 +894,13 @@ class SerenaAgent: """ return self._active_tools.contains_tool_name(tool_name) + def tool_is_exposed(self, tool_name: str) -> bool: + """ + :param tool_name: the name of the tool to check + :return: True if the tool is in the exposed tool set, False otherwise + """ + return self._exposed_tools.contains_tool_name(tool_name) + def get_current_config_overview(self) -> str: """ :return: a string overview of the current configuration, including the active and available configuration options diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index 528af993..3c33f1a2 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -630,7 +630,7 @@ class SerenaDashboardAPI: # ===== Remote News Methods ===== # The branch from which news are fetched. Change to a feature branch for testing. - _NEWS_JSON_URL = "https://raw.githubusercontent.com/oraios/serena/main/news/news.json" + _NEWS_JSON_URL = "https://oraios-software.de/serena_news.json" def _fetch_news(self) -> None: """Fetch news.json from GitHub using ETag-based caching and store in memory. Silently ignores network errors.""" diff --git a/src/serena/hooks.py b/src/serena/hooks.py index 4a04e9f4..4a0ff12f 100644 --- a/src/serena/hooks.py +++ b/src/serena/hooks.py @@ -103,11 +103,13 @@ class PreToolUseRemindAboutSerenaHook(PreToolUseHook): increment nor reset counters, so they also do not mask bursts by pushing the last timestamp forward. - Deny emissions are additionally rate-limited to at most one per - :attr:`ToolUseCounter._MIN_DENY_INTERVAL_SECONDS` (one minute): once a deny has - been emitted, subsequent bursts detected within that window still reset the - counters but do not produce further nudges, so the agent is not swamped with - reminders during a sustained non-symbolic-tool burst. + The hook is additionally gated by :attr:`ToolUseCounter._MIN_DENY_INTERVAL_SECONDS` + (two minutes by default): once a deny has been emitted, *every* subsequent + invocation of this hook is a no-op until the window has elapsed — neither the + counters are updated nor any further deny is produced. This prevents the agent + from being nudged more than once per window during a sustained non-symbolic-tool + burst, and also avoids surprising the user with reminders that were already + counted up under stale state. """ @dataclass @@ -124,8 +126,9 @@ class PreToolUseRemindAboutSerenaHook(PreToolUseHook): # reset period for the combined counter _NON_SYMBOLIC_RESET_PERIOD_SECONDS = 2000 - # minimum seconds between two emitted denies; rate-limits reminder nudges so - # a single sustained burst triggers at most one deny per minute + # minimum seconds between two engagements of the hook after a deny; the entire + # hook (counter updates included) is a no-op while this window is active, so a + # single sustained burst triggers at most one nudge per window _MIN_DENY_INTERVAL_SECONDS = 120 n_recent_read_file_uses: int = 0 @@ -147,13 +150,13 @@ class PreToolUseRemindAboutSerenaHook(PreToolUseHook): def too_many_recent_non_symbolic(self) -> bool: return self.n_recent_non_symbolic_uses >= self._NON_SYMBOLIC_USES_THRESHOLD - def can_emit_deny(self, now: datetime) -> bool: - """:return: whether enough time has elapsed since the last emitted deny - to allow another one. A value of ``True`` is also returned when no - deny has been emitted yet in this session. The rate limit prevents - the agent from being nudged more than once per - :attr:`_MIN_DENY_INTERVAL_SECONDS`, even if further bursts are - detected in the meantime. + def is_hook_active(self, now: datetime) -> bool: + """:return: whether the hook should engage at all at ``now``. Returns + ``False`` while we are still within :attr:`_MIN_DENY_INTERVAL_SECONDS` + of the most recent emitted deny — in that case the entire hook is + short-circuited (no counter updates, no deny). Returns ``True`` when + no deny has been emitted yet in this session, or when the window has + elapsed. """ if self.last_deny_timestamp is None: return True @@ -258,6 +261,13 @@ class PreToolUseRemindAboutSerenaHook(PreToolUseHook): return any(verb in name for verb in self._READ_FILE_VERB_SUBSTRINGS) def execute(self) -> None: + # gate the entire hook on the rate-limit window: while we are within + # _MIN_DENY_INTERVAL_SECONDS of the last emitted deny, no counter + # updates and no deny detection happen at all. The pickle is left + # untouched so state survives the gating window unchanged. + if not self._tool_call_counter.is_hook_active(self.triggered_at_timestamp): + return + self._tool_call_counter.update(self) # pick the deny that matches the current call first, so the emitted message lines up @@ -282,13 +292,12 @@ class PreToolUseRemindAboutSerenaHook(PreToolUseHook): output_data = self._build_non_symbolic_deny() if output_data is not None: - # reset first so burst detection starts fresh regardless of whether the - # deny is actually emitted; the rate limit only gates *emission*, not - # detection + # reset burst counters so the next interval starts fresh, then record + # the deny timestamp and emit. The is_hook_active() guard at the top + # ensures we never reach this branch within the rate-limit window. self._tool_call_counter.reset() - if self._tool_call_counter.can_emit_deny(self.triggered_at_timestamp): - self._tool_call_counter.last_deny_timestamp = self.triggered_at_timestamp - click.echo(output_data.to_json_string()) + self._tool_call_counter.last_deny_timestamp = self.triggered_at_timestamp + click.echo(output_data.to_json_string()) self._tool_call_counter.save(self) def _build_grep_deny(self) -> "PreToolUseHook.OutputData": diff --git a/src/serena/resources/config/contexts/claude-code.yml b/src/serena/resources/config/contexts/claude-code.yml index 6678c913..b6d632a7 100644 --- a/src/serena/resources/config/contexts/claude-code.yml +++ b/src/serena/resources/config/contexts/claude-code.yml @@ -4,11 +4,12 @@ prompt: | as well as shell commands are handled by your own, internal tools. If Serena's tools can be used to achieve your task, you should prioritize them. - **IMPORTANT**: If you haven't seen Serena's tools yet, you should use the tool search tool to explore them. - Avoid reading entire source code files unless it is strictly necessary! + **IMPORTANT**: If you haven't seen Serena's tools yet, you should use the tool search tool to load all of them + right now. For exploring and reading code in a token-efficient manner, use Serena's code-centric overview and symbolic search tools. For edits, where it makes sense, use Serena's editing tools since they are more token efficient. Use your own editing tools only if Serena's editing tools would be suboptimal. + **IMPORTANT**: Never use a tool before reading its schema via the tool-search tool! excluded_tools: @@ -18,6 +19,7 @@ excluded_tools: - replace_content - find_file - list_dir + - search_for_pattern tool_description_overrides: {} diff --git a/src/serena/tools/workflow_tools.py b/src/serena/tools/workflow_tools.py index e8bc67b4..7b1f5d96 100644 --- a/src/serena/tools/workflow_tools.py +++ b/src/serena/tools/workflow_tools.py @@ -4,7 +4,7 @@ Tools supporting the general workflow of the agent import platform -from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject +from serena.tools import ReadMemoryTool, Tool, ToolMarkerDoesNotRequireActiveProject, WriteMemoryTool class CheckOnboardingPerformedTool(Tool): @@ -17,12 +17,16 @@ class CheckOnboardingPerformedTool(Tool): Checks whether project onboarding was already performed. You should always call this tool before beginning to actually work on the project/after activating a project. """ + read_memory_tool_available = self.agent.tool_is_exposed(ReadMemoryTool.get_name_from_cls()) + perform_onboarding_tool_available = self.agent.tool_is_exposed(OnboardingTool.get_name_from_cls()) + + if not read_memory_tool_available: + return "Memory reading tool not activated, skipping onboarding check." project_memories = self.memories_manager.list_project_memories() if len(project_memories) == 0: - msg = ( - "Onboarding not performed yet (no memories available). " - "You should perform onboarding by calling the `onboarding` tool before proceeding with the task. " - ) + msg = "Onboarding not performed yet (no memories available). " + if perform_onboarding_tool_available: + msg += "You should perform onboarding by calling the `onboarding` tool before proceeding with the task. " else: # Not reporting the list of memories here, as they were already reported at project activation # (with the system prompt if the project was activated at startup) @@ -46,6 +50,9 @@ class OnboardingTool(Tool): :return: instructions on how to create the onboarding information """ + write_memory_tool_available = self.agent.tool_is_exposed(WriteMemoryTool.get_name_from_cls()) + if not write_memory_tool_available: + return "Memory writing tool not activated, skipping onboarding." system = platform.system() return self.prompt_factory.create_onboarding_prompt(system=system) diff --git a/test/serena/test_hooks.py b/test/serena/test_hooks.py index a98714fd..25408f0d 100644 --- a/test/serena/test_hooks.py +++ b/test/serena/test_hooks.py @@ -180,8 +180,10 @@ class TestPreToolUseRemindAboutSerenaHook: assert capsys.readouterr().out == "" - def test_rate_limit_suppresses_second_deny_within_interval(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]): - """A second burst detected within the minimum deny interval must not emit a deny.""" + def test_rate_limit_gates_entire_hook_within_interval(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]): + """While within the rate-limit window, the entire hook must be a no-op: + no deny is emitted, AND the persisted counters must remain untouched. + """ # first burst: should emit a deny for _ in range(ToolUseCounter._GREP_USES_THRESHOLD): with patch("sys.stdin", _make_stdin(_base_input("grep"))), patch("serena.hooks.serena_home_dir", str(tmp_path)): @@ -189,13 +191,22 @@ class TestPreToolUseRemindAboutSerenaHook: first_output = capsys.readouterr().out.strip() assert first_output, "first burst should have emitted a deny" - # second burst immediately after: within the 1-minute window, must be suppressed + # snapshot the persisted counter immediately after the deny was emitted + stub_for_path = object.__new__(PreToolUseRemindAboutSerenaHook) + stub_for_path.session_persistence_dir = str(tmp_path / "hook_data" / _base_input()["session_id"]) + counter_before = ToolUseCounter.load(stub_for_path) + + # second burst immediately after: within the rate-limit window, the entire + # hook must short-circuit — no deny output and no counter mutation for _ in range(ToolUseCounter._GREP_USES_THRESHOLD): with patch("sys.stdin", _make_stdin(_base_input("grep"))), patch("serena.hooks.serena_home_dir", str(tmp_path)): PreToolUseRemindAboutSerenaHook(HookClient.CLAUDE_CODE).execute() assert capsys.readouterr().out == "" + counter_after = ToolUseCounter.load(stub_for_path) + assert counter_after == counter_before, "gated hook must not mutate the persisted counter" + def test_rate_limit_allows_deny_after_interval(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]): """Once the minimum deny interval has elapsed, a fresh burst emits a deny again.""" # first burst: emits a deny @@ -348,23 +359,23 @@ class TestToolUseCounter: loaded = ToolUseCounter.load(hook_stub) # type: ignore[arg-type] assert loaded == ToolUseCounter() - def test_can_emit_deny_respects_min_interval(self): - """:meth:`can_emit_deny` returns False within the minimum interval, True outside it.""" + def test_is_hook_active_respects_min_interval(self): + """:meth:`is_hook_active` returns False within the minimum interval, True outside it.""" counter = ToolUseCounter() base = datetime.now() - # no prior deny → always allowed - assert counter.can_emit_deny(base) + # no prior deny → hook is always active + assert counter.is_hook_active(base) - # within the interval → rate-limited + # within the interval → hook gated counter.last_deny_timestamp = base interval = ToolUseCounter._MIN_DENY_INTERVAL_SECONDS - assert not counter.can_emit_deny(base + timedelta(seconds=interval - 1)) - assert not counter.can_emit_deny(base) + assert not counter.is_hook_active(base + timedelta(seconds=interval - 1)) + assert not counter.is_hook_active(base) - # at/after the interval → allowed again - assert counter.can_emit_deny(base + timedelta(seconds=interval)) - assert counter.can_emit_deny(base + timedelta(seconds=interval + 1)) + # at/after the interval → hook active again + assert counter.is_hook_active(base + timedelta(seconds=interval)) + assert counter.is_hook_active(base + timedelta(seconds=interval + 1)) def test_reset_preserves_last_deny_timestamp(self): """``reset`` clears burst counters but must keep ``last_deny_timestamp`` intact.""" diff --git a/uv.lock b/uv.lock index b79d5624..9cf66c5f 100644 --- a/uv.lock +++ b/uv.lock @@ -1685,6 +1685,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] +[[package]] +name = "oraios-pywebview" +version = "6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bottle" }, + { name = "proxy-tools" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" }, + { name = "pythonnet", marker = "sys_platform == 'win32'" }, + { name = "qtpy", marker = "sys_platform == 'openbsd6'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/e4/6820d2a00b42621240762947c9e07d83acbb1251575349472a48fe9b1a9f/oraios_pywebview-6.2.tar.gz", hash = "sha256:2b176547321ff9c289ebb10d5b2423bc8887a4fc730b35a78656de7839e5be37", size = 499560, upload-time = "2026-04-10T15:01:42.363Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/76/ff666b5abc3b3ccc35f69d62b27290b75b7ef5ac4beb2c59e8f59f5da5ef/oraios_pywebview-6.2-py3-none-any.whl", hash = "sha256:c5b81d2f6cf4cbfdf3d608d5bc3e76d8f0a732dbdd8567660a9bc9cebdd35b84", size = 512739, upload-time = "2026-04-10T15:01:43.689Z" }, +] + [[package]] name = "overrides" version = "7.7.0" @@ -2412,24 +2434,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/3d/a378aa74085fc6f72923d5e39fc30efbc97541cfd49b3fe0a35f20d09ff7/pythonnet-3.1.0rc0-py3-none-any.whl", hash = "sha256:286ccf23d08e1cc2b5200bb91d0ee649436d2e8386fec36a85193c66d0ff8b5b", size = 293150, upload-time = "2026-02-25T10:46:48.535Z" }, ] -[[package]] -name = "pywebview" -version = "6.2.dev50+g345596c1f" -source = { git = "https://github.com/r0x0r/pywebview?rev=345596c1f4fd59fd26c7aa02d4176c5362bcbabe#345596c1f4fd59fd26c7aa02d4176c5362bcbabe" } -dependencies = [ - { name = "bottle" }, - { name = "proxy-tools" }, - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-uniformtypeidentifiers", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" }, - { name = "pythonnet", marker = "sys_platform == 'win32'" }, - { name = "qtpy", marker = "sys_platform == 'openbsd6'" }, - { name = "typing-extensions" }, -] - [[package]] name = "pywin32" version = "311" @@ -2921,6 +2925,7 @@ dependencies = [ { name = "jinja2" }, { name = "joblib" }, { name = "mcp" }, + { name = "oraios-pywebview" }, { name = "overrides" }, { name = "pathspec" }, { name = "psutil" }, @@ -2930,7 +2935,6 @@ dependencies = [ { name = "python-dotenv" }, { name = "python-multipart" }, { name = "pythonnet", marker = "sys_platform == 'win32'" }, - { name = "pywebview" }, { name = "pyyaml" }, { name = "regex" }, { name = "requests" }, @@ -2998,6 +3002,7 @@ requires-dist = [ { name = "mypy", marker = "extra == 'dev'", specifier = "==1.17.0" }, { name = "nbconvert", marker = "extra == 'dev'", specifier = "==7.17.0" }, { name = "nbsphinx", marker = "extra == 'dev'", specifier = "==0.9.7" }, + { name = "oraios-pywebview", specifier = "==6.2" }, { name = "overrides", specifier = "==7.7.0" }, { name = "pathspec", specifier = "==0.12.1" }, { name = "poethepoet", marker = "extra == 'dev'", specifier = "==0.36.0" }, @@ -3014,7 +3019,6 @@ requires-dist = [ { name = "python-dotenv", specifier = "==1.2.1" }, { name = "python-multipart", specifier = "==0.0.22" }, { name = "pythonnet", marker = "sys_platform == 'win32'", specifier = "==3.1.0rc0" }, - { name = "pywebview", git = "https://github.com/r0x0r/pywebview?rev=345596c1f4fd59fd26c7aa02d4176c5362bcbabe" }, { name = "pyyaml", specifier = "==6.0.2" }, { name = "regex", specifier = "==2026.2.28" }, { name = "requests", specifier = "==2.33.0" },