kctl-sentry 0.6.3__tar.gz

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.
Files changed (36) hide show
  1. kctl_sentry-0.6.3/.gitignore +10 -0
  2. kctl_sentry-0.6.3/PKG-INFO +17 -0
  3. kctl_sentry-0.6.3/README.md +117 -0
  4. kctl_sentry-0.6.3/pyproject.toml +45 -0
  5. kctl_sentry-0.6.3/skills/sentry-admin/SKILL.md +166 -0
  6. kctl_sentry-0.6.3/src/kctl_sentry/__init__.py +3 -0
  7. kctl_sentry-0.6.3/src/kctl_sentry/__main__.py +5 -0
  8. kctl_sentry-0.6.3/src/kctl_sentry/cli.py +136 -0
  9. kctl_sentry-0.6.3/src/kctl_sentry/commands/__init__.py +0 -0
  10. kctl_sentry-0.6.3/src/kctl_sentry/commands/alerts.py +176 -0
  11. kctl_sentry-0.6.3/src/kctl_sentry/commands/config_cmd.py +400 -0
  12. kctl_sentry-0.6.3/src/kctl_sentry/commands/dashboard.py +86 -0
  13. kctl_sentry-0.6.3/src/kctl_sentry/commands/doctor_cmd.py +57 -0
  14. kctl_sentry-0.6.3/src/kctl_sentry/commands/environments.py +51 -0
  15. kctl_sentry-0.6.3/src/kctl_sentry/commands/health.py +72 -0
  16. kctl_sentry-0.6.3/src/kctl_sentry/commands/issues.py +285 -0
  17. kctl_sentry-0.6.3/src/kctl_sentry/commands/projects.py +173 -0
  18. kctl_sentry-0.6.3/src/kctl_sentry/commands/releases.py +208 -0
  19. kctl_sentry-0.6.3/src/kctl_sentry/commands/skill_cmd.py +76 -0
  20. kctl_sentry-0.6.3/src/kctl_sentry/commands/stats.py +138 -0
  21. kctl_sentry-0.6.3/src/kctl_sentry/commands/teams.py +120 -0
  22. kctl_sentry-0.6.3/src/kctl_sentry/core/__init__.py +0 -0
  23. kctl_sentry-0.6.3/src/kctl_sentry/core/callbacks.py +33 -0
  24. kctl_sentry-0.6.3/src/kctl_sentry/core/client.py +84 -0
  25. kctl_sentry-0.6.3/src/kctl_sentry/core/config.py +122 -0
  26. kctl_sentry-0.6.3/src/kctl_sentry/core/exceptions.py +10 -0
  27. kctl_sentry-0.6.3/src/kctl_sentry/core/plugins.py +13 -0
  28. kctl_sentry-0.6.3/tests/conftest.py +50 -0
  29. kctl_sentry-0.6.3/tests/test_alerts.py +373 -0
  30. kctl_sentry-0.6.3/tests/test_client.py +61 -0
  31. kctl_sentry-0.6.3/tests/test_config.py +64 -0
  32. kctl_sentry-0.6.3/tests/test_health.py +23 -0
  33. kctl_sentry-0.6.3/tests/test_issues.py +72 -0
  34. kctl_sentry-0.6.3/tests/test_projects.py +34 -0
  35. kctl_sentry-0.6.3/tests/test_releases.py +36 -0
  36. kctl_sentry-0.6.3/tests/test_smoke.py +60 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .ruff_cache/
4
+ .mypy_cache/
5
+ .pytest_cache/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .venv/
10
+ .env
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: kctl-sentry
3
+ Version: 0.6.3
4
+ Summary: Kodemeio Sentry CLI — error tracking management
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: httpx>=0.28.0
7
+ Requires-Dist: kctl-lib>=0.8.0
8
+ Requires-Dist: pydantic>=2.10.0
9
+ Requires-Dist: pyyaml>=6.0.2
10
+ Requires-Dist: rich>=13.9.0
11
+ Requires-Dist: typer>=0.15.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: mypy>=1.14.0; extra == 'dev'
14
+ Requires-Dist: pytest-httpx>=0.35.0; extra == 'dev'
15
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.9.0; extra == 'dev'
17
+ Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
@@ -0,0 +1,117 @@
1
+ # kctl-sentry
2
+
3
+ Sentry error tracking CLI — error triage, release tracking, and project management for the Kodemeio platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # From workspace (development)
9
+ uv tool install --editable packages/kctl-sentry
10
+
11
+ # From PyPI
12
+ uv tool install kctl-sentry
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Initialize configuration
19
+ kctl-sentry config init
20
+
21
+ # Check connectivity
22
+ kctl-sentry health check
23
+
24
+ # List all projects
25
+ kctl-sentry projects list
26
+
27
+ # Triage open issues
28
+ kctl-sentry issues list --project my-project
29
+
30
+ # View error stats
31
+ kctl-sentry stats errors --project my-project
32
+ ```
33
+
34
+ ## Command Groups
35
+
36
+ | Group | Commands | Description |
37
+ |-------|----------|-------------|
38
+ | `config` | init, add, use, show, set, remove, profiles, current, test, migrate | Manage profiles and credentials |
39
+ | `health` | check | Verify Sentry API connectivity |
40
+ | `dashboard` | overview | High-level summary across all projects |
41
+ | `issues` | list, show, resolve, ignore, bulk-resolve, assign | Error issue triage and management |
42
+ | `projects` | list, show, dsn, create | Project listing, details, and DSN retrieval |
43
+ | `releases` | list, show, create, associate | Release tracking and commit association |
44
+ | `alerts` | list, show, create | Alert rule management |
45
+ | `stats` | events, errors | Event and error volume statistics |
46
+ | `teams` | list, show | Team membership and details |
47
+ | `environments` | list | Environment listing per project |
48
+
49
+ ## Global Options
50
+
51
+ All commands accept these options:
52
+
53
+ | Option | Short | Description |
54
+ |--------|-------|-------------|
55
+ | `--json` | | Output as JSON |
56
+ | `--format` | `-f` | Output format: `pretty`, `json`, `csv`, `yaml` |
57
+ | `--quiet` | `-q` | Suppress info messages |
58
+ | `--no-header` | | Omit header row in table/CSV output |
59
+ | `--profile` | `-p` | Config profile name |
60
+ | `--auth-token` | | Auth token override (bypasses config) |
61
+ | `--version` | `-V` | Show version and exit |
62
+
63
+ ## Configuration
64
+
65
+ Config is stored in `~/.config/kodemeio/config.yaml` under the `sentry` key.
66
+
67
+ ```bash
68
+ # Create a new profile
69
+ kctl-sentry config init
70
+
71
+ # Add a named profile
72
+ kctl-sentry config add --profile prod
73
+
74
+ # Switch active profile
75
+ kctl-sentry config use prod
76
+
77
+ # Show current config (secrets masked)
78
+ kctl-sentry config show
79
+
80
+ # Test connectivity
81
+ kctl-sentry config test
82
+ ```
83
+
84
+ ### Config Keys
85
+
86
+ | Key | Description |
87
+ |-----|-------------|
88
+ | `url` | Sentry instance URL (e.g. `https://sentry.io` or self-hosted) |
89
+ | `auth_token` | Sentry auth token with `project:read`, `event:read` scopes |
90
+ | `org` | Default organization slug |
91
+
92
+ ## Common Workflows
93
+
94
+ ```bash
95
+ # Bulk resolve all resolved issues in a project
96
+ kctl-sentry issues bulk-resolve --project my-project --status resolved
97
+
98
+ # Create a release and associate commits
99
+ kctl-sentry releases create --project my-project --version 1.2.3
100
+ kctl-sentry releases associate --project my-project --version 1.2.3
101
+
102
+ # Get DSN for SDK integration
103
+ kctl-sentry projects dsn my-project
104
+
105
+ # View event volume over the last 24h
106
+ kctl-sentry stats events --project my-project --period 24h
107
+ ```
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ cd packages/kctl-sentry
113
+ uv sync --all-extras
114
+ uv run pytest tests/ -v
115
+ uv run mypy src/
116
+ uv run ruff check src/
117
+ ```
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kctl-sentry"
7
+ version = "0.6.3"
8
+ description = "Kodemeio Sentry CLI — error tracking management"
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "kctl-lib>=0.8.0",
12
+ "typer>=0.15.0",
13
+ "rich>=13.9.0",
14
+ "pydantic>=2.10.0",
15
+ "pyyaml>=6.0.2",
16
+ "httpx>=0.28.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=8.3.0",
22
+ "pytest-httpx>=0.35.0",
23
+ "ruff>=0.9.0",
24
+ "mypy>=1.14.0",
25
+ "types-PyYAML>=6.0.0",
26
+ ]
27
+
28
+ [project.scripts]
29
+ kctl-sentry = "kctl_sentry.cli:_run"
30
+
31
+ [tool.uv.sources]
32
+ kctl-lib = { workspace = true }
33
+
34
+ [project.entry-points."kctl_sentry.plugins"]
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/kctl_sentry"]
38
+
39
+ [tool.ruff]
40
+ target-version = "py312"
41
+ line-length = 120
42
+
43
+ [tool.mypy]
44
+ python_version = "3.12"
45
+ strict = true
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: sentry-admin
3
+ description: >
4
+ Sentry error tracking administration via kctl-sentry CLI (11 groups, ~35 commands).
5
+ MUST use for ANY kctl-sentry operation.
6
+ Triggers on: "alerts", "assign", "associate", "bulk-resolve", "check", "config", "current", "dashboard", "deploy", "environments", "errors", "events", "generate", "health", "ignore", "init", "issues", "kctl-sentry", "migrate", "overview", "profile", "profiles", "projects", "releases", "remove", "resolve", "skill", "stats", "teams", "test".
7
+ Auto-generated: 2026-04-05
8
+ registry_hash: ff99415189e2
9
+ ---
10
+
11
+ # sentry-admin — kctl-sentry CLI Reference
12
+
13
+ > Auto-generated from `kctl-sentry` command registry. Do not edit manually.
14
+ > To regenerate: `kctl-sentry skill generate`
15
+ > To add custom content: edit `SKILL.extra.md` in the same directory.
16
+
17
+ ## Overview
18
+
19
+ **CLI:** `kctl-sentry`
20
+ **Command groups:** 11
21
+ **Total commands:** ~35
22
+ **Install:** `cd cli && uv tool install --editable .`
23
+
24
+ ## Global Options
25
+
26
+ | Flag | Description |
27
+ |------|-------------|
28
+ | `--json` | JSON output |
29
+ | `--quiet`, `-q` | Suppress info messages |
30
+ | `--format`, `-f` | Output format: pretty/json/csv/yaml |
31
+ | `--no-header` | Omit CSV header row |
32
+ | `--profile`, `-p` | Config profile name |
33
+ | `--version`, `-V` | Show version |
34
+
35
+ ## Command Reference
36
+
37
+ ### `kctl-sentry alerts`
38
+
39
+ Manage alert rules.
40
+
41
+ | Command | Description |
42
+ |---------|-------------|
43
+ | `alerts create <project> <name> [--metric] [--threshold] [--time_window]` | Create a new metric alert rule. |
44
+ | `alerts list [--project]` | List alert rules. |
45
+ | `alerts show <rule_id> <project>` | Show alert rule details and trigger history. |
46
+
47
+ ### `kctl-sentry config`
48
+
49
+ Manage CLI configuration and profiles.
50
+
51
+ | Command | Description |
52
+ |---------|-------------|
53
+ | `config add <name> [--auth_token] [--organization] [--url] [--default_project] [--set_default]` | Add or update a profile's Sentry connection. |
54
+ | `config current` | Show the active profile and connection status. |
55
+ | `config init [--auth_token] [--organization] [--url] [--default_project] [--name]` | Initialize CLI configuration. |
56
+ | `config migrate` | Migrate config from flat format to service-scoped format. |
57
+ | `config profiles` | List all profiles with Sentry connection status. |
58
+ | `config remove <name> [--force] [--service_only]` | Remove a profile or just its Sentry config. |
59
+ | `config set <key> <value> [--profile_arg]` | Set a configuration value for the current service. |
60
+ | `config show` | Show configuration. |
61
+ | `config test` | Test API connection. |
62
+ | `config use <name>` | Switch default profile. |
63
+
64
+ ### `kctl-sentry dashboard`
65
+
66
+ Quick overview of Sentry state.
67
+
68
+ | Command | Description |
69
+ |---------|-------------|
70
+ | `dashboard overview` | Show unresolved issues, recent releases, and alert status across projects. |
71
+
72
+ ### `kctl-sentry environments`
73
+
74
+ Manage project environments.
75
+
76
+ | Command | Description |
77
+ |---------|-------------|
78
+ | `environments list [--project]` | List environments for a project (e.g. |
79
+
80
+ ### `kctl-sentry health`
81
+
82
+ API connectivity checks.
83
+
84
+ | Command | Description |
85
+ |---------|-------------|
86
+ | `health check` | Check Sentry API connectivity, org info, and rate limits. |
87
+
88
+ ### `kctl-sentry issues`
89
+
90
+ Error triage — list, inspect, resolve, ignore, assign issues.
91
+
92
+ | Command | Description |
93
+ |---------|-------------|
94
+ | `issues assign <issue_id> <to>` | Assign an issue to a team member. |
95
+ | `issues bulk-resolve <project> [--before] [--force]` | Bulk-resolve old unresolved issues in a project. |
96
+ | `issues ignore <issue_id> [--duration] [--count]` | Ignore an issue, optionally for a duration or until N more events. |
97
+ | `issues list [--project] [--status] [--limit] [--sort]` | List recent issues for a project. |
98
+ | `issues resolve <issue_id> [--release]` | Resolve an issue. |
99
+ | `issues show <issue_id>` | Show issue details, stack trace, and affected users. |
100
+
101
+ ### `kctl-sentry projects`
102
+
103
+ Manage Sentry projects.
104
+
105
+ | Command | Description |
106
+ |---------|-------------|
107
+ | `projects create <name> <team> [--platform]` | Create a new project. |
108
+ | `projects dsn <slug>` | Get DSN key for SDK configuration. |
109
+ | `projects list` | List all projects with issue counts. |
110
+ | `projects show <slug>` | Show project details. |
111
+
112
+ ### `kctl-sentry releases`
113
+
114
+ Manage releases and deploy tracking.
115
+
116
+ | Command | Description |
117
+ |---------|-------------|
118
+ | `releases associate <version> <commits>` | Associate commits with a release for tracking regressions. |
119
+ | `releases create <version> <project>` | Create a new release for a project. |
120
+ | `releases list [--project] [--limit]` | List recent releases. |
121
+ | `releases show <version>` | Show release details and associated issues. |
122
+
123
+ ### `kctl-sentry skill`
124
+
125
+ Claude Code skill management.
126
+
127
+ | Command | Description |
128
+ |---------|-------------|
129
+ | `skill generate [--output] [--install] [--check]` | Auto-generate SKILL.md from CLI command registry. |
130
+
131
+ **Examples:**
132
+ ```bash
133
+ kctl-sentry skill generate
134
+ kctl-sentry skill generate --install
135
+ kctl-sentry skill generate --check
136
+ ```
137
+
138
+ ### `kctl-sentry stats`
139
+
140
+ Event and error statistics.
141
+
142
+ | Command | Description |
143
+ |---------|-------------|
144
+ | `stats errors [--project] [--period]` | Show error rate trends for a project. |
145
+ | `stats events [--project] [--period]` | Show event volume for a project or organization. |
146
+
147
+ ### `kctl-sentry teams`
148
+
149
+ Manage teams.
150
+
151
+ | Command | Description |
152
+ |---------|-------------|
153
+ | `teams list` | List all teams in the organization. |
154
+ | `teams show <slug>` | Show team details, members, and assigned projects. |
155
+
156
+ ## Configuration
157
+
158
+ Shared config: `~/.config/kodemeio/config.yaml`
159
+
160
+ ```bash
161
+ kctl-sentry config init # Interactive setup
162
+ kctl-sentry config show # Show current config
163
+ kctl-sentry config profiles # List profiles
164
+ kctl-sentry config current # Show active profile
165
+ kctl-sentry config validate # Verify config
166
+ ```
@@ -0,0 +1,3 @@
1
+ """kctl-sentry: Sentry error tracking management."""
2
+
3
+ __version__ = "0.6.3"
@@ -0,0 +1,5 @@
1
+ """Allow running as python -m kctl_sentry."""
2
+
3
+ from kctl_sentry.cli import _run
4
+
5
+ _run()
@@ -0,0 +1,136 @@
1
+ """Main CLI entry point for kctl-sentry."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from kctl_lib import KctlError, handle_cli_error
10
+
11
+ from kctl_sentry import __version__
12
+ from kctl_sentry.commands.alerts import app as alerts_app
13
+ from kctl_sentry.commands.config_cmd import app as config_app
14
+ from kctl_sentry.commands.dashboard import app as dashboard_app
15
+ from kctl_sentry.commands.environments import app as environments_app
16
+ from kctl_sentry.commands.health import app as health_app
17
+ from kctl_sentry.commands.issues import app as issues_app
18
+ from kctl_sentry.commands.projects import app as projects_app
19
+ from kctl_sentry.commands.releases import app as releases_app
20
+ from kctl_sentry.commands.stats import app as stats_app
21
+ from kctl_sentry.commands.teams import app as teams_app
22
+ from kctl_sentry.core.callbacks import AppContext
23
+ from kctl_sentry.core.plugins import discover_and_load_plugins
24
+ from kctl_sentry.commands.skill_cmd import app as skill_app
25
+ from kctl_sentry.commands.doctor_cmd import app as doctor_app
26
+ from kctl_lib.self_update import notify_if_outdated
27
+ from kctl_lib.tui import add_tui_command
28
+
29
+
30
+ def version_callback(value: bool) -> None:
31
+ if value:
32
+ typer.echo(f"kctl-sentry {__version__}")
33
+ raise typer.Exit()
34
+
35
+
36
+ app = typer.Typer(
37
+ name="kctl-sentry",
38
+ help="Kodemeio Sentry CLI — error triage, release tracking, and project management.",
39
+ no_args_is_help=True,
40
+ rich_markup_mode="rich",
41
+ pretty_exceptions_enable=False,
42
+ )
43
+
44
+
45
+ @app.callback()
46
+ def main(
47
+ ctx: typer.Context,
48
+ json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
49
+ quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
50
+ profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
51
+ format: Annotated[str, typer.Option("--format", "-f", help="Output format: pretty, json, csv, yaml")] = "pretty",
52
+ no_header: Annotated[bool, typer.Option("--no-header", help="Omit header row in CSV output")] = False,
53
+ auth_token: Annotated[str | None, typer.Option("--auth-token", help="Auth token override")] = None,
54
+ version: Annotated[
55
+ bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True, help="Show version")
56
+ ] = False,
57
+ ) -> None:
58
+ """Kodemeio Sentry CLI."""
59
+ ctx.ensure_object(dict)
60
+ ctx.obj = AppContext(
61
+ json_mode=json_output,
62
+ quiet=quiet,
63
+ profile=profile,
64
+ format=format,
65
+ no_header=no_header,
66
+ auth_token_override=auth_token,
67
+ )
68
+ notify_if_outdated(ctx.obj.output, "kctl-sentry", __version__)
69
+
70
+
71
+ app.add_typer(config_app, name="config")
72
+ app.add_typer(health_app, name="health")
73
+ app.add_typer(dashboard_app, name="dashboard")
74
+ app.add_typer(issues_app, name="issues")
75
+ app.add_typer(projects_app, name="projects")
76
+ app.add_typer(releases_app, name="releases")
77
+ app.add_typer(alerts_app, name="alerts")
78
+ app.add_typer(stats_app, name="stats")
79
+ app.add_typer(teams_app, name="teams")
80
+ app.add_typer(environments_app, name="environments")
81
+ app.add_typer(skill_app, name="skill", hidden=True)
82
+ app.add_typer(doctor_app, name="doctor")
83
+
84
+ # Load third-party plugins via entry points
85
+ discover_and_load_plugins(app)
86
+ add_tui_command(app, service_key="sentry", version=__version__)
87
+
88
+
89
+ @app.command("self-update")
90
+ def self_update_cmd(ctx: typer.Context) -> None:
91
+ """Check for updates and upgrade kctl-sentry."""
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-sentry", __version__)
99
+ if latest:
100
+ out.info(f"Updating to {latest}...")
101
+ do_update("kctl-sentry")
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-sentry", 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-sentry", 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,176 @@
1
+ """Alert rule management commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+ from kctl_sentry.core.callbacks import AppContext
10
+ from kctl_sentry.core.exceptions import KctlError
11
+
12
+ app = typer.Typer(help="Manage alert rules.")
13
+
14
+
15
+ @app.command("list")
16
+ def list_(
17
+ ctx: typer.Context,
18
+ project: Annotated[str | None, typer.Option("--project", "-p", help="Filter by project slug")] = None,
19
+ ) -> None:
20
+ """List alert rules."""
21
+ c: AppContext = ctx.obj
22
+ out = c.output
23
+
24
+ try:
25
+ rules = c.client.project_get(project, "/rules/") if project else c.client.org_get("/alert-rules/")
26
+ if not isinstance(rules, list):
27
+ rules = []
28
+
29
+ rows: list[list[str]] = []
30
+ for rule in rules:
31
+ rule_id = str(rule.get("id", ""))
32
+ name = (rule.get("name", "") or "")[:50]
33
+ # Project-scoped rules have 'projects', org-scoped have 'project'
34
+ proj_info = ""
35
+ if isinstance(rule.get("projects"), list):
36
+ proj_info = ", ".join(rule["projects"])
37
+ elif isinstance(rule.get("project"), dict):
38
+ proj_info = rule["project"].get("slug", "")
39
+
40
+ status_val = rule.get("status", "active")
41
+ owner = ""
42
+ if isinstance(rule.get("owner"), dict):
43
+ owner = rule["owner"].get("name", "")
44
+ elif isinstance(rule.get("owner"), str):
45
+ owner = rule["owner"]
46
+
47
+ rows.append([rule_id, name, proj_info, status_val, owner])
48
+
49
+ out.table(
50
+ "Alert Rules",
51
+ [
52
+ ("ID", "cyan"),
53
+ ("Name", ""),
54
+ ("Project", ""),
55
+ ("Status", "green"),
56
+ ("Owner", "dim"),
57
+ ],
58
+ rows,
59
+ data_for_json=rules,
60
+ )
61
+ except KctlError as e:
62
+ out.error(f"Failed to list alerts: {e}")
63
+ raise typer.Exit(1) from e
64
+
65
+
66
+ @app.command("show")
67
+ def show(
68
+ ctx: typer.Context,
69
+ rule_id: Annotated[str, typer.Argument(help="Alert rule ID")],
70
+ project: Annotated[str, typer.Option("--project", "-p", help="Project slug")],
71
+ ) -> None:
72
+ """Show alert rule details and trigger history."""
73
+ c: AppContext = ctx.obj
74
+ out = c.output
75
+
76
+ try:
77
+ rule = c.client.project_get(project, f"/rules/{rule_id}/")
78
+ if not isinstance(rule, dict):
79
+ rule = {}
80
+
81
+ # Parse conditions
82
+ conditions = rule.get("conditions", [])
83
+ condition_strs: list[tuple[str, str]] = []
84
+ for cond in conditions if isinstance(conditions, list) else []:
85
+ if isinstance(cond, dict):
86
+ condition_strs.append(("Condition", cond.get("name", str(cond.get("id", "")))))
87
+
88
+ # Parse actions
89
+ actions = rule.get("actions", [])
90
+ action_strs: list[tuple[str, str]] = []
91
+ for act in actions if isinstance(actions, list) else []:
92
+ if isinstance(act, dict):
93
+ action_strs.append(("Action", act.get("name", str(act.get("id", "")))))
94
+
95
+ sections = [
96
+ (
97
+ "Alert Rule",
98
+ [
99
+ ("ID", str(rule.get("id", ""))),
100
+ ("Name", rule.get("name", "")),
101
+ ("Status", rule.get("status", "")),
102
+ ("Frequency", f"{rule.get('frequency', '')} seconds"),
103
+ ("Date created", (rule.get("dateCreated", "") or "")[:19]),
104
+ ],
105
+ ),
106
+ ]
107
+
108
+ if condition_strs:
109
+ sections.append(("Conditions", condition_strs))
110
+ if action_strs:
111
+ sections.append(("Actions", action_strs))
112
+
113
+ out.detail(
114
+ f"Alert Rule: {rule.get('name', rule_id)}",
115
+ sections,
116
+ data_for_json=rule,
117
+ )
118
+ except KctlError as e:
119
+ out.error(f"Failed to show alert: {e}")
120
+ raise typer.Exit(1) from e
121
+
122
+
123
+ @app.command("create")
124
+ def create(
125
+ ctx: typer.Context,
126
+ project: Annotated[str, typer.Option("--project", "-p", help="Project slug")],
127
+ name: Annotated[str, typer.Option("--name", "-n", help="Alert rule name")],
128
+ metric: Annotated[str, typer.Option("--metric", help="Metric: events, users")] = "events",
129
+ threshold: Annotated[int, typer.Option("--threshold", help="Threshold value")] = 100,
130
+ time_window: Annotated[int, typer.Option("--time-window", help="Time window in minutes")] = 60,
131
+ ) -> None:
132
+ """Create a new metric alert rule."""
133
+ c: AppContext = ctx.obj
134
+ out = c.output
135
+
136
+ try:
137
+ # Map metric to Sentry's aggregate format
138
+ aggregate_map = {
139
+ "events": "count()",
140
+ "users": "count_unique(user)",
141
+ }
142
+ aggregate = aggregate_map.get(metric, "count()")
143
+
144
+ payload = {
145
+ "name": name,
146
+ "aggregate": aggregate,
147
+ "timeWindow": time_window,
148
+ "dataset": "events",
149
+ "query": "",
150
+ "thresholdType": 0, # Above threshold
151
+ "resolveThreshold": None,
152
+ "triggers": [
153
+ {
154
+ "label": "critical",
155
+ "alertThreshold": threshold,
156
+ "actions": [],
157
+ }
158
+ ],
159
+ "projects": [project],
160
+ "owner": None,
161
+ }
162
+
163
+ result = c.client.org_post("/alert-rules/", json=payload)
164
+ if not isinstance(result, dict):
165
+ result = {}
166
+
167
+ if out.json_mode:
168
+ out.raw_json(result)
169
+ else:
170
+ out.success(f"Alert rule created: {result.get('name', name)}")
171
+ out.kv("ID", str(result.get("id", "")))
172
+ out.kv("Metric", aggregate)
173
+ out.kv("Threshold", str(threshold))
174
+ except KctlError as e:
175
+ out.error(f"Failed to create alert: {e}")
176
+ raise typer.Exit(1) from e