kctl-glitchtip 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kctl_glitchtip/__init__.py +1 -0
- kctl_glitchtip/__main__.py +3 -0
- kctl_glitchtip/cli.py +136 -0
- kctl_glitchtip/commands/__init__.py +0 -0
- kctl_glitchtip/commands/alerts.py +110 -0
- kctl_glitchtip/commands/config_cmd.py +498 -0
- kctl_glitchtip/commands/doctor_cmd.py +82 -0
- kctl_glitchtip/commands/events.py +79 -0
- kctl_glitchtip/commands/health.py +200 -0
- kctl_glitchtip/commands/issues.py +175 -0
- kctl_glitchtip/commands/orgs.py +75 -0
- kctl_glitchtip/commands/projects.py +253 -0
- kctl_glitchtip/commands/skill_cmd.py +76 -0
- kctl_glitchtip/commands/teams.py +172 -0
- kctl_glitchtip/commands/uptime.py +166 -0
- kctl_glitchtip/commands/users.py +90 -0
- kctl_glitchtip/core/__init__.py +0 -0
- kctl_glitchtip/core/callbacks.py +30 -0
- kctl_glitchtip/core/client.py +106 -0
- kctl_glitchtip/core/config.py +120 -0
- kctl_glitchtip/core/exceptions.py +21 -0
- kctl_glitchtip/core/plugins.py +13 -0
- kctl_glitchtip-0.2.0.dist-info/METADATA +138 -0
- kctl_glitchtip-0.2.0.dist-info/RECORD +26 -0
- kctl_glitchtip-0.2.0.dist-info/WHEEL +4 -0
- kctl_glitchtip-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
kctl_glitchtip/cli.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Main CLI entry point for kctl-glitchtip."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from kctl_lib import KctlError, handle_cli_error
|
|
9
|
+
from kctl_lib.self_update import notify_if_outdated
|
|
10
|
+
|
|
11
|
+
from kctl_glitchtip import __version__
|
|
12
|
+
from kctl_glitchtip.commands.alerts import app as alerts_app
|
|
13
|
+
from kctl_glitchtip.commands.config_cmd import app as config_app
|
|
14
|
+
from kctl_glitchtip.commands.doctor_cmd import app as doctor_app
|
|
15
|
+
from kctl_glitchtip.commands.events import app as events_app
|
|
16
|
+
from kctl_glitchtip.commands.health import app as health_app
|
|
17
|
+
from kctl_glitchtip.commands.issues import app as issues_app
|
|
18
|
+
from kctl_glitchtip.commands.orgs import app as orgs_app
|
|
19
|
+
from kctl_glitchtip.commands.projects import app as projects_app
|
|
20
|
+
from kctl_glitchtip.commands.skill_cmd import app as skill_app
|
|
21
|
+
from kctl_glitchtip.commands.teams import app as teams_app
|
|
22
|
+
from kctl_glitchtip.commands.uptime import app as uptime_app
|
|
23
|
+
from kctl_glitchtip.commands.users import app as users_app
|
|
24
|
+
from kctl_glitchtip.core.callbacks import AppContext
|
|
25
|
+
from kctl_glitchtip.core.plugins import discover_and_load_plugins
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def version_callback(value: bool) -> None:
|
|
29
|
+
if value:
|
|
30
|
+
typer.echo(f"kctl-glitchtip {__version__}")
|
|
31
|
+
raise typer.Exit()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
app = typer.Typer(
|
|
35
|
+
name="kctl-glitchtip",
|
|
36
|
+
help="Kodemeio GlitchTip CLI - manage your GlitchTip error tracking platform.",
|
|
37
|
+
no_args_is_help=True,
|
|
38
|
+
rich_markup_mode="rich",
|
|
39
|
+
pretty_exceptions_enable=False,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.callback()
|
|
44
|
+
def main(
|
|
45
|
+
ctx: typer.Context,
|
|
46
|
+
json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
|
|
47
|
+
quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
|
|
48
|
+
profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
|
|
49
|
+
format: Annotated[str, typer.Option("--format", "-f", help="Output format: pretty, json, csv, yaml")] = "pretty",
|
|
50
|
+
no_header: Annotated[bool, typer.Option("--no-header", help="Omit header row in CSV output")] = False,
|
|
51
|
+
url: Annotated[str | None, typer.Option("--url", help="API URL override")] = None,
|
|
52
|
+
token: Annotated[str | None, typer.Option("--token", help="API token override")] = None,
|
|
53
|
+
version: Annotated[
|
|
54
|
+
bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True, help="Show version")
|
|
55
|
+
] = False,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Kodemeio GlitchTip CLI."""
|
|
58
|
+
ctx.ensure_object(dict)
|
|
59
|
+
ctx.obj = AppContext(
|
|
60
|
+
json_mode=json_output,
|
|
61
|
+
quiet=quiet,
|
|
62
|
+
profile=profile,
|
|
63
|
+
format=format,
|
|
64
|
+
no_header=no_header,
|
|
65
|
+
url_override=url,
|
|
66
|
+
token_override=token,
|
|
67
|
+
)
|
|
68
|
+
notify_if_outdated(ctx.obj.output, "kctl-glitchtip", __version__)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Register all command groups
|
|
72
|
+
app.add_typer(projects_app, name="projects")
|
|
73
|
+
app.add_typer(issues_app, name="issues")
|
|
74
|
+
app.add_typer(teams_app, name="teams")
|
|
75
|
+
app.add_typer(orgs_app, name="orgs")
|
|
76
|
+
app.add_typer(events_app, name="events")
|
|
77
|
+
app.add_typer(users_app, name="users")
|
|
78
|
+
app.add_typer(health_app, name="health")
|
|
79
|
+
app.add_typer(alerts_app, name="alerts")
|
|
80
|
+
app.add_typer(config_app, name="config")
|
|
81
|
+
app.add_typer(uptime_app, name="uptime")
|
|
82
|
+
app.add_typer(doctor_app, name="doctor")
|
|
83
|
+
app.add_typer(skill_app, name="skill", hidden=True)
|
|
84
|
+
|
|
85
|
+
# Load third-party plugins via entry points
|
|
86
|
+
discover_and_load_plugins(app)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.command("self-update")
|
|
90
|
+
def self_update_cmd(ctx: typer.Context) -> None:
|
|
91
|
+
"""Check for updates and upgrade kctl-glitchtip."""
|
|
92
|
+
actx = ctx.obj
|
|
93
|
+
out = actx.output
|
|
94
|
+
|
|
95
|
+
from kctl_lib.self_update import check_update
|
|
96
|
+
from kctl_lib.self_update import update as do_update
|
|
97
|
+
|
|
98
|
+
latest = check_update("kctl-glitchtip", __version__)
|
|
99
|
+
if latest:
|
|
100
|
+
out.info(f"Updating to {latest}...")
|
|
101
|
+
do_update("kctl-glitchtip")
|
|
102
|
+
out.success(f"Updated to {latest}")
|
|
103
|
+
else:
|
|
104
|
+
out.success("Already up to date")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.command()
|
|
108
|
+
def completions(
|
|
109
|
+
shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
|
|
110
|
+
install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Generate or install shell completions."""
|
|
113
|
+
from kctl_lib.completions import get_completion_script, install_completions
|
|
114
|
+
|
|
115
|
+
if install:
|
|
116
|
+
path = install_completions("kctl-glitchtip", shell)
|
|
117
|
+
if path:
|
|
118
|
+
typer.echo(f"Completions installed to {path}")
|
|
119
|
+
else:
|
|
120
|
+
typer.echo(f"Could not install completions for {shell}", err=True)
|
|
121
|
+
raise typer.Exit(code=1)
|
|
122
|
+
else:
|
|
123
|
+
script = get_completion_script("kctl-glitchtip", shell)
|
|
124
|
+
typer.echo(script)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _run() -> None:
|
|
128
|
+
"""Entry point with error handling."""
|
|
129
|
+
try:
|
|
130
|
+
app()
|
|
131
|
+
except KctlError as e:
|
|
132
|
+
handle_cli_error(e)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
_run()
|
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Alert and notification commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from kctl_glitchtip.core.callbacks import AppContext
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="Manage alerts and notifications.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command("list")
|
|
17
|
+
def list_(
|
|
18
|
+
ctx: typer.Context,
|
|
19
|
+
org_slug: Annotated[str, typer.Option("--org", help="Organization slug")],
|
|
20
|
+
project_slug: Annotated[str, typer.Option("--project", help="Project slug")],
|
|
21
|
+
) -> None:
|
|
22
|
+
"""List project alerts."""
|
|
23
|
+
actx: AppContext = ctx.obj
|
|
24
|
+
c, out = actx.client, actx.output
|
|
25
|
+
|
|
26
|
+
alerts = c.get_list(f"projects/{org_slug}/{project_slug}/alerts/")
|
|
27
|
+
|
|
28
|
+
rows: list[list[str]] = []
|
|
29
|
+
for a in alerts:
|
|
30
|
+
alert_id = str(a.get("id", ""))
|
|
31
|
+
name = a.get("name", "")
|
|
32
|
+
alert_type = a.get("alertType", a.get("type", ""))
|
|
33
|
+
quantity = str(a.get("quantity", ""))
|
|
34
|
+
timespan = str(a.get("timespanMinutes", a.get("timespan_minutes", "")))
|
|
35
|
+
rows.append([alert_id, name, alert_type, quantity, f"{timespan}m" if timespan else "-"])
|
|
36
|
+
|
|
37
|
+
out.table(
|
|
38
|
+
f"Alerts — {org_slug}/{project_slug}",
|
|
39
|
+
[("ID", "cyan"), ("Name", ""), ("Type", ""), ("Threshold", ""), ("Window", "dim")],
|
|
40
|
+
rows,
|
|
41
|
+
data_for_json=alerts,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command("test-alert")
|
|
46
|
+
def test_alert(
|
|
47
|
+
ctx: typer.Context,
|
|
48
|
+
org_slug: Annotated[str, typer.Option("--org", help="Organization slug")],
|
|
49
|
+
project_slug: Annotated[str, typer.Option("--project", help="Project slug")],
|
|
50
|
+
alert_id: Annotated[int, typer.Argument(help="Alert ID to test")],
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Test a project alert."""
|
|
53
|
+
actx: AppContext = ctx.obj
|
|
54
|
+
c, out = actx.client, actx.output
|
|
55
|
+
|
|
56
|
+
result = c.post(f"projects/{org_slug}/{project_slug}/alerts/{alert_id}/test/")
|
|
57
|
+
out.success(f"Test alert {alert_id} sent")
|
|
58
|
+
if out.json_mode:
|
|
59
|
+
out.raw_json(result)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.command("test-webhook")
|
|
63
|
+
def test_webhook(
|
|
64
|
+
ctx: typer.Context,
|
|
65
|
+
url: Annotated[str, typer.Argument(help="Webhook URL to test")],
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Send test alert to a webhook URL."""
|
|
68
|
+
actx: AppContext = ctx.obj
|
|
69
|
+
out = actx.output
|
|
70
|
+
|
|
71
|
+
payload = {
|
|
72
|
+
"text": "GlitchTip test alert from kctl-glitchtip",
|
|
73
|
+
"alias": "kctl-glitchtip-test",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
resp = httpx.post(url, json=payload, timeout=10)
|
|
78
|
+
if resp.status_code < 300:
|
|
79
|
+
out.success(f"Webhook responded: {resp.status_code}")
|
|
80
|
+
else:
|
|
81
|
+
out.error(f"Webhook failed: {resp.status_code} — {resp.text[:200]}")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
out.error(f"Webhook error: {e}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@app.command("test-email")
|
|
87
|
+
def test_email(
|
|
88
|
+
ctx: typer.Context,
|
|
89
|
+
to: Annotated[str | None, typer.Option("--to", help="Recipient email (default: admin)")] = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Send test email via Django (requires Docker access)."""
|
|
92
|
+
actx: AppContext = ctx.obj
|
|
93
|
+
out = actx.output
|
|
94
|
+
|
|
95
|
+
cmd = ["docker", "exec", "kodemeio-glitchtip", "./manage.py", "sendtestemail"]
|
|
96
|
+
if to:
|
|
97
|
+
cmd.append(to)
|
|
98
|
+
else:
|
|
99
|
+
cmd.append("--admin")
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
103
|
+
if result.returncode == 0:
|
|
104
|
+
out.success("Test email sent")
|
|
105
|
+
else:
|
|
106
|
+
out.error(f"Failed: {result.stderr.strip()}")
|
|
107
|
+
except FileNotFoundError:
|
|
108
|
+
out.error("Docker not found")
|
|
109
|
+
except subprocess.TimeoutExpired:
|
|
110
|
+
out.error("Email send timed out")
|