diff --git a/agentkit/toolkit/cli/cli.py b/agentkit/toolkit/cli/cli.py index 25261c2..57a7bac 100644 --- a/agentkit/toolkit/cli/cli.py +++ b/agentkit/toolkit/cli/cli.py @@ -47,6 +47,7 @@ from agentkit.toolkit.cli.cli_add import add_app from agentkit.toolkit.cli.cli_list import list_app from agentkit.toolkit.cli.cli_delete import delete_app +from agentkit.toolkit.cli.cli_logs import logs_command # Note: Avoid importing heavy packages at the top to keep CLI startup fast @@ -109,6 +110,7 @@ def main( app.command(name="launch")(launch_command) app.command(name="status")(status_command) app.command(name="destroy")(destroy_command) +app.command(name="logs")(logs_command) # Auth: top-level convenience commands + an `auth` group for profiles. app.command(name="login")(login_command) diff --git a/agentkit/toolkit/cli/cli_logs.py b/agentkit/toolkit/cli/cli_logs.py new file mode 100644 index 0000000..6a75911 --- /dev/null +++ b/agentkit/toolkit/cli/cli_logs.py @@ -0,0 +1,253 @@ +# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""AgentKit CLI - ``logs`` command. + +Query a deployed harness runtime's logs from APMPlus / TLS. The ``--harness`` +value names a runtime; it must carry the harness tag stamped at deploy time +(``agentkit:agenttype=harness``) — otherwise it is a regular agent app whose logs +this command cannot query. +""" + +import datetime +import re +import time +from pathlib import Path +from typing import Optional + +import typer +from rich.console import Console + +console = Console() + +_DURATION_UNITS_MS = {"s": 1000, "m": 60_000, "h": 3_600_000, "d": 86_400_000} +_DURATION_RE = re.compile(r"(\d+)([smhd])") + + +def _parse_since(since: str) -> int: + """Parse a relative duration like ``1h`` / ``30m`` / ``1h30m`` / ``2d`` to ms. + + Raises: + ValueError: when the string has no recognizable ```` token. + """ + matches = _DURATION_RE.findall(since.strip().lower()) + if not matches or "".join(n + u for n, u in matches) != since.strip().lower(): + raise ValueError( + f"无法解析 --since '{since}',请使用形如 1h / 30m / 2d / 1h30m 的格式。" + ) + return sum(int(n) * _DURATION_UNITS_MS[u] for n, u in matches) + + +def _format_timestamp(ts) -> str: + """Format a millisecond epoch timestamp to local time; pass through on failure.""" + try: + return datetime.datetime.fromtimestamp(int(ts) / 1000).strftime( + "%Y-%m-%d %H:%M:%S" + ) + except (ValueError, TypeError, OSError): + return str(ts) + + +def _render_line(entry: dict) -> str: + """Render one log entry as a plain ``