diff --git a/Makefile b/Makefile index a1fb098..5329084 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: check check: + uv run ruff format uv run ruff check . uv run ty check . uv run pytest diff --git a/src/takopi/config.py b/src/takopi/config.py index 251fc25..915f7e6 100644 --- a/src/takopi/config.py +++ b/src/takopi/config.py @@ -11,8 +11,7 @@ class ConfigError(RuntimeError): _EXAMPLE_CONFIG = ( - 'bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"\n' - "chat_id = 123456789\n" + 'bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"\nchat_id = 123456789\n' ) @@ -29,10 +28,7 @@ def _display_path(path: Path) -> str: def _missing_config_message(primary: Path, alternate: Path | None = None) -> str: example = "Example config:\n```\n" + _EXAMPLE_CONFIG + "```\n" if alternate is None: - return ( - f"Missing config file `{_display_path(primary)}`.\n" - f"{example}" - ) + return f"Missing config file `{_display_path(primary)}`.\n{example}" return ( "Missing takopi config.\n" "Create one of these files:\n" diff --git a/src/takopi/exec_bridge.py b/src/takopi/exec_bridge.py index 9d8df90..2955d44 100644 --- a/src/takopi/exec_bridge.py +++ b/src/takopi/exec_bridge.py @@ -41,9 +41,7 @@ def extract_session_id(text: str | None) -> str | None: return found -def resolve_resume_session( - text: str | None, reply_text: str | None -) -> str | None: +def resolve_resume_session(text: str | None, reply_text: str | None) -> str | None: return extract_session_id(text) or extract_session_id(reply_text) @@ -124,9 +122,7 @@ def truncate_for_telegram(text: str, limit: int) -> str: return (head + sep + tail)[:limit] -def prepare_telegram( - md: str, *, limit: int -) -> tuple[str, list[dict[str, Any]] | None]: +def prepare_telegram(md: str, *, limit: int) -> tuple[str, list[dict[str, Any]] | None]: rendered, entities = render_markdown(md) if len(rendered) > limit: rendered = truncate_for_telegram(rendered, limit) @@ -655,9 +651,7 @@ async def _run_main_loop(cfg: BridgeConfig) -> None: r = msg.get("reply_to_message") or {} resume_session = resolve_resume_session(text, r.get("text")) - await queue.put( - (msg["chat"]["id"], user_msg_id, text, resume_session) - ) + await queue.put((msg["chat"]["id"], user_msg_id, text, resume_session)) finally: await cfg.bot.close() diff --git a/src/takopi/exec_render.py b/src/takopi/exec_render.py index 594b5c4..a31613f 100644 --- a/src/takopi/exec_render.py +++ b/src/takopi/exec_render.py @@ -130,15 +130,27 @@ def format_event( exit_part = "" else: status = STATUS_FAIL if exit_code is not None else STATUS_DONE - exit_part = f" (exit {exit_code})" if exit_code is not None else "" + exit_part = ( + f" (exit {exit_code})" if exit_code is not None else "" + ) line = prefix + f"{status} {command}{exit_part}" return last_item, [line], line, prefix case ("mcp_tool_call", "item.started"): - name = ".".join(part for part in (item["server"], item["tool"]) if part) or "tool" + name = ( + ".".join( + part for part in (item["server"], item["tool"]) if part + ) + or "tool" + ) line = prefix + f"{STATUS_RUNNING} tool: {name}" return last_item, [line], line, prefix case ("mcp_tool_call", "item.completed"): - name = ".".join(part for part in (item["server"], item["tool"]) if part) or "tool" + name = ( + ".".join( + part for part in (item["server"], item["tool"]) if part + ) + or "tool" + ) line = prefix + f"{STATUS_DONE} tool: {name}" return last_item, [line], line, prefix case ("web_search", "item.completed"): @@ -146,12 +158,20 @@ def format_event( line = prefix + f"{STATUS_DONE} searched: {query}" return last_item, [line], line, prefix case ("file_change", "item.completed"): - paths = [change["path"] for change in item["changes"] if change.get("path")] + paths = [ + change["path"] + for change in item["changes"] + if change.get("path") + ] if not paths: total = len(item["changes"]) - desc = "updated files" if total == 0 else f"updated {total} files" + desc = ( + "updated files" if total == 0 else f"updated {total} files" + ) elif len(paths) <= 3: - desc = "updated " + ", ".join(f"`{_shorten_path(p, MAX_PATH_LEN)}`" for p in paths) + desc = "updated " + ", ".join( + f"`{_shorten_path(p, MAX_PATH_LEN)}`" for p in paths + ) else: desc = f"updated {len(paths)} files" line = prefix + f"{STATUS_DONE} {desc}" @@ -200,7 +220,11 @@ class ExecProgressRenderer: return False # Replace the preceding "running" line for the same item on completion. - if event["type"] == "item.completed" and progress_prefix and self.recent_actions: + if ( + event["type"] == "item.completed" + and progress_prefix + and self.recent_actions + ): last = self.recent_actions[-1] if last.startswith(progress_prefix + f"{STATUS_RUNNING} "): self.recent_actions.pop() diff --git a/tests/conftest.py b/tests/conftest.py index 25412bc..99f1af1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,4 +2,3 @@ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) - diff --git a/tests/test_exec_bridge.py b/tests/test_exec_bridge.py index 2001da3..a8d8388 100644 --- a/tests/test_exec_bridge.py +++ b/tests/test_exec_bridge.py @@ -41,9 +41,7 @@ def test_resolve_resume_session_prefers_message_text() -> None: uuid_reply = "019b66fc-64c2-7a71-81cd-081c504cfeb2" assert ( - resolve_resume_session( - f"resume: `{uuid_message}`", f"resume: `{uuid_reply}`" - ) + resolve_resume_session(f"resume: `{uuid_message}`", f"resume: `{uuid_reply}`") == uuid_message ) @@ -51,7 +49,10 @@ def test_resolve_resume_session_prefers_message_text() -> None: def test_resolve_resume_session_uses_reply_when_missing() -> None: uuid_reply = "019b66fc-64c2-7a71-81cd-081c504cfeb2" - assert resolve_resume_session("no resume here", f"resume: `{uuid_reply}`") == uuid_reply + assert ( + resolve_resume_session("no resume here", f"resume: `{uuid_reply}`") + == uuid_reply + ) def test_truncate_for_telegram_preserves_resume_line() -> None: @@ -143,7 +144,11 @@ class _FakeRunner: self._saw_agent_message = saw_agent_message async def run_serialized(self, *_args, **_kwargs) -> tuple[str, str, bool]: - return ("019b66fc-64c2-7a71-81cd-081c504cfeb2", self._answer, self._saw_agent_message) + return ( + "019b66fc-64c2-7a71-81cd-081c504cfeb2", + self._answer, + self._saw_agent_message, + ) class _FakeClock: diff --git a/tests/test_exec_render.py b/tests/test_exec_render.py index 2af43dd..b8fdd27 100644 --- a/tests/test_exec_render.py +++ b/tests/test_exec_render.py @@ -81,7 +81,10 @@ def test_render_event_cli_all_formats_fixture() -> None: assert any(line.startswith("stream error:") for line in out) assert any(line.startswith("4. ▸ `pytest -q`") for line in out) assert any("✗ `pytest -q` (exit 1)" in line for line in out) - assert any("searched: python jsonlines parser handle unknown fields" in line for line in out) + assert any( + "searched: python jsonlines parser handle unknown fields" in line + for line in out + ) assert any("tool: github.search_issues" in line for line in out) assert any("updated `src/compute_answer.py`" in line for line in out) assert any( diff --git a/tests/test_rendering.py b/tests/test_rendering.py index 35b9af9..541aeb7 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -16,7 +16,5 @@ def test_render_markdown_code_fence_language_is_string() -> None: assert text == "print('x')\n\n" assert entities is not None - assert any( - e.get("type") == "pre" and e.get("language") == "py" for e in entities - ) + assert any(e.get("type") == "pre" and e.get("language") == "py" for e in entities) assert any(e.get("type") == "code" for e in entities)