Skip to content

Commit 10e021f

Browse files
committed
first commmit
0 parents  commit 10e021f

9 files changed

Lines changed: 305 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
build-and-publish:
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
matrix:
16+
os: [ubuntu-latest, windows-latest]
17+
fail-fast: false # Continue even if one OS fails
18+
19+
steps:
20+
# Checkout the repository
21+
- name: Checkout code
22+
uses: actions/checkout@v4
23+
24+
# Set up Python
25+
- name: Set up Python
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: '3.13.2'
29+
30+
# Install pyenv dependencies (Ubuntu only)
31+
- name: Install pyenv dependencies (Ubuntu)
32+
if: matrix.os == 'ubuntu-latest'
33+
run: |
34+
sudo apt-get update
35+
sudo apt-get install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
36+
libreadline-dev libsqlite3-dev curl libncurses5-dev libncursesw5-dev \
37+
xz-utils libffi-dev liblzma-dev python3-openssl
38+
39+
# Install dependencies
40+
- name: Install project dependencies
41+
run: |
42+
python -m pip install --upgrade pip
43+
pip install -r requirements.txt
44+
pip install pyinstaller
45+
46+
# Run tests (placeholder—uncomment and adjust when tests are added)
47+
# - name: Run tests
48+
# run: |
49+
# pip install pytest
50+
# pytest tests/
51+
52+
# Build the executable with PyInstaller
53+
- name: Build executable
54+
run: |
55+
pyinstaller pagertree.spec
56+
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
57+
mv dist/pagertree dist/pagertree-linux
58+
elif [ "${{ matrix.os }}" = "windows-latest" ]; then
59+
mv dist/pagertree.exe dist/pagertree-windows.exe
60+
fi
61+
shell: bash # Use bash for consistent scripting across OSes
62+
63+
# Version the build (using git tag or commit SHA)
64+
- name: Set version
65+
id: version
66+
run: |
67+
VERSION=$(git describe --tags --always --dirty)
68+
echo "VERSION=$VERSION" >> $GITHUB_ENV
69+
shell: bash
70+
71+
# Upload artifact
72+
- name: Upload executable
73+
uses: actions/upload-artifact@v4
74+
with:
75+
name: pagertree-${{ matrix.os == 'ubuntu-latest' && 'linux' || 'windows' }}-${{ env.VERSION }}
76+
path: dist/pagertree-${{ matrix.os == 'ubuntu-latest' && 'linux' || 'windows' }}${{ matrix.os == 'windows-latest' && '.exe' || '' }}
77+
78+
# Publish to GitHub Releases (only on push to main)
79+
- name: Create Release
80+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
81+
uses: softprops/action-gh-release@v2
82+
with:
83+
tag_name: ${{ env.VERSION }}
84+
name: Release ${{ env.VERSION }}
85+
draft: false
86+
prerelease: false
87+
files: |
88+
dist/pagertree-linux
89+
dist/pagertree-windows.exe
90+
env:
91+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Python-specific
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
dist/
9+
*.egg-info/
10+
*.egg
11+
pip_log.txt
12+
13+
# Virtual environment
14+
venv/
15+
env/
16+
.venv/
17+
18+
# Environment variables (from our .env suggestion)
19+
.env
20+
21+
# PyInstaller (if you package it as an executable, as mentioned earlier)
22+
*.spec
23+
dist/
24+
build/
25+
26+
# IDEs and editors
27+
.idea/
28+
.vscode/
29+
*.sublime-project
30+
*.sublime-workspace
31+
32+
# OS-specific
33+
.DS_Store
34+
Thumbs.db
35+
36+
# Logs and temporary files
37+
*.log
38+
*.tmp
39+
*.bak
40+
41+
# Project-specific caches or local data (if you add caching later)
42+
*.cache
43+
cache/
44+
45+
# Coverage and testing (if you add tests later)
46+
.coverage
47+
coverage.xml
48+
htmlcov/

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# PagerTree CLI
2+
3+
A command-line interface (CLI) tool for interacting with the PagerTree API. This tool allows users to manage alerts and other resources in PagerTree directly from the terminal. Built with Python and `click`, it supports nested commands, pagination, and table-formatted output.
4+
5+
## Features
6+
- Manage PagerTree alerts (`alerts create`, `alerts list`, `alerts show`).
7+
- Pagination support with `--limit`, `--offset`, and `--all` options.
8+
- Pretty table output using `tabulate`.
9+
- Environment variable configuration with `.env` file support.
10+
- Extensible design with dynamic command registration.

api.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import requests
2+
from config import API_KEY, BASE_URL
3+
4+
def api_create_alert(title, description):
5+
"""Create a new alert in PagerTree."""
6+
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
7+
payload = {
8+
"title": title,
9+
"description": description,
10+
}
11+
response = requests.post(f"{BASE_URL}/alerts", json=payload, headers=headers)
12+
response.raise_for_status() # Raise an error for bad responses
13+
return response.json()
14+
15+
def api_list_alerts(limit=10, offset=0):
16+
"""List all alerts in PagerTree."""
17+
headers = {"Authorization": f"Bearer {API_KEY}"}
18+
params = {"limit": limit, "offset": offset} # Adjust based on API docs
19+
response = requests.get(f"{BASE_URL}/alerts", headers=headers, params=params)
20+
response.raise_for_status()
21+
data = response.json()
22+
return {
23+
"data": data.get("data", []), # List of incidents
24+
"total": data.get("total_count", 0), # Total number of incidents
25+
"has_more": data.get("has_more", False), # Boolean indicating if there are more incidents
26+
"limit": limit,
27+
"offset": offset
28+
}
29+
30+
def api_show_alert(alert_id):
31+
"""Fetch a single alert by ID from PagerTree."""
32+
headers = {"Authorization": f"Bearer {API_KEY}"}
33+
response = requests.get(f"{BASE_URL}/alerts/{alert_id}", headers=headers)
34+
response.raise_for_status()
35+
return response.json()

commands/alerts.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import click
2+
from api import api_create_alert, api_list_alerts, api_show_alert
3+
from utils import display_paginated_results, handle_api_error, format_item_details
4+
5+
@click.group()
6+
def alerts():
7+
"""Commands for managing PagerTree alerts."""
8+
pass
9+
10+
@alerts.command(name="create")
11+
@click.option("--title", required=True, help="Title of the alert")
12+
@click.option("--description", help="Description of the alert")
13+
def create_alert_cmd(title, description):
14+
"""Create a new alert in PagerTree."""
15+
try:
16+
result = api_create_alert(title, description or "No description provided")
17+
click.echo(f"Alert created successfully: {result.get('id')}")
18+
except Exception as e:
19+
click.echo(f"Error creating alert: {str(e)}", err=True)
20+
21+
@alerts.command(name="list")
22+
@click.option("--limit", default=10, type=click.IntRange(1, 100), help="Number of alerts per page")
23+
@click.option("--offset", default=0, type=click.IntRange(0), help="Starting point for pagination")
24+
def list_alerts_cmd(limit, offset):
25+
"""List alerts in PagerTree with pagination."""
26+
try:
27+
result = api_list_alerts(limit=limit, offset=offset)
28+
alerts_list = result["data"]
29+
total = result["total"]
30+
# Prepare table data
31+
headers = ["ID", "Title", "Status"]
32+
table_data = [[alert.get("tiny_id"), alert.get("title"), alert.get("status")] for alert in alerts_list]
33+
display_paginated_results(alerts_list, total, limit, offset, "alert", headers, table_data)
34+
except Exception as e:
35+
handle_api_error(e, action="listing alerts")
36+
37+
@alerts.command(name="show")
38+
@click.argument("alert_id", required=True)
39+
def show_alert_cmd(alert_id):
40+
"""Show details of a specific alert in PagerTree."""
41+
try:
42+
alert = api_show_alert(alert_id)
43+
fields = {
44+
"id": "Alert ID",
45+
"title": "Title",
46+
"description": "Description",
47+
"status": "Status",
48+
"created_at": "Created At"
49+
}
50+
format_item_details(alert, fields)
51+
except requests.exceptions.HTTPError as e:
52+
click.echo(f"Error fetching alert: {e.response.status_code} - {e.response.reason}", err=True)
53+
except Exception as e:
54+
handle_api_error(e, "showing alert")

config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import os
2+
from dotenv import load_dotenv
3+
4+
# Load .env file if it exists (useful for development)
5+
load_dotenv()
6+
7+
# Configuration variables from environment variables
8+
API_KEY = os.getenv("PAGERTREE_API_KEY", "your-default-api-key-here")
9+
BASE_URL = os.getenv("PAGERTREE_BASE_URL", "https://api.pagertree.com/api/v4")

pagertree.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python
2+
3+
import click
4+
import os
5+
import importlib
6+
import pkgutil
7+
8+
@click.group()
9+
def cli():
10+
"""PagerTree CLI Tool - Manage alerts from the command line."""
11+
pass
12+
13+
# Dynamically register all command groups from the 'commands' package
14+
commands_dir = os.path.join(os.path.dirname(__file__), "commands")
15+
for _, module_name, _ in pkgutil.iter_modules([commands_dir]):
16+
module = importlib.import_module(f"commands.{module_name}")
17+
if hasattr(module, module_name): # Check if the module has a group with the same name
18+
cli.add_command(getattr(module, module_name))
19+
20+
if __name__ == "__main__":
21+
cli()

requirements.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
altgraph==0.17.4
2+
certifi==2025.1.31
3+
charset-normalizer==3.4.1
4+
click==8.1.8
5+
idna==3.10
6+
packaging==24.2
7+
pyinstaller==6.12.0
8+
pyinstaller-hooks-contrib==2025.2
9+
python-dotenv==1.1.0
10+
requests==2.32.3
11+
setuptools==78.1.0
12+
tabulate==0.9.0
13+
urllib3==2.3.0

utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import click
2+
import requests
3+
from tabulate import tabulate
4+
5+
def display_paginated_results(items, total, limit, offset, item_type="item", table_headers=None, table_data=None):
6+
"""Display a paginated list of items with consistent formatting."""
7+
click.echo(f"Showing {len(items)} of {total} {item_type}s (offset: {offset}, limit: {limit})")
8+
if table_headers and table_data:
9+
click.echo(tabulate(table_data, headers=table_headers, tablefmt="github"))
10+
if offset + limit < total:
11+
click.echo(f"More {item_type}s available. Use --offset {offset + limit} to see next page.")
12+
13+
def handle_api_error(e, action="performing action"):
14+
"""Handle API errors with consistent messaging."""
15+
if isinstance(e, requests.exceptions.HTTPError):
16+
click.echo(f"Error {action}: {e.response.status_code} - {e.response.reason}", err=True)
17+
else:
18+
click.echo(f"Error {action}: {str(e)}", err=True)
19+
20+
def format_item_details(item, fields):
21+
"""Format and display item details from a dict with specified fields."""
22+
for field_name, display_name in fields.items():
23+
value = item.get(field_name, "N/A")
24+
click.echo(f"{display_name}: {value}")

0 commit comments

Comments
 (0)