Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ python -m logpilot.cli analyze <file> [OPTIONS]

### Options

- `--version` : Show the current logpilot version and exit
- `--format [auto|json|text]` : Log format (default: auto)
- `--output [text|json]` : Output type (default: text)
- `--max-tokens <int>` : Max tokens per chunk (default: 2048)
Expand Down
43 changes: 43 additions & 0 deletions logpilot/cli.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
import os

import typer

try:
import tomllib
except ImportError:
import toml as tomllib

from logpilot.chunker import chunk_logs
from logpilot.llm_client import LLMClient
from logpilot.log_parser import parse_logs
from logpilot.postprocessor import aggregate_responses
from logpilot.prompt_engine import format_prompt


def get_version() -> str:
pyproject_path = os.path.join(
os.path.dirname(os.path.dirname(__file__)), "pyproject.toml"
)
try:
with open(pyproject_path, "rb") as f:
data = tomllib.load(f)
version = data.get("project", {}).get("version")
if version:
return version
except Exception:
pass
return "unknown"


def version_callback(value: bool):
if value:
typer.echo(f"logpilot version {get_version()}")
raise typer.Exit()


app = typer.Typer()


Expand Down Expand Up @@ -70,5 +99,19 @@ def analyze(
typer.echo(summary)


@app.callback()
def main(
version: bool = typer.Option(
None,
"--version",
help="Show the version and exit.",
callback=version_callback,
is_eager=True,
),
):
"""Logpilot CLI"""
pass


if __name__ == "__main__":
app()
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[project]
name = "logpilot"
version = "0.1.0"
[tool.black]
line-length = 88
skip-string-normalization = false
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
toml
typer
rich
git+https://github.com/github/copilot-sdk.git#subdirectory=python
Expand Down
20 changes: 10 additions & 10 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ def run_cli(args, env=None):


def test_cli_missing_file():
result = run_cli(["not_a_real_file.log"])
result = run_cli(["analyze", "not_a_real_file.log"])
assert result.returncode != 0
assert "No such file" in result.stderr or "Error" in result.stderr


def test_cli_empty_file(tmp_path):
log_file = tmp_path / "empty.log"
log_file.write_text("")
result = run_cli([str(log_file)])
result = run_cli(["analyze", str(log_file)])
assert result.returncode != 0
assert "No log entries found" in result.stderr or "Error" in result.stderr


def test_cli_invalid_format(tmp_path):
log_file = tmp_path / "invalid.log"
log_file.write_text("not a log line\nnot json\n")
result = run_cli([str(log_file)])
result = run_cli(["analyze", str(log_file)])
assert result.returncode != 0
assert "No log entries found" in result.stderr or "Error" in result.stderr

Expand All @@ -44,7 +44,7 @@ def test_cli_out_file(tmp_path):
)
)
out_file = tmp_path / "out.txt"
result = run_cli([str(log_file), "--out-file", str(out_file)])
result = run_cli(["analyze", str(log_file), "--out-file", str(out_file)])
assert result.returncode == 0, f"stdout: {result.stdout}\nstderr: {result.stderr}"
assert out_file.read_text().strip() != ""

Expand All @@ -58,7 +58,7 @@ def test_cli_output_json(tmp_path):
'"message": "Something failed"}\n'
)
)
result = run_cli([str(log_file), "--output", "json"])
result = run_cli(["analyze", str(log_file), "--output", "json"])
assert result.returncode == 0, f"stdout: {result.stdout}\nstderr: {result.stderr}"
import json as pyjson

Expand All @@ -78,14 +78,14 @@ def test_cli_format_json(tmp_path):
'"message": "Something failed"}\n'
)
)
result = run_cli([str(log_file), "--format", "json"])
result = run_cli(["analyze", str(log_file), "--format", "json"])
assert result.returncode == 0, f"stdout: {result.stdout}\nstderr: {result.stderr}"


def test_cli_format_text(tmp_path):
log_file = tmp_path / "test.log"
log_file.write_text("plain log line\n")
result = run_cli([str(log_file), "--format", "text"])
result = run_cli(["analyze", str(log_file), "--format", "text"])
assert result.returncode != 0 or "plain log line" in result.stdout


Expand All @@ -94,7 +94,7 @@ def test_cli_unreadable_file(tmp_path):
log_file.write_text("test")
log_file.chmod(0)
try:
result = run_cli([str(log_file)])
result = run_cli(["analyze", str(log_file)])
assert result.returncode != 0
# Accept either error message or 'No log entries found.'
valid = (
Expand All @@ -117,7 +117,7 @@ def test_cli_large_file(tmp_path):
)
* 10000
)
result = run_cli([str(log_file)])
result = run_cli(["analyze", str(log_file)])
assert result.returncode == 0, f"stdout: {result.stdout}\nstderr: {result.stderr}"


Expand Down Expand Up @@ -160,7 +160,7 @@ def test_cli_analyze_basic(tmp_path, monkeypatch):
env = dict(os.environ)
env["LOGPILOT_MOCK_LLM"] = "1"
result = subprocess.run(
[sys.executable, "-m", "logpilot.cli", str(log_file)],
[sys.executable, "-m", "logpilot.cli", "analyze", str(log_file)],
capture_output=True,
text=True,
env=env,
Expand Down