jira-as 1.0.0__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.
- jira_as-1.0.0/.gitignore +76 -0
- jira_as-1.0.0/LICENSE +21 -0
- jira_as-1.0.0/PKG-INFO +281 -0
- jira_as-1.0.0/README.md +236 -0
- jira_as-1.0.0/pyproject.toml +221 -0
- jira_as-1.0.0/src/jira_as/__init__.py +393 -0
- jira_as-1.0.0/src/jira_as/adf_helper.py +482 -0
- jira_as-1.0.0/src/jira_as/autocomplete_cache.py +365 -0
- jira_as-1.0.0/src/jira_as/automation_client.py +673 -0
- jira_as-1.0.0/src/jira_as/batch_processor.py +142 -0
- jira_as-1.0.0/src/jira_as/cache.py +471 -0
- jira_as-1.0.0/src/jira_as/cli/__init__.py +0 -0
- jira_as-1.0.0/src/jira_as/cli/cli_utils.py +306 -0
- jira_as-1.0.0/src/jira_as/cli/commands/__init__.py +0 -0
- jira_as-1.0.0/src/jira_as/cli/commands/admin_cmds.py +3484 -0
- jira_as-1.0.0/src/jira_as/cli/commands/agile_cmds.py +2033 -0
- jira_as-1.0.0/src/jira_as/cli/commands/bulk_cmds.py +1302 -0
- jira_as-1.0.0/src/jira_as/cli/commands/collaborate_cmds.py +1330 -0
- jira_as-1.0.0/src/jira_as/cli/commands/dev_cmds.py +1070 -0
- jira_as-1.0.0/src/jira_as/cli/commands/fields_cmds.py +758 -0
- jira_as-1.0.0/src/jira_as/cli/commands/issue_cmds.py +771 -0
- jira_as-1.0.0/src/jira_as/cli/commands/jsm_cmds.py +2944 -0
- jira_as-1.0.0/src/jira_as/cli/commands/lifecycle_cmds.py +1402 -0
- jira_as-1.0.0/src/jira_as/cli/commands/ops_cmds.py +1035 -0
- jira_as-1.0.0/src/jira_as/cli/commands/relationships_cmds.py +1803 -0
- jira_as-1.0.0/src/jira_as/cli/commands/search_cmds.py +1850 -0
- jira_as-1.0.0/src/jira_as/cli/commands/time_cmds.py +1672 -0
- jira_as-1.0.0/src/jira_as/cli/main.py +87 -0
- jira_as-1.0.0/src/jira_as/config_manager.py +415 -0
- jira_as-1.0.0/src/jira_as/constants.py +24 -0
- jira_as-1.0.0/src/jira_as/credential_manager.py +336 -0
- jira_as-1.0.0/src/jira_as/error_handler.py +386 -0
- jira_as-1.0.0/src/jira_as/formatters.py +551 -0
- jira_as-1.0.0/src/jira_as/jira_client.py +6776 -0
- jira_as-1.0.0/src/jira_as/mock/__init__.py +83 -0
- jira_as-1.0.0/src/jira_as/mock/base.py +1206 -0
- jira_as-1.0.0/src/jira_as/mock/clients.py +212 -0
- jira_as-1.0.0/src/jira_as/mock/factories.py +236 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/__init__.py +27 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/admin.py +661 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/agile.py +511 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/collaborate.py +510 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/dev.py +481 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/fields.py +748 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/jsm.py +834 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/relationships.py +494 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/search.py +649 -0
- jira_as-1.0.0/src/jira_as/mock/mixins/time.py +517 -0
- jira_as-1.0.0/src/jira_as/mock/protocols.py +51 -0
- jira_as-1.0.0/src/jira_as/mock_responses.py +30 -0
- jira_as-1.0.0/src/jira_as/permission_helpers.py +341 -0
- jira_as-1.0.0/src/jira_as/project_context.py +533 -0
- jira_as-1.0.0/src/jira_as/request_batcher.py +88 -0
- jira_as-1.0.0/src/jira_as/search_helpers.py +145 -0
- jira_as-1.0.0/src/jira_as/time_utils.py +456 -0
- jira_as-1.0.0/src/jira_as/transition_helpers.py +78 -0
- jira_as-1.0.0/src/jira_as/user_helpers.py +114 -0
- jira_as-1.0.0/src/jira_as/validators.py +494 -0
jira_as-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Environments
|
|
54
|
+
.env
|
|
55
|
+
.venv
|
|
56
|
+
env/
|
|
57
|
+
venv/
|
|
58
|
+
ENV/
|
|
59
|
+
env.bak/
|
|
60
|
+
venv.bak/
|
|
61
|
+
|
|
62
|
+
# mypy
|
|
63
|
+
.mypy_cache/
|
|
64
|
+
.dmypy.json
|
|
65
|
+
dmypy.json
|
|
66
|
+
|
|
67
|
+
# IDE
|
|
68
|
+
.idea/
|
|
69
|
+
.vscode/
|
|
70
|
+
*.swp
|
|
71
|
+
*.swo
|
|
72
|
+
*~
|
|
73
|
+
|
|
74
|
+
# OS
|
|
75
|
+
.DS_Store
|
|
76
|
+
Thumbs.db
|
jira_as-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jason Krueger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
jira_as-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jira-as
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: JIRA Assistant Skills library and CLI - HTTP client, configuration, error handling, and jira-as command for JIRA REST API automation
|
|
5
|
+
Project-URL: Homepage, https://github.com/grandcamel/jira-as-lib
|
|
6
|
+
Project-URL: Repository, https://github.com/grandcamel/jira-as-lib
|
|
7
|
+
Project-URL: Issues, https://github.com/grandcamel/jira-as-lib/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/grandcamel/jira-as-lib/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Jason Krueger <grandcamel@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,assistant,atlassian,automation,claude,jira,rest,skills
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Office/Business :: Groupware
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: assistant-skills-lib>=1.0.0
|
|
26
|
+
Requires-Dist: click>=8.0
|
|
27
|
+
Requires-Dist: colorama>=0.4.6
|
|
28
|
+
Requires-Dist: nest-asyncio>=1.5.0
|
|
29
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
30
|
+
Requires-Dist: requests>=2.28.0
|
|
31
|
+
Requires-Dist: tabulate>=0.9.0
|
|
32
|
+
Requires-Dist: tqdm>=4.66.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: isort>=5.12.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: responses>=0.24.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: types-requests>=2.28.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: types-tabulate>=0.9.0; extra == 'dev'
|
|
42
|
+
Provides-Extra: keyring
|
|
43
|
+
Requires-Dist: keyring>=24.0.0; extra == 'keyring'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# JIRA AS
|
|
47
|
+
|
|
48
|
+
[](https://badge.fury.io/py/jira-as)
|
|
49
|
+
[](https://www.python.org/downloads/)
|
|
50
|
+
[](https://opensource.org/licenses/MIT)
|
|
51
|
+
|
|
52
|
+
A Python library and CLI for JIRA REST API automation, providing HTTP client, configuration management, error handling, and utilities for the [JIRA Assistant Skills](https://github.com/grandcamel/Jira-Assistant-Skills) Claude Code plugin.
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install jira-as
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
With optional keyring support for secure credential storage:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install jira-as[keyring]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- **CLI (`jira-as`)**: Command-line interface for JIRA operations
|
|
69
|
+
- **JiraClient**: HTTP client with automatic retry logic and exponential backoff
|
|
70
|
+
- **ConfigManager**: Multi-source configuration (env vars > keychain > settings.local.json > settings.json > defaults)
|
|
71
|
+
- **Error Handling**: Exception hierarchy mapping HTTP status codes to domain exceptions
|
|
72
|
+
- **Validators**: Input validation for issue keys, project keys, JQL queries, URLs, and more
|
|
73
|
+
- **Formatters**: Output formatting for tables, JSON, CSV export
|
|
74
|
+
- **ADF Helper**: Atlassian Document Format conversion (markdown/text to ADF and back)
|
|
75
|
+
- **Time Utils**: JIRA time format parsing and formatting (e.g., '2h', '1d 4h 30m')
|
|
76
|
+
- **Cache**: SQLite-based caching with TTL support for API responses
|
|
77
|
+
- **Credential Manager**: Secure credential storage via system keychain or JSON fallback
|
|
78
|
+
- **Mock Client**: Full mock implementation for testing without JIRA access
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
### Configuration
|
|
83
|
+
|
|
84
|
+
Set environment variables:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export JIRA_API_TOKEN="your-api-token" # Get from https://id.atlassian.com/manage-profile/security/api-tokens
|
|
88
|
+
export JIRA_EMAIL="your-email@company.com"
|
|
89
|
+
export JIRA_SITE_URL="https://your-company.atlassian.net"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### CLI Usage
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Get an issue
|
|
96
|
+
jira-as issue get PROJ-123
|
|
97
|
+
|
|
98
|
+
# Search issues
|
|
99
|
+
jira-as search query "project = PROJ AND status = Open"
|
|
100
|
+
|
|
101
|
+
# Create an issue
|
|
102
|
+
jira-as issue create PROJ --summary "New task" --type Task
|
|
103
|
+
|
|
104
|
+
# Transition an issue
|
|
105
|
+
jira-as lifecycle transition PROJ-123 "In Progress"
|
|
106
|
+
|
|
107
|
+
# See all commands
|
|
108
|
+
jira-as --help
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Library Usage
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from jira_as import get_jira_client, handle_errors
|
|
115
|
+
|
|
116
|
+
@handle_errors
|
|
117
|
+
def main():
|
|
118
|
+
# Get a configured JIRA client (use as context manager)
|
|
119
|
+
with get_jira_client() as client:
|
|
120
|
+
# Fetch an issue
|
|
121
|
+
issue = client.get_issue('PROJ-123')
|
|
122
|
+
print(f"Summary: {issue['fields']['summary']}")
|
|
123
|
+
|
|
124
|
+
# Search issues with JQL
|
|
125
|
+
results = client.search_issues('project = PROJ AND status = Open')
|
|
126
|
+
for issue in results['issues']:
|
|
127
|
+
print(f"{issue['key']}: {issue['fields']['summary']}")
|
|
128
|
+
|
|
129
|
+
if __name__ == '__main__':
|
|
130
|
+
main()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Core Components
|
|
134
|
+
|
|
135
|
+
### JiraClient
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from jira_as import JiraClient
|
|
139
|
+
|
|
140
|
+
# Direct instantiation (prefer get_jira_client() for config management)
|
|
141
|
+
client = JiraClient(
|
|
142
|
+
base_url="https://your-company.atlassian.net",
|
|
143
|
+
email="your-email@company.com",
|
|
144
|
+
api_token="your-api-token"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Use as context manager
|
|
148
|
+
with client:
|
|
149
|
+
issue = client.get_issue('PROJ-123')
|
|
150
|
+
client.create_issue(project_key='PROJ', summary='New issue', issue_type='Task')
|
|
151
|
+
client.transition_issue('PROJ-123', 'Done')
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Error Handling
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from jira_as import (
|
|
158
|
+
JiraError,
|
|
159
|
+
AuthenticationError,
|
|
160
|
+
PermissionError,
|
|
161
|
+
NotFoundError,
|
|
162
|
+
handle_errors
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@handle_errors
|
|
166
|
+
def main():
|
|
167
|
+
# Exceptions are caught and formatted nicely
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
# Or handle manually
|
|
171
|
+
try:
|
|
172
|
+
with get_jira_client() as client:
|
|
173
|
+
client.get_issue('INVALID-999')
|
|
174
|
+
except NotFoundError as e:
|
|
175
|
+
print(f"Issue not found: {e}")
|
|
176
|
+
except AuthenticationError as e:
|
|
177
|
+
print(f"Auth failed: {e}")
|
|
178
|
+
except JiraError as e:
|
|
179
|
+
print(f"JIRA error: {e}")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Validators
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from jira_as import (
|
|
186
|
+
validate_issue_key,
|
|
187
|
+
validate_project_key,
|
|
188
|
+
validate_jql,
|
|
189
|
+
validate_url,
|
|
190
|
+
ValidationError
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
key = validate_issue_key('PROJ-123') # Returns 'PROJ-123'
|
|
195
|
+
key = validate_issue_key('invalid') # Raises ValidationError
|
|
196
|
+
except ValidationError as e:
|
|
197
|
+
print(f"Invalid input: {e}")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### ADF Helper
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from jira_as import (
|
|
204
|
+
markdown_to_adf,
|
|
205
|
+
text_to_adf,
|
|
206
|
+
adf_to_text
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Convert markdown to ADF for JIRA
|
|
210
|
+
adf = markdown_to_adf("**Bold** and *italic* text")
|
|
211
|
+
|
|
212
|
+
# Convert plain text to ADF
|
|
213
|
+
adf = text_to_adf("Simple text content")
|
|
214
|
+
|
|
215
|
+
# Extract text from ADF
|
|
216
|
+
text = adf_to_text(adf_document)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Time Utils
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from jira_as import (
|
|
223
|
+
parse_time_string,
|
|
224
|
+
format_seconds,
|
|
225
|
+
parse_relative_date
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Parse JIRA time format to seconds
|
|
229
|
+
seconds = parse_time_string('2h 30m') # 9000
|
|
230
|
+
|
|
231
|
+
# Format seconds to JIRA time format
|
|
232
|
+
time_str = format_seconds(9000) # '2h 30m'
|
|
233
|
+
|
|
234
|
+
# Parse relative dates
|
|
235
|
+
dt = parse_relative_date('yesterday')
|
|
236
|
+
dt = parse_relative_date('2025-01-15')
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Mock Mode
|
|
240
|
+
|
|
241
|
+
For testing without JIRA access:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
export JIRA_MOCK_MODE=true
|
|
245
|
+
jira-as issue get DEMO-85 # Returns mock data
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
import os
|
|
250
|
+
os.environ['JIRA_MOCK_MODE'] = 'true'
|
|
251
|
+
|
|
252
|
+
from jira_as import get_jira_client
|
|
253
|
+
|
|
254
|
+
with get_jira_client() as client: # Returns MockJiraClient
|
|
255
|
+
issue = client.get_issue('DEMO-85') # Mock data
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Development
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Clone the repository
|
|
262
|
+
git clone https://github.com/grandcamel/jira-as-lib.git
|
|
263
|
+
cd jira-as-lib
|
|
264
|
+
|
|
265
|
+
# Install development dependencies
|
|
266
|
+
pip install -e ".[dev]"
|
|
267
|
+
|
|
268
|
+
# Run tests
|
|
269
|
+
pytest
|
|
270
|
+
|
|
271
|
+
# Format code
|
|
272
|
+
black src tests
|
|
273
|
+
isort src tests
|
|
274
|
+
|
|
275
|
+
# Type checking
|
|
276
|
+
mypy src
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
jira_as-1.0.0/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# JIRA AS
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/jira-as)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
A Python library and CLI for JIRA REST API automation, providing HTTP client, configuration management, error handling, and utilities for the [JIRA Assistant Skills](https://github.com/grandcamel/Jira-Assistant-Skills) Claude Code plugin.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install jira-as
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
With optional keyring support for secure credential storage:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install jira-as[keyring]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **CLI (`jira-as`)**: Command-line interface for JIRA operations
|
|
24
|
+
- **JiraClient**: HTTP client with automatic retry logic and exponential backoff
|
|
25
|
+
- **ConfigManager**: Multi-source configuration (env vars > keychain > settings.local.json > settings.json > defaults)
|
|
26
|
+
- **Error Handling**: Exception hierarchy mapping HTTP status codes to domain exceptions
|
|
27
|
+
- **Validators**: Input validation for issue keys, project keys, JQL queries, URLs, and more
|
|
28
|
+
- **Formatters**: Output formatting for tables, JSON, CSV export
|
|
29
|
+
- **ADF Helper**: Atlassian Document Format conversion (markdown/text to ADF and back)
|
|
30
|
+
- **Time Utils**: JIRA time format parsing and formatting (e.g., '2h', '1d 4h 30m')
|
|
31
|
+
- **Cache**: SQLite-based caching with TTL support for API responses
|
|
32
|
+
- **Credential Manager**: Secure credential storage via system keychain or JSON fallback
|
|
33
|
+
- **Mock Client**: Full mock implementation for testing without JIRA access
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Configuration
|
|
38
|
+
|
|
39
|
+
Set environment variables:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export JIRA_API_TOKEN="your-api-token" # Get from https://id.atlassian.com/manage-profile/security/api-tokens
|
|
43
|
+
export JIRA_EMAIL="your-email@company.com"
|
|
44
|
+
export JIRA_SITE_URL="https://your-company.atlassian.net"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### CLI Usage
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Get an issue
|
|
51
|
+
jira-as issue get PROJ-123
|
|
52
|
+
|
|
53
|
+
# Search issues
|
|
54
|
+
jira-as search query "project = PROJ AND status = Open"
|
|
55
|
+
|
|
56
|
+
# Create an issue
|
|
57
|
+
jira-as issue create PROJ --summary "New task" --type Task
|
|
58
|
+
|
|
59
|
+
# Transition an issue
|
|
60
|
+
jira-as lifecycle transition PROJ-123 "In Progress"
|
|
61
|
+
|
|
62
|
+
# See all commands
|
|
63
|
+
jira-as --help
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Library Usage
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from jira_as import get_jira_client, handle_errors
|
|
70
|
+
|
|
71
|
+
@handle_errors
|
|
72
|
+
def main():
|
|
73
|
+
# Get a configured JIRA client (use as context manager)
|
|
74
|
+
with get_jira_client() as client:
|
|
75
|
+
# Fetch an issue
|
|
76
|
+
issue = client.get_issue('PROJ-123')
|
|
77
|
+
print(f"Summary: {issue['fields']['summary']}")
|
|
78
|
+
|
|
79
|
+
# Search issues with JQL
|
|
80
|
+
results = client.search_issues('project = PROJ AND status = Open')
|
|
81
|
+
for issue in results['issues']:
|
|
82
|
+
print(f"{issue['key']}: {issue['fields']['summary']}")
|
|
83
|
+
|
|
84
|
+
if __name__ == '__main__':
|
|
85
|
+
main()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Core Components
|
|
89
|
+
|
|
90
|
+
### JiraClient
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from jira_as import JiraClient
|
|
94
|
+
|
|
95
|
+
# Direct instantiation (prefer get_jira_client() for config management)
|
|
96
|
+
client = JiraClient(
|
|
97
|
+
base_url="https://your-company.atlassian.net",
|
|
98
|
+
email="your-email@company.com",
|
|
99
|
+
api_token="your-api-token"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Use as context manager
|
|
103
|
+
with client:
|
|
104
|
+
issue = client.get_issue('PROJ-123')
|
|
105
|
+
client.create_issue(project_key='PROJ', summary='New issue', issue_type='Task')
|
|
106
|
+
client.transition_issue('PROJ-123', 'Done')
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Error Handling
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from jira_as import (
|
|
113
|
+
JiraError,
|
|
114
|
+
AuthenticationError,
|
|
115
|
+
PermissionError,
|
|
116
|
+
NotFoundError,
|
|
117
|
+
handle_errors
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
@handle_errors
|
|
121
|
+
def main():
|
|
122
|
+
# Exceptions are caught and formatted nicely
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
# Or handle manually
|
|
126
|
+
try:
|
|
127
|
+
with get_jira_client() as client:
|
|
128
|
+
client.get_issue('INVALID-999')
|
|
129
|
+
except NotFoundError as e:
|
|
130
|
+
print(f"Issue not found: {e}")
|
|
131
|
+
except AuthenticationError as e:
|
|
132
|
+
print(f"Auth failed: {e}")
|
|
133
|
+
except JiraError as e:
|
|
134
|
+
print(f"JIRA error: {e}")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Validators
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from jira_as import (
|
|
141
|
+
validate_issue_key,
|
|
142
|
+
validate_project_key,
|
|
143
|
+
validate_jql,
|
|
144
|
+
validate_url,
|
|
145
|
+
ValidationError
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
key = validate_issue_key('PROJ-123') # Returns 'PROJ-123'
|
|
150
|
+
key = validate_issue_key('invalid') # Raises ValidationError
|
|
151
|
+
except ValidationError as e:
|
|
152
|
+
print(f"Invalid input: {e}")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### ADF Helper
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from jira_as import (
|
|
159
|
+
markdown_to_adf,
|
|
160
|
+
text_to_adf,
|
|
161
|
+
adf_to_text
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Convert markdown to ADF for JIRA
|
|
165
|
+
adf = markdown_to_adf("**Bold** and *italic* text")
|
|
166
|
+
|
|
167
|
+
# Convert plain text to ADF
|
|
168
|
+
adf = text_to_adf("Simple text content")
|
|
169
|
+
|
|
170
|
+
# Extract text from ADF
|
|
171
|
+
text = adf_to_text(adf_document)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Time Utils
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from jira_as import (
|
|
178
|
+
parse_time_string,
|
|
179
|
+
format_seconds,
|
|
180
|
+
parse_relative_date
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Parse JIRA time format to seconds
|
|
184
|
+
seconds = parse_time_string('2h 30m') # 9000
|
|
185
|
+
|
|
186
|
+
# Format seconds to JIRA time format
|
|
187
|
+
time_str = format_seconds(9000) # '2h 30m'
|
|
188
|
+
|
|
189
|
+
# Parse relative dates
|
|
190
|
+
dt = parse_relative_date('yesterday')
|
|
191
|
+
dt = parse_relative_date('2025-01-15')
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Mock Mode
|
|
195
|
+
|
|
196
|
+
For testing without JIRA access:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
export JIRA_MOCK_MODE=true
|
|
200
|
+
jira-as issue get DEMO-85 # Returns mock data
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
import os
|
|
205
|
+
os.environ['JIRA_MOCK_MODE'] = 'true'
|
|
206
|
+
|
|
207
|
+
from jira_as import get_jira_client
|
|
208
|
+
|
|
209
|
+
with get_jira_client() as client: # Returns MockJiraClient
|
|
210
|
+
issue = client.get_issue('DEMO-85') # Mock data
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Clone the repository
|
|
217
|
+
git clone https://github.com/grandcamel/jira-as-lib.git
|
|
218
|
+
cd jira-as-lib
|
|
219
|
+
|
|
220
|
+
# Install development dependencies
|
|
221
|
+
pip install -e ".[dev]"
|
|
222
|
+
|
|
223
|
+
# Run tests
|
|
224
|
+
pytest
|
|
225
|
+
|
|
226
|
+
# Format code
|
|
227
|
+
black src tests
|
|
228
|
+
isort src tests
|
|
229
|
+
|
|
230
|
+
# Type checking
|
|
231
|
+
mypy src
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
MIT License - see [LICENSE](LICENSE) for details.
|