Metadata-Version: 2.4
Name: jirasify
Version: 2.7.3
Summary: Terminal UI for Jira worklogs and status transitions
Author-email: Jakob Holst <code@jakobholst.dk>
License: MIT
Project-URL: Homepage, https://gitlab.com/jakobholst/jaho_local_bin
Project-URL: Repository, https://gitlab.com/jakobholst/jaho_local_bin
Keywords: jira,tui,worklog,textual,terminal
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development
Classifier: Topic :: Utilities
Requires-Python: >=3.14
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28
Requires-Dist: textual>=0.50
Requires-Dist: pyyaml>=6.0

# jirasify

Terminal UI for logging work and moving Jira issues, built with [Textual](https://textual.textualize.io/).

The distribution name on PyPI is `jirasify`; the main CLI command is `jirasify-tui`; the importable Python module is `jiratui`.

## Install

```bash
pip install jirasify
```

Each command (`jirasify`, `jirasify-tui`, `jirasify-list`) has a `--version`
flag that prints the installed version and a bundled changelog.

## Configure

On first run, `jirasify-tui` writes a template config to `~/.local/etc/jiratui/config.yaml` and prints a usage page. Edit the file (or answer the interactive prompt to fill it) with your Jira URL, project key, and username.

Your Jira token is read from the environment variable named by `authentication.token_env` (default: `JIRATOKEN`):

```bash
export JIRATOKEN=your_token
```

- **Self-hosted Jira (Data Center / Server)**: use a **Personal Access Token**. It is sent as `Authorization: Bearer <token>`.
- **Atlassian Cloud** (URL contains `atlassian.net`): use an **API token** from your Atlassian account. It is combined with `assignee.username` (your account email) via HTTP Basic auth.

Both hierarchy conventions are supported: Data Center's *Epic Link* custom
field, and Cloud's standard *parent* field for the Epic → Story link. The
subtask issue type name is auto-discovered per project (Cloud team-managed
projects use `Subtask`; most others use `Sub-task`).

The Sprint / Epic Link / Epic Name / Parent Link custom fields are matched
first by display name and then by their Greenhopper `schema.custom` value,
so localized or renamed installations still resolve. The enhanced JQL
endpoint (`POST /rest/api/2/search/jql`) is tried first and the client
falls back to the classic `/rest/api/2/search` on any of 400/404/405/501,
so old Data Center instances that answer differently still work.

Verify connectivity before launching the TUI:

```bash
jirasify-tui --test
```

Prints the configured URL and token env var, then hits `/rest/api/2/myself`. Exits with a non-zero status on missing config, unset token, or auth failure.

## Usage

```bash
jirasify-tui
```

| Key      | Action                            |
| -------- | --------------------------------- |
| `l`      | Log view                          |
| `o`      | Overview                          |
| `r`      | Monthly report                    |
| `t`/`w`  | Today's / this week's report      |
| `m`      | This month's report               |
| `s`      | Change status of selected issue   |
| `e`      | Set estimate on selected issue    |
| `h`      | Help                              |
| `Ctrl+R` | Refresh                           |
| `q`      | Quit                              |

Time input accepts `30m`, `1h`, `2h30m`, `1d` (a day = 8h).

## `jirasify`

`pip install jirasify` also installs a `jirasify` command that turns a
structured Markdown file into Jira user stories using the same
`~/.local/etc/jiratui/config.yaml` and `JIRATOKEN` env var as `jirasify-tui`.

Expected Markdown layout:

```markdown
# My Epic Name - User Stories

## 1. Project Overview

Free text.

---

# Phase 1 - Foundations

## User Story 001 - Inventory Environment

**Estimate:** 15 h

> As a developer, I will document ... so that ...

### Scope
- Item A
- Item B

### Substories
- Break out first sub-piece
- Break out second sub-piece

### Acceptance Criteria
- Criterion 1
- Criterion 2
```

- The top-level `# ...` heading (any H1 that is not a `# Phase ...`) is used
  as the Epic name. A trailing ` - User Stories` suffix is stripped.
- Stories are only picked up under a `## User Story ...` heading inside a
  `# Phase ...` section.
- Recognised subsections: `Scope`, `Validate`, `Include`, `Examples`,
  `Substories`, `Acceptance Criteria`.
- Each `### Substories` bullet becomes a **Sub-task** in Jira under the
  parent story. Substories are excluded from the story's Jira
  description so they aren't duplicated.
- A `**Tags:** foo, bar, baz` line anywhere in a user story block adds
  extra Jira labels (spaces replaced with `_`). Combined with the
  auto-generated `Phase_...` label on every created issue.

### Commands

```bash
jirasify --example                              # write ./example.md (plain markdown starter)
jirasify --template --file stories.md.j2        # write an Ansible/Jinja2 template

jirasify --file stories.md                      # parse + write .jirastories.state.json
jirasify --file stories.md --create             # ensure Epic exists, create missing stories linked to it (idempotent)
jirasify --file stories.md --status             # key/status/assignee/epic/estimate/used per story, grouped by phase with totals
jirasify --file stories.md --epic VOS-100       # relink stories to a specific epic
jirasify --file stories.md --epic VOS-100 --story VOS-142   # relink a single story
jirasify --file stories.md --link-epic          # relink recorded stories to the Epic named by the H1 (creates it if missing)
jirasify --file stories.md --tag                # add Phase_ and **Tags:** labels to already-created stories/substories (union)
jirasify --file stories.md --delete             # delete every recorded issue (prompts unless --yes)

jirasify --reverse VOS-1234                     # print a markdown reconstruction of an Epic and its stories
jirasify --reverse VOS-1234 --save              # write it to <Epic_Name>.md (spaces -> _)
```

`--create` skips stories that already have an `issue_key` recorded in
`.jirastories.state.json`, and also skips summaries that already exist in the
target project, so re-runs won't duplicate issues. If no Epic with the H1's
name exists, one is created before the stories. Substories are handled the
same way — each story tracks its `sub_keys` map, and existing subtasks are
adopted by summary before creating new ones.

`--reverse` also emits each fetched story's subtasks as a `### Substories`
block, so round-tripping `--create` then `--reverse` preserves the
sub-structure.

Each created story (and its substories) is tagged with a Jira label derived
from its markdown phase — spaces are replaced with underscores, e.g.
`Phase_1_-_Foundations`. This lets `jirasify-list --epics --full` render a
phase level in its tree; the labels are inert otherwise.

Any `**Tags:** ...` line in a user story adds additional labels to that
story (and its substories). `--tag` performs a label alignment on already-
created issues — it unions the phase label and markdown tags onto each
recorded issue via an atomic PUT update op, so any Jira-side labels you
added manually are preserved.

`--reverse` reads back the labels and emits one `# Phase N - ...` heading
per phase, and a `**Tags:** ...` line under stories that have non-phase
labels — so create → reverse round-trips the tag structure.

`--delete` undoes `--create` for the current state file: it lists the
recorded stories and substories, prompts for confirmation (skip with
`--yes`), then deletes each in Jira (substories first). The epic itself
is left alone — remove it in the Jira UI if you want.

### Ansible / Jinja2 template

`--template` emits a `.j2` scaffold expecting these variables:

- `epic_name` (string)
- `project_overview` (string, optional)
- `phases`: list of `{ name, stories: [ { id, summary, estimate, description, scope?, acceptance_criteria? } ] }`

Render it with `ansible.builtin.template` (or `jinja2.Template`) to produce
a `stories.md`, then run `jirasify --file stories.md --create`.

## `jirasify-list`

`pip install jirasify` also installs a `jirasify-list` command for read-only
listing of Jira objects, using the same config as `jirasify-tui`.

```bash
jirasify-list --epics                     # Epics in jira.project (config), excluding Cancelled and Done
jirasify-list --epics --key VOS           # override the project key
```

### Status filters (mutually exclusive)

```bash
jirasify-list --epics --active            # status = Implementing
jirasify-list --epics --planning          # status = Planning
jirasify-list --epics --done              # status = Done
jirasify-list --epics --cancelled         # status = Cancelled
```

Without any of these flags, Cancelled and Done epics are hidden by default.

### Parent Link filter

```bash
jirasify-list --epics --parent VOS-100    # exact parent (JQL: "Parent Link" = VOS-100)
jirasify-list --epics --parent VOS        # any parent whose key starts with VOS-
```

### Full hierarchy

```bash
jirasify-list --epics --full              # also show stories under each epic and subtasks under each story
```

`--full` issues one extra batched JQL for all epic children (`"Epic Link" in (…)`)
and one for all subtasks (`parent in (…)`), regardless of item count.

### Output

Epics are grouped by their Parent Link and printed as a tree with box-drawing
branches (`├──`, `└──`, `│`):

```
VOS-100  Digital Platform Modernization
├── VOS-215      Implementing         Jane Doe                          Artifactory PyPI
│   ├── Phase 1 - Prepare Artifactory
│   │   ├── VOS-231    In Progress          Bob                               Provision PyPI local repo
│   │   │   └── VOS-232  To Do                Alice                             Configure retention
│   │   └── VOS-233    To Do                Bob                               Assemble virtual PyPI
│   └── Phase 2 - POC Publishing Pipeline
│       └── VOS-241    To Do                Alice                             Build POC publishing pipeline
└── VOS-220      To Do                Bob                               Sisyphos Onboarding
    └── (no phase)
        └── VOS-260    To Do                Alice                             Ad-hoc story

VOS-105  Infrastructure 2026
└── VOS-311      Implementing         Alice                             VMware 9.1 Enablement

(no parent)
└── VOS-999      Planning             Unassigned                        Ad-hoc Epic
```

Columns per row: issue key, status, assignee (truncated to 32 chars with `…`),
and the label — Epic Name for epics (falls back to summary when the Epic Name
custom field is absent), summary for stories and subtasks. Without `--full`
only the epic level is shown; with `--full`, stories are grouped under a phase
row derived from the story's `Phase_...` Jira label (falls back to `(no phase)`
when a story has no such label — typical for stories created outside `jirasify`
or before phase-labelling was added).

Rows are colorized when stdout is a terminal: cyan parent header and phase,
magenta epic, green story, blue subtask. Piping or redirecting produces plain
output.
