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.
Files changed (58) hide show
  1. jira_as-1.0.0/.gitignore +76 -0
  2. jira_as-1.0.0/LICENSE +21 -0
  3. jira_as-1.0.0/PKG-INFO +281 -0
  4. jira_as-1.0.0/README.md +236 -0
  5. jira_as-1.0.0/pyproject.toml +221 -0
  6. jira_as-1.0.0/src/jira_as/__init__.py +393 -0
  7. jira_as-1.0.0/src/jira_as/adf_helper.py +482 -0
  8. jira_as-1.0.0/src/jira_as/autocomplete_cache.py +365 -0
  9. jira_as-1.0.0/src/jira_as/automation_client.py +673 -0
  10. jira_as-1.0.0/src/jira_as/batch_processor.py +142 -0
  11. jira_as-1.0.0/src/jira_as/cache.py +471 -0
  12. jira_as-1.0.0/src/jira_as/cli/__init__.py +0 -0
  13. jira_as-1.0.0/src/jira_as/cli/cli_utils.py +306 -0
  14. jira_as-1.0.0/src/jira_as/cli/commands/__init__.py +0 -0
  15. jira_as-1.0.0/src/jira_as/cli/commands/admin_cmds.py +3484 -0
  16. jira_as-1.0.0/src/jira_as/cli/commands/agile_cmds.py +2033 -0
  17. jira_as-1.0.0/src/jira_as/cli/commands/bulk_cmds.py +1302 -0
  18. jira_as-1.0.0/src/jira_as/cli/commands/collaborate_cmds.py +1330 -0
  19. jira_as-1.0.0/src/jira_as/cli/commands/dev_cmds.py +1070 -0
  20. jira_as-1.0.0/src/jira_as/cli/commands/fields_cmds.py +758 -0
  21. jira_as-1.0.0/src/jira_as/cli/commands/issue_cmds.py +771 -0
  22. jira_as-1.0.0/src/jira_as/cli/commands/jsm_cmds.py +2944 -0
  23. jira_as-1.0.0/src/jira_as/cli/commands/lifecycle_cmds.py +1402 -0
  24. jira_as-1.0.0/src/jira_as/cli/commands/ops_cmds.py +1035 -0
  25. jira_as-1.0.0/src/jira_as/cli/commands/relationships_cmds.py +1803 -0
  26. jira_as-1.0.0/src/jira_as/cli/commands/search_cmds.py +1850 -0
  27. jira_as-1.0.0/src/jira_as/cli/commands/time_cmds.py +1672 -0
  28. jira_as-1.0.0/src/jira_as/cli/main.py +87 -0
  29. jira_as-1.0.0/src/jira_as/config_manager.py +415 -0
  30. jira_as-1.0.0/src/jira_as/constants.py +24 -0
  31. jira_as-1.0.0/src/jira_as/credential_manager.py +336 -0
  32. jira_as-1.0.0/src/jira_as/error_handler.py +386 -0
  33. jira_as-1.0.0/src/jira_as/formatters.py +551 -0
  34. jira_as-1.0.0/src/jira_as/jira_client.py +6776 -0
  35. jira_as-1.0.0/src/jira_as/mock/__init__.py +83 -0
  36. jira_as-1.0.0/src/jira_as/mock/base.py +1206 -0
  37. jira_as-1.0.0/src/jira_as/mock/clients.py +212 -0
  38. jira_as-1.0.0/src/jira_as/mock/factories.py +236 -0
  39. jira_as-1.0.0/src/jira_as/mock/mixins/__init__.py +27 -0
  40. jira_as-1.0.0/src/jira_as/mock/mixins/admin.py +661 -0
  41. jira_as-1.0.0/src/jira_as/mock/mixins/agile.py +511 -0
  42. jira_as-1.0.0/src/jira_as/mock/mixins/collaborate.py +510 -0
  43. jira_as-1.0.0/src/jira_as/mock/mixins/dev.py +481 -0
  44. jira_as-1.0.0/src/jira_as/mock/mixins/fields.py +748 -0
  45. jira_as-1.0.0/src/jira_as/mock/mixins/jsm.py +834 -0
  46. jira_as-1.0.0/src/jira_as/mock/mixins/relationships.py +494 -0
  47. jira_as-1.0.0/src/jira_as/mock/mixins/search.py +649 -0
  48. jira_as-1.0.0/src/jira_as/mock/mixins/time.py +517 -0
  49. jira_as-1.0.0/src/jira_as/mock/protocols.py +51 -0
  50. jira_as-1.0.0/src/jira_as/mock_responses.py +30 -0
  51. jira_as-1.0.0/src/jira_as/permission_helpers.py +341 -0
  52. jira_as-1.0.0/src/jira_as/project_context.py +533 -0
  53. jira_as-1.0.0/src/jira_as/request_batcher.py +88 -0
  54. jira_as-1.0.0/src/jira_as/search_helpers.py +145 -0
  55. jira_as-1.0.0/src/jira_as/time_utils.py +456 -0
  56. jira_as-1.0.0/src/jira_as/transition_helpers.py +78 -0
  57. jira_as-1.0.0/src/jira_as/user_helpers.py +114 -0
  58. jira_as-1.0.0/src/jira_as/validators.py +494 -0
@@ -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
+ [![PyPI version](https://badge.fury.io/py/jira-as.svg)](https://badge.fury.io/py/jira-as)
49
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.
@@ -0,0 +1,236 @@
1
+ # JIRA AS
2
+
3
+ [![PyPI version](https://badge.fury.io/py/jira-as.svg)](https://badge.fury.io/py/jira-as)
4
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.