Metadata-Version: 2.4
Name: jiracommandkit
Version: 1.0.0
Summary: A Jira REST API toolkit for querying, creating, and managing tickets from Python or the CLI
Author-email: Leon Denard <ltdenard@denard.me>
License: BSD-3-Clause
Keywords: jira,atlassian,cli,api,ticketing
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Bug Tracking
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Dynamic: license-file

# jiracommandkit

A Jira REST API toolkit for querying, creating, and managing tickets — usable
as a Python library or as a `jiracommandkit` command-line tool.

Supports Jira Cloud's v3 issues API and the Service Desk API: JQL search,
ticket creation (service-desk and regular projects), comments, attachments,
label management, and mandatory-field discovery for projects with required
custom fields.

## Installation

```bash
pip install jiracommandkit
```

For local development, install from a clone in editable mode:

```bash
pip install -e ".[dev]"
```

## Configuration

`jiracommandkit` reads credentials and settings in this order:

1. A `JIRACOMMANDKIT_<KEY>` environment variable
2. A value saved in `~/.config/jiracommandkit/jiracommandkit.json`
3. A caller-supplied default (`None` if not given)

| Key                             | Required | Description                                                                 |
|----------------------------------|:--------:|-------------------------------------------------------------------------------|
| `JIRA_USERNAME`                  | yes      | Jira account email/username.                                                  |
| `JIRA_API_KEY`                   | yes      | Jira API token.                                                               |
| `JIRA_BASE_URL`                  | yes      | Base URL, e.g. `https://yourcompany.atlassian.net`.                           |
| `JIRA_SERVICE_DESK_ID`           | no       | Default service desk ID used by `create_ticket`.                              |
| `JIRA_REQUEST_TYPE_ID`           | no       | Default request type ID used by `create_ticket`.                              |
| `JIRA_SECURITY_LABELS`           | no       | Comma-separated labels applied by `create_security_ticket`.                   |
| `JIRA_MANDATORY_FIELDS_FILE`     | no       | Path to a JSON file of per-project required fields (see below).               |

### Setting credentials via environment variables

```bash
export JIRACOMMANDKIT_JIRA_USERNAME="you@example.com"
export JIRACOMMANDKIT_JIRA_API_KEY="your-api-token"
export JIRACOMMANDKIT_JIRA_BASE_URL="https://yourcompany.atlassian.net"
```

### Setting credentials via the CLI (writes to `~/.config/jiracommandkit/jiracommandkit.json`)

```bash
jiracommandkit config set JIRA_USERNAME you@example.com
jiracommandkit config set JIRA_API_KEY your-api-token
jiracommandkit config set JIRA_BASE_URL https://yourcompany.atlassian.net

jiracommandkit config list        # list saved keys
jiracommandkit config get JIRA_USERNAME
```

## CLI usage

### Search tickets with JQL

```bash
jiracommandkit search "project = ENG AND status = Open"
jiracommandkit search "project = ENG" --format json
jiracommandkit search "project = ENG" --fields summary status assignee --max-results 50
```

### Get a single ticket

```bash
jiracommandkit get ENG-123
jiracommandkit get ENG-123 --format json
```

### Comments

```bash
jiracommandkit comment add ENG-123 "Investigating now."
jiracommandkit comment add ENG-123 "Internal note" --internal
jiracommandkit comment list ENG-123
```

### Labels

```bash
jiracommandkit label ENG-123 security backend        # overwrite the labels field
jiracommandkit label ENG-123 security --merge         # merge into existing labels
```

### Update a field

```bash
jiracommandkit set-field ENG-123 summary "Updated summary text"
jiracommandkit set-field ENG-123 priority '{"id": "2"}'
```

### Attachments

```bash
jiracommandkit attach ENG-123 ./screenshot.png
```

### Create a ticket

```bash
# Service desk ticket, using JIRACOMMANDKIT_JIRA_SERVICE_DESK_ID / JIRACOMMANDKIT_JIRA_REQUEST_TYPE_ID
jiracommandkit create --summary "Printer on fire" --description "Send help" --priority 2

# Regular (non-service-desk) project ticket
jiracommandkit create --project ENG --issue-type Bug \
    --summary "Null pointer on checkout" \
    --description "Crashes when cart is empty" \
    --field customfield_10005='"Backend"'

# Override the service desk / request type for one call
jiracommandkit create --service-desk-id 4 --request-type-id 12 \
    --summary "New laptop" --description "Onboarding request"
```

### Create a security ticket

Creates a ticket and applies `JIRACOMMANDKIT_JIRA_SECURITY_LABELS` to it:

```bash
jiracommandkit create-security \
    --summary "Suspicious login activity" \
    --description "Multiple failed logins from unfamiliar IP" \
    --priority 1 \
    --attachment ./evidence.log
```

### Discover required fields for your Jira instance

Queries every service desk and project you have access to, and writes a
starter JSON file listing required custom fields and their selectable values:

```bash
jiracommandkit discover-fields ~/.config/jiracommandkit/mandatory_fields.json
jiracommandkit config set JIRA_MANDATORY_FIELDS_FILE ~/.config/jiracommandkit/mandatory_fields.json
```

Edit the generated file to pick one `requestTypeId` (service desk projects)
or `issueType` (regular projects) per project, and a single value for each
required custom field. Once configured, `create_ticket`/`create-security`
calls with a matching `--project` automatically include those fields:

```json
{
    "SEC": {
        "serviceDeskId": 2,
        "requestTypeId": 36,
        "customfield_10001": {"value": "Security"}
    },
    "ENG": {
        "issueType": "Bug",
        "customfield_10005": "Backend"
    }
}
```

## Library usage

Every CLI command has a corresponding method on `jiracommandkit.JiraClient`, so you
can use jiracommandkit directly in other Python projects without shelling out.

```python
from jiracommandkit import JiraClient

client = JiraClient()  # reads JIRACOMMANDKIT_JIRA_* env vars / jiracommandkit.json
```

### Search

```python
issues = client.query_jql("project = ENG AND status = Open", max_results=100)

tickets_by_project = client.format_tickets(issues)
for ticket in tickets_by_project["ENG"]:
    print(ticket.key, ticket.status, ticket.summary)
```

### Get a ticket

```python
issue = client.get_jira_by_id("ENG-123")
```

### Comments

```python
client.add_comment_to_ticket("ENG-123", "Investigating now.", public=True)
comments = client.get_comments_for_ticket("ENG-123")
```

### Labels

```python
client.set_custom_labels("ENG-123", "labels", ["security", "backend"])   # overwrite
client.add_labels("ENG-123", ["security"])                                # merge
```

### Update a field

```python
client.edit_ticket_field("ENG-123", "summary", "Updated summary text")
```

### Attachments

```python
client.add_attachment_to_ticket("ENG-123", "./screenshot.png")
```

### Create a ticket

```python
ticket_key, ticket_id = client.create_ticket(
    summary="Null pointer on checkout",
    desc="Crashes when cart is empty",
    project_key="ENG",
    issue_type="Bug",
    extra_fields={"customfield_10005": "Backend"},
)
```

### Create a security ticket

```python
ticket_key, ticket_id = client.create_security_ticket(
    summary="Suspicious login activity",
    desc="Multiple failed logins from unfamiliar IP",
    priority=1,
    attachment_path="./evidence.log",
)
```

### Discover mandatory fields

```python
mapping = client.discover_mandatory_fields("~/.config/jiracommandkit/mandatory_fields.json")
```

### Working with configuration directly

```python
from jiracommandkit import JiraConfig

config = JiraConfig()
config.save_api_key("JIRA_USERNAME", "you@example.com")
username = config.get_api_key("JIRA_USERNAME")
```

### Handling errors

```python
from jiracommandkit import JiraClient, JiraConfigError, JiraAPIError

try:
    client = JiraClient()
    issues = client.query_jql("project = ENG")
except JiraConfigError as e:
    print(f"Missing configuration: {e}")
except JiraAPIError as e:
    print(f"Jira API request failed: {e}")
```

## Data models

`JiraTicket` and `JiraComment` (in `jiracommandkit.models`) are plain dataclasses
returned by `JiraClient.format_tickets()` / `JiraTicket.from_api()`:

```python
from jiracommandkit import JiraTicket

ticket = JiraTicket.from_api(issue)  # issue: a raw dict from the Jira API
ticket.key            # "ENG-123"
ticket.project         # "ENG"
ticket.summary
ticket.priority
ticket.assignee
ticket.status
ticket.description      # HTML-escaped, newlines converted to <br>
ticket.comments         # list[JiraComment], newest first, capped at 5
ticket.comments_as_html()
ticket.to_dict()
```

## Development

```bash
pip install -e ".[dev]"
pytest
```

### Building and publishing to PyPI

```bash
python -m pip install --upgrade build twine
python -m build                       # produces dist/*.whl and dist/*.tar.gz
python -m twine upload --repository testpypi dist/*   # verify on TestPyPI first
python -m twine upload dist/*                          # publish to PyPI
git tag v<version>
git push origin v<version>
```

## License

BSD-3-Clause. See [LICENSE](LICENSE).
