diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5ee0983 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,118 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + +permissions: + contents: write # needed to create GitHub Releases + id-token: write # needed for PyPI trusted publishing (OIDC) + +jobs: + # ────────────────────────────────────────────────────────────────────────── + # 1. Run the full test suite before releasing + # ────────────────────────────────────────────────────────────────────────── + test: + name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-dev.txt + + - name: Install package (editable) + run: pip install -e . + + - name: Run tests + run: pytest + + # ────────────────────────────────────────────────────────────────────────── + # 2. Build the source distribution and wheel + # ────────────────────────────────────────────────────────────────────────── + build: + name: Build distribution + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build tool + run: pip install --upgrade build + + - name: Build sdist and wheel + run: python -m build + + - name: Upload distribution artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + retention-days: 7 + + # ────────────────────────────────────────────────────────────────────────── + # 3. Publish to PyPI via OIDC trusted publishing (no API token required) + # Set up the trusted publisher on PyPI first: + # https://docs.pypi.org/trusted-publishers/adding-a-publisher/ + # ────────────────────────────────────────────────────────────────────────── + publish-pypi: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/logpilot + steps: + - name: Download distribution artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + # ────────────────────────────────────────────────────────────────────────── + # 4. Create a GitHub Release with auto-generated release notes + # ────────────────────────────────────────────────────────────────────────── + github-release: + name: Create GitHub Release + needs: publish-pypi + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for release notes + + - name: Download distribution artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: dist/* + generate_release_notes: true + draft: false + prerelease: ${{ contains(github.ref_name, '-') }} # e.g. v1.0.0-rc1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1d3414f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0] - 2026-02-26 + +### Added + +- Initial release of `logpilot` +- `analyze` command to analyze log files and directories using the GitHub Copilot SDK +- Support for plain-text and JSON log formats with `--format auto|json|text` +- Auto-detection of log format when `--format auto` (default) +- Accurate token counting via [tiktoken](https://github.com/openai/tiktoken) (cl100k_base / GPT-4 compatible) +- Character-based token count fallback when tiktoken is unavailable +- Automatic chunking of large logs to stay within the model context window (`--max-tokens`) +- Directory mode: analyze all log files in a directory (with optional `--recursive`) +- Glob filters for directory mode (`--include`, `--exclude`) +- Output to stdout or a file (`--out-file`) +- Output format selection: human-readable text or JSON (`--output text|json`) +- Model selection via `--model` (default: `gpt-4`) +- `--version` flag to display the installed version +- Cross-platform support: Windows, macOS, Linux +- Python 3.9–3.13 compatibility + +[Unreleased]: https://github.com/colinmakerofthings/logpilot-cli/compare/v0.1.0...HEAD +[0.1.0]: https://github.com/colinmakerofthings/logpilot-cli/releases/tag/v0.1.0 diff --git a/README.md b/README.md index c1ce46f..4f49e48 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # logpilot 🪵 -![Python Version](https://img.shields.io/badge/python-3.9%2B-blue) +[![PyPI version](https://img.shields.io/pypi/v/logpilot.svg)](https://pypi.org/project/logpilot/) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/logpilot.svg)](https://pypi.org/project/logpilot/) +[![Release](https://github.com/colinmakerofthings/logpilot-cli/actions/workflows/release.yml/badge.svg)](https://github.com/colinmakerofthings/logpilot-cli/actions/workflows/release.yml) +[![Tests](https://github.com/colinmakerofthings/logpilot-cli/actions/workflows/test.yml/badge.svg)](https://github.com/colinmakerofthings/logpilot-cli/actions/workflows/test.yml) ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg) ![Linter: ruff](https://img.shields.io/badge/linter-ruff-d7ff64.svg) -![Security: ruff](https://img.shields.io/badge/security-ruff%20S-orange) A Python CLI tool for analyzing logs using the GitHub Copilot SDK. @@ -18,10 +20,10 @@ A Python CLI tool for analyzing logs using the GitHub Copilot SDK. - Large logs chunked automatically to stay within the model's context window - Uses Copilot SDK for LLM-powered analysis -## Usage (MVP) +## Usage ```sh -python -m logpilot.cli [--version] analyze [OPTIONS] +logpilot [--version] analyze [OPTIONS] ``` ### Global Options @@ -48,31 +50,31 @@ python -m logpilot.cli [--version] analyze [OPTIONS] Summarize a log file and print to terminal: ```sh -python -m logpilot.cli analyze mylog.txt +logpilot analyze mylog.txt ``` Summarize all logs in a directory (combined analysis): ```sh -python -m logpilot.cli analyze ./logs +logpilot analyze ./logs ``` Summarize logs in a directory recursively with filters: ```sh -python -m logpilot.cli analyze ./logs --recursive --include "*.log" --exclude "archive/*" +logpilot analyze ./logs --recursive --include "*.log" --exclude "archive/*" ``` Summarize a log file and write to a file: ```sh -python -m logpilot.cli analyze mylog.txt --out-file summary.txt +logpilot analyze mylog.txt --out-file summary.txt ``` Analyze logs with a different model: ```sh -python -m logpilot.cli analyze mylog.txt --model gpt-3.5-turbo +logpilot analyze mylog.txt --model gpt-3.5-turbo ``` ## Project Structure @@ -95,12 +97,32 @@ python -m logpilot.cli analyze mylog.txt --model gpt-3.5-turbo logpilot works on Windows, macOS, and Linux platforms where Python 3.9+ is available. -## Setup +## Installation -1. Clone the repo -2. Install dependencies: `pip install -r requirements.txt` -3. (Recommended) Install dev dependencies: `pip install -r requirements-dev.txt` -4. Authenticate with Copilot SDK as per GitHub's instructions +### From PyPI (recommended) + +```sh +pip install logpilot +``` + +> **Note:** `logpilot` requires the [GitHub Copilot SDK](https://github.com/github/copilot-sdk), which is not yet available on PyPI. +> Install it separately before running `logpilot`: +> +> ```sh +> pip install "git+https://github.com/github/copilot-sdk.git#subdirectory=python" +> ``` + +Then authenticate with the Copilot SDK as per [GitHub's instructions](https://github.com/github/copilot-sdk). + +### From source (development) + +```sh +git clone https://github.com/your-github-username/logpilot-cli.git +cd logpilot-cli +pip install -e . +# Optional: install dev/test dependencies +pip install -r requirements-dev.txt +``` ## License diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..36db869 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,171 @@ +# Release Process + +This document describes how to cut a new release of `logpilot`. + +Releases are published to [PyPI](https://pypi.org/project/logpilot/) and a +corresponding [GitHub Release](https://github.com/colinmakerofthings/logpilot-cli/releases) +is created automatically by the [release workflow](.github/workflows/release.yml). + +--- + +## Prerequisites (one-time setup) + +These steps only need to be done once before the very first release. + +### 1. Configure PyPI Trusted Publishing + +`logpilot` uses [OIDC Trusted Publishing](https://docs.pypi.org/trusted-publishers/) — +no PyPI API token is required. + +1. Log in to [pypi.org](https://pypi.org) and go to your account's + *Publishing* settings (or the project page once it exists). +2. Click **Add a new publisher** and fill in: + | Field | Value | + |---|---| + | Owner | `colinmakerofthings` | + | Repository | `logpilot-cli` | + | Workflow filename | `release.yml` | + | Environment name | `pypi` | +3. Save. No secrets need to be added to GitHub. + +### 2. Create the `pypi` GitHub environment + +1. In the GitHub repo go to **Settings → Environments → New environment**. +2. Name it `pypi`. +3. Optionally add a required reviewer for an extra approval gate before + publishing. + +--- + +## Release checklist + +Work through these steps in order every time you release a new version. + +### Step 1 — Decide the new version number + +Follow [Semantic Versioning](https://semver.org/): + +| Change type | Example bump | +|---|---| +| Bug fixes only | `0.1.0 → 0.1.1` | +| New backwards-compatible features | `0.1.0 → 0.2.0` | +| Breaking changes | `0.1.0 → 1.0.0` | +| Pre-release / release candidate | `0.2.0-rc1` | + +### Step 2 — Update `pyproject.toml` + +Bump the `version` field: + +```toml +[project] +name = "logpilot" +version = "0.2.0" # ← new version +``` + +### Step 3 — Update `CHANGELOG.md` + +1. Add a new version section under `## [Unreleased]`, using today's date: + + ```markdown + ## [0.2.0] - YYYY-MM-DD + + ### Added + - … + + ### Fixed + - … + + ### Changed + - … + ``` + +2. Leave `## [Unreleased]` empty above it (for the next cycle). + +3. Add a comparison link at the bottom of the file: + + ```markdown + [0.2.0]: https://github.com/colinmakerofthings/logpilot-cli/compare/v0.1.0...v0.2.0 + ``` + + And update the `[Unreleased]` link: + + ```markdown + [Unreleased]: https://github.com/colinmakerofthings/logpilot-cli/compare/v0.2.0...HEAD + ``` + +### Step 4 — Commit and tag + +```sh +git add pyproject.toml CHANGELOG.md +git commit -m "chore: release v0.2.0" + +git tag v0.2.0 +git push origin main +git push origin v0.2.0 +``` + +> Pushing the tag triggers the release workflow. The version tag **must** +> match the `version` field in `pyproject.toml` exactly (prefixed with `v`). + +--- + +## What the release workflow does + +The [`.github/workflows/release.yml`](.github/workflows/release.yml) pipeline +runs automatically when a `v*.*.*` tag is pushed. It has four sequential jobs: + +``` +test → build → publish-pypi → github-release +``` + +| Job | What it does | +|---|---| +| **test** | Runs the full pytest suite across Python 3.11/3.12/3.13 on Ubuntu, Windows, and macOS. A failure here aborts the release. | +| **build** | Builds the sdist (`*.tar.gz`) and wheel (`*.whl`) using `python -m build`. Artifacts are uploaded for the subsequent jobs. | +| **publish-pypi** | Publishes the distribution to PyPI using OIDC trusted publishing (no API token required). Runs inside the `pypi` GitHub environment. | +| **github-release** | Creates a GitHub Release for the tag, attaches the sdist and wheel, and auto-generates release notes from merged PRs / commits since the last tag. Tags containing a hyphen (e.g. `v1.0.0-rc1`) are automatically marked as pre-releases. | + +--- + +## Pre-releases + +To publish a release candidate or alpha without marking it as the latest stable +release on PyPI, use a version number with a hyphen suffix: + +```sh +# pyproject.toml → version = "0.2.0-rc1" +git tag v0.2.0-rc1 +git push origin v0.2.0-rc1 +``` + +The workflow detects the hyphen and sets `prerelease: true` on the GitHub +Release automatically. PyPI also treats `rc`, `a`, `b` suffixes as +pre-releases and will not serve them to users who run `pip install logpilot` +without `--pre`. + +--- + +## Verifying the release + +After the workflow completes: + +1. Check the [GitHub Release](https://github.com/colinmakerofthings/logpilot-cli/releases) + page — the sdist and wheel should be attached. +2. Check [PyPI](https://pypi.org/project/logpilot/) — the new version should + be visible within a few minutes. +3. Smoke-test in a clean environment: + + ```sh + pip install logpilot==0.2.0 + logpilot --version + ``` + +--- + +## Troubleshooting + +| Problem | Resolution | +|---|---| +| `publish-pypi` fails with a 403 | Check that the Trusted Publisher on PyPI is configured exactly as described in the prerequisites. | +| Tests fail on the tag push | Fix the failing tests on `main`, delete the tag (`git tag -d v0.x.x && git push origin :refs/tags/v0.x.x`), and re-tag after merging the fix. | +| Wrong version shown by `logpilot --version` | Ensure the `version` in `pyproject.toml` and the git tag match, and that the package was reinstalled after the bump. | diff --git a/logpilot/__init__.py b/logpilot/__init__.py index e69de29..9c3e112 100644 --- a/logpilot/__init__.py +++ b/logpilot/__init__.py @@ -0,0 +1,6 @@ +try: + from importlib.metadata import version + + __version__ = version("logpilot") +except Exception: + __version__ = "unknown" diff --git a/logpilot/cli.py b/logpilot/cli.py index a64055a..8a331e3 100644 --- a/logpilot/cli.py +++ b/logpilot/cli.py @@ -18,15 +18,24 @@ def get_version() -> str: + # Prefer importlib.metadata for installed packages (includes PyPI installs) + try: + from importlib.metadata import version + + return version("logpilot") + except Exception: # noqa: S110 + pass # Intentionally silent - fall through to pyproject.toml fallback + + # Fallback: read pyproject.toml directly (useful in editable/dev installs) 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 + ver = data.get("project", {}).get("version") + if ver: + return ver except Exception: # noqa: S110 pass # Intentionally silent - version detection is optional return "unknown" diff --git a/pyproject.toml b/pyproject.toml index b8ca950..aae5687 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,65 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [project] name = "logpilot" version = "0.1.0" +description = "A CLI tool for AI-powered log analysis using the GitHub Copilot SDK" +readme = "README.md" +license = { text = "MIT" } +requires-python = ">=3.9" +authors = [ + { name = "logpilot contributors" }, +] +keywords = ["cli", "logs", "log-analysis", "ai", "copilot", "llm"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Logging", + "Topic :: Utilities", +] +dependencies = [ + "typer>=0.9.0", + "rich>=13.0.0", + "tqdm>=4.0.0", + "orjson>=3.0.0", + "tiktoken>=0.5.0", + "toml>=0.10.0; python_version < '3.11'", +] + +[project.optional-dependencies] +dev = [ + "black", + "ruff", + "pytest", + "pytest-mock", + "pytest-cov", +] + +[project.scripts] +logpilot = "logpilot.cli:app" + +[project.urls] +Homepage = "https://github.com/colinmakerofthings/logpilot-cli" +Repository = "https://github.com/colinmakerofthings/logpilot-cli" +Issues = "https://github.com/colinmakerofthings/logpilot-cli/issues" +Changelog = "https://github.com/colinmakerofthings/logpilot-cli/blob/main/CHANGELOG.md" + +[tool.hatch.build.targets.wheel] +packages = ["logpilot"] + [tool.black] line-length = 88 skip-string-normalization = false