youtrack-cli 0.3.2__tar.gz → 0.3.4__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 (94) hide show
  1. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/PKG-INFO +11 -1
  2. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/README.md +10 -0
  3. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/admin.rst +9 -0
  4. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/articles.rst +49 -8
  5. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/pyproject.toml +1 -1
  6. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_admin.py +32 -0
  7. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_articles.py +83 -0
  8. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/uv.lock +1 -1
  9. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/admin.py +61 -18
  10. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/commands/articles.py +37 -2
  11. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/.claude/settings.local.json +0 -0
  12. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/.github/dependabot.yml +0 -0
  13. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/.github/workflows/ci.yml +0 -0
  14. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/.github/workflows/release.yml +0 -0
  15. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/.gitignore +0 -0
  16. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/.pre-commit-config.yaml +0 -0
  17. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/.readthedocs.yaml +0 -0
  18. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/CLAUDE.md +0 -0
  19. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/PUBLISHING.md +0 -0
  20. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/Makefile +0 -0
  21. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/api/index.rst +0 -0
  22. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/changelog.rst +0 -0
  23. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/command-aliases.rst +0 -0
  24. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/auth.rst +0 -0
  25. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/boards.rst +0 -0
  26. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/config.rst +0 -0
  27. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/index.rst +0 -0
  28. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/issues.rst +0 -0
  29. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/projects.rst +0 -0
  30. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/reports.rst +0 -0
  31. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/time.rst +0 -0
  32. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/commands/users.rst +0 -0
  33. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/conf.py +0 -0
  34. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/configuration.rst +0 -0
  35. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/development.rst +0 -0
  36. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/index.rst +0 -0
  37. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/installation.rst +0 -0
  38. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/learning-path.rst +0 -0
  39. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/logging.rst +0 -0
  40. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/performance.md +0 -0
  41. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/progress-indicators.md +0 -0
  42. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/quickstart.rst +0 -0
  43. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/requirements.txt +0 -0
  44. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/security.rst +0 -0
  45. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/troubleshooting.rst +0 -0
  46. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/workflows.rst +0 -0
  47. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/docs/youtrack-concepts.rst +0 -0
  48. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/justfile +0 -0
  49. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/package-lock.json +0 -0
  50. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/package.json +0 -0
  51. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/__init__.py +0 -0
  52. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/conftest.py +0 -0
  53. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_auth.py +0 -0
  54. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_boards.py +0 -0
  55. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_config.py +0 -0
  56. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_issues.py +0 -0
  57. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_logging.py +0 -0
  58. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_main.py +0 -0
  59. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_projects.py +0 -0
  60. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_reports.py +0 -0
  61. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_security.py +0 -0
  62. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_time.py +0 -0
  63. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tests/test_users.py +0 -0
  64. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/tox.ini +0 -0
  65. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/__init__.py +0 -0
  66. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/articles.py +0 -0
  67. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/auth.py +0 -0
  68. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/boards.py +0 -0
  69. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/cache.py +0 -0
  70. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/cli_utils/__init__.py +0 -0
  71. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/cli_utils/aliases.py +0 -0
  72. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/client.py +0 -0
  73. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/commands/__init__.py +0 -0
  74. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/commands/boards.py +0 -0
  75. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/commands/common.py +0 -0
  76. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/commands/issues.py +0 -0
  77. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/commands/projects.py +0 -0
  78. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/commands/time_tracking.py +0 -0
  79. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/commands/users.py +0 -0
  80. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/common.py +0 -0
  81. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/config.py +0 -0
  82. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/exceptions.py +0 -0
  83. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/issues.py +0 -0
  84. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/logging.py +0 -0
  85. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/main.py +0 -0
  86. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/performance.py +0 -0
  87. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/progress.py +0 -0
  88. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/projects.py +0 -0
  89. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/py.typed +0 -0
  90. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/reports.py +0 -0
  91. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/security.py +0 -0
  92. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/time.py +0 -0
  93. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/users.py +0 -0
  94. {youtrack_cli-0.3.2 → youtrack_cli-0.3.4}/youtrack_cli/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: youtrack-cli
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: YouTrack CLI - Command line interface for JetBrains YouTrack issue tracking system
5
5
  Project-URL: Homepage, https://github.com/ryan-murphy/yt-cli
6
6
  Project-URL: Documentation, https://yt-cli.readthedocs.io/
@@ -136,6 +136,9 @@ yt projects list
136
136
  # Create an article
137
137
  yt articles create "Getting Started" --content "Welcome to our documentation"
138
138
 
139
+ # Create an article from a markdown file
140
+ yt articles create "API Documentation" --file api-docs.md
141
+
139
142
  # Log work time
140
143
  yt time log ISSUE-123 "2h" --description "Feature development"
141
144
 
@@ -198,6 +201,13 @@ yt issues tag add ISSUE-456 urgent
198
201
  ```bash
199
202
  # Create and manage knowledge base
200
203
  yt articles create "API Guide" --content "Comprehensive API documentation"
204
+
205
+ # Create articles from markdown files
206
+ yt articles create "Installation Guide" --file docs/install.md
207
+
208
+ # Organize existing documentation
209
+ yt articles create "Developer Guide" --file dev-guide.md --project-id PROJECT-123
210
+
201
211
  yt articles tree --project-id PROJECT-123
202
212
  yt articles search "authentication"
203
213
  ```
@@ -96,6 +96,9 @@ yt projects list
96
96
  # Create an article
97
97
  yt articles create "Getting Started" --content "Welcome to our documentation"
98
98
 
99
+ # Create an article from a markdown file
100
+ yt articles create "API Documentation" --file api-docs.md
101
+
99
102
  # Log work time
100
103
  yt time log ISSUE-123 "2h" --description "Feature development"
101
104
 
@@ -158,6 +161,13 @@ yt issues tag add ISSUE-456 urgent
158
161
  ```bash
159
162
  # Create and manage knowledge base
160
163
  yt articles create "API Guide" --content "Comprehensive API documentation"
164
+
165
+ # Create articles from markdown files
166
+ yt articles create "Installation Guide" --file docs/install.md
167
+
168
+ # Organize existing documentation
169
+ yt articles create "Developer Guide" --file dev-guide.md --project-id PROJECT-123
170
+
161
171
  yt articles tree --project-id PROJECT-123
162
172
  yt articles search "authentication"
163
173
  ```
@@ -552,6 +552,15 @@ Common Issues and Solutions
552
552
  **Health Check Issues**
553
553
  Review system logs and resource availability.
554
554
 
555
+ **Health Check 404 Errors**
556
+ If the health check command returns a 404 error, this may indicate:
557
+
558
+ * Your YouTrack version doesn't support the system settings endpoint
559
+ * The API endpoint has changed in your YouTrack version
560
+ * Your YouTrack instance has a different API configuration
561
+
562
+ The command will automatically try fallback endpoints and provide specific guidance based on the error type.
563
+
555
564
  System Diagnostics
556
565
  ~~~~~~~~~~~~~~~~~
557
566
 
@@ -54,7 +54,10 @@ Create a new article in YouTrack.
54
54
  - Description
55
55
  * - ``--content, -c``
56
56
  - text
57
- - Article content (will prompt if not provided)
57
+ - Article content (required if --file not provided)
58
+ * - ``--file, -f``
59
+ - path
60
+ - Path to markdown file containing article content (required if --content not provided)
58
61
  * - ``--project-id, -p``
59
62
  - string
60
63
  - Project ID to associate with the article
@@ -72,17 +75,23 @@ Create a new article in YouTrack.
72
75
 
73
76
  .. code-block:: bash
74
77
 
75
- # Create a simple article
78
+ # Create a simple article with inline content
76
79
  yt articles create "Getting Started Guide" --content "This is a comprehensive guide..."
77
80
 
78
- # Create an article in a specific project
79
- yt articles create "API Documentation" --content "API usage guide" --project-id PROJECT-123
81
+ # Create an article from a markdown file
82
+ yt articles create "Getting Started Guide" --file getting-started.md
83
+
84
+ # Create an article in a specific project from a file
85
+ yt articles create "API Documentation" --file api-docs.md --project-id PROJECT-123
86
+
87
+ # Create a nested article (child of another article) from a file
88
+ yt articles create "Advanced Features" --file advanced.md --parent-id ARTICLE-456
80
89
 
81
- # Create a nested article (child of another article)
82
- yt articles create "Advanced Features" --content "Advanced guide" --parent-id ARTICLE-456
90
+ # Create a draft article (private visibility) from a file
91
+ yt articles create "Draft Article" --file draft.md --visibility private
83
92
 
84
- # Create a draft article (private visibility)
85
- yt articles create "Draft Article" --content "Work in progress" --visibility private
93
+ # Create an article with inline content (traditional approach)
94
+ yt articles create "API Documentation" --content "API usage guide" --project-id PROJECT-123
86
95
 
87
96
  edit
88
97
  ~~~~
@@ -671,6 +680,24 @@ Content Management
671
680
  # View article details
672
681
  yt articles edit ARTICLE-123 --show-details
673
682
 
683
+ Working with Markdown Files
684
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
685
+
686
+ .. code-block:: bash
687
+
688
+ # Create articles from existing markdown files
689
+ yt articles create "Installation Guide" --file docs/installation.md
690
+
691
+ # Create multiple articles from markdown files
692
+ yt articles create "User Manual" --file user-manual.md --project-id PROJECT-123
693
+ yt articles create "Developer Guide" --file dev-guide.md --project-id PROJECT-123
694
+
695
+ # Organize markdown documentation into YouTrack articles
696
+ for file in docs/*.md; do
697
+ title=$(basename "$file" .md)
698
+ yt articles create "$title" --file "$file" --project-id PROJECT-123
699
+ done
700
+
674
701
  Best Practices
675
702
  --------------
676
703
 
@@ -690,6 +717,8 @@ Best Practices
690
717
 
691
718
  8. **Consistent Formatting**: Follow consistent formatting and style guidelines across articles.
692
719
 
720
+ 9. **Use Markdown Files**: For complex content, consider writing in markdown files first and using the ``--file`` option for better version control and editing experience.
721
+
693
722
  Error Handling
694
723
  --------------
695
724
 
@@ -710,6 +739,18 @@ Common error scenarios and solutions:
710
739
  **Content Too Large**
711
740
  YouTrack may have limits on article content size. Consider breaking large articles into smaller sections.
712
741
 
742
+ **File Not Found**
743
+ Ensure the file path provided with ``--file`` exists and is accessible.
744
+
745
+ **Invalid File Content**
746
+ The specified file must be a valid text file. Binary files or files with invalid encoding will be rejected.
747
+
748
+ **Empty File**
749
+ Files provided with ``--file`` must contain content. Empty files will be rejected.
750
+
751
+ **Both Content and File Specified**
752
+ You cannot use both ``--content`` and ``--file`` options simultaneously. Choose one method for providing article content.
753
+
713
754
  See Also
714
755
  --------
715
756
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "youtrack-cli"
3
- version = "0.3.2"
3
+ version = "0.3.4"
4
4
  description = "YouTrack CLI - Command line interface for JetBrains YouTrack issue tracking system"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9, <3.14"
@@ -175,6 +175,38 @@ class TestAdminManager:
175
175
  assert result["status"] == "success"
176
176
  assert result["data"] == mock_health
177
177
 
178
+ @pytest.mark.asyncio
179
+ async def test_get_system_health_404_error(self, admin_manager, auth_manager):
180
+ """Test system health check with 404 error on all endpoints."""
181
+ with patch("httpx.AsyncClient") as mock_client:
182
+ mock_response = Mock()
183
+ mock_response.status_code = 404
184
+ mock_request = Mock()
185
+ http_error = httpx.HTTPStatusError("Not Found", request=mock_request, response=mock_response)
186
+ mock_client.return_value.__aenter__.return_value.get.side_effect = http_error
187
+
188
+ result = await admin_manager.get_system_health()
189
+
190
+ assert result["status"] == "error"
191
+ assert "System health endpoint not found (404)" in result["message"]
192
+ assert "YouTrack version doesn't support this endpoint" in result["message"]
193
+
194
+ @pytest.mark.asyncio
195
+ async def test_get_system_health_403_error(self, admin_manager, auth_manager):
196
+ """Test system health check with 403 permission error."""
197
+ with patch("httpx.AsyncClient") as mock_client:
198
+ mock_response = Mock()
199
+ mock_response.status_code = 403
200
+ mock_request = Mock()
201
+ http_error = httpx.HTTPStatusError("Forbidden", request=mock_request, response=mock_response)
202
+ mock_client.return_value.__aenter__.return_value.get.side_effect = http_error
203
+
204
+ result = await admin_manager.get_system_health()
205
+
206
+ assert result["status"] == "error"
207
+ assert "Insufficient permissions for health check" in result["message"]
208
+ assert "Low-level Admin Read" in result["message"]
209
+
178
210
  @pytest.mark.asyncio
179
211
  async def test_clear_caches_success(self, admin_manager, auth_manager):
180
212
  """Test successful cache clearing."""
@@ -1,5 +1,6 @@
1
1
  """Tests for article management functionality."""
2
2
 
3
+ from pathlib import Path
3
4
  from unittest.mock import MagicMock, Mock, patch
4
5
 
5
6
  import pytest
@@ -434,6 +435,88 @@ class TestArticlesCLI:
434
435
  assert result.exit_code == 0
435
436
  assert "Creating article" in result.output
436
437
 
438
+ def test_articles_create_command_with_file(self):
439
+ """Test articles create command with file input."""
440
+ from youtrack_cli.main import main
441
+
442
+ runner = CliRunner()
443
+
444
+ with (
445
+ patch("youtrack_cli.main.asyncio.run") as mock_run,
446
+ patch("youtrack_cli.main.AuthManager"),
447
+ patch("youtrack_cli.articles.ArticleManager"),
448
+ runner.isolated_filesystem(),
449
+ ):
450
+ # Create a test markdown file
451
+ test_file = Path("test_article.md")
452
+ test_file.write_text("# Test Article\n\nThis is test content from a markdown file.")
453
+
454
+ mock_run.return_value = {
455
+ "status": "success",
456
+ "message": "Article created successfully",
457
+ "data": {"id": "123"},
458
+ }
459
+
460
+ result = runner.invoke(main, ["articles", "create", "Test Title", "--file", str(test_file)])
461
+
462
+ assert result.exit_code == 0
463
+ assert "Reading content from" in result.output
464
+ assert "Creating article" in result.output
465
+
466
+ def test_articles_create_command_file_not_found(self):
467
+ """Test articles create command with non-existent file."""
468
+ from youtrack_cli.main import main
469
+
470
+ runner = CliRunner()
471
+
472
+ result = runner.invoke(main, ["articles", "create", "Test Title", "--file", "nonexistent.md"])
473
+
474
+ assert result.exit_code != 0
475
+ assert "does not exist" in result.output
476
+
477
+ def test_articles_create_command_both_content_and_file(self):
478
+ """Test articles create command with both content and file (should fail)."""
479
+ from youtrack_cli.main import main
480
+
481
+ runner = CliRunner()
482
+
483
+ with runner.isolated_filesystem():
484
+ test_file = Path("test_article.md")
485
+ test_file.write_text("Test content")
486
+
487
+ result = runner.invoke(
488
+ main, ["articles", "create", "Test Title", "--content", "Test content", "--file", str(test_file)]
489
+ )
490
+
491
+ assert result.exit_code != 0
492
+ assert "Cannot specify both --content and --file options" in result.output
493
+
494
+ def test_articles_create_command_no_content_or_file(self):
495
+ """Test articles create command with neither content nor file (should fail)."""
496
+ from youtrack_cli.main import main
497
+
498
+ runner = CliRunner()
499
+
500
+ result = runner.invoke(main, ["articles", "create", "Test Title"])
501
+
502
+ assert result.exit_code != 0
503
+ assert "Either --content or --file must be specified" in result.output
504
+
505
+ def test_articles_create_command_empty_file(self):
506
+ """Test articles create command with empty file."""
507
+ from youtrack_cli.main import main
508
+
509
+ runner = CliRunner()
510
+
511
+ with runner.isolated_filesystem():
512
+ test_file = Path("empty.md")
513
+ test_file.write_text("")
514
+
515
+ result = runner.invoke(main, ["articles", "create", "Test Title", "--file", str(test_file)])
516
+
517
+ assert result.exit_code != 0
518
+ assert "is empty" in result.output
519
+
437
520
  def test_articles_list_command(self):
438
521
  """Test articles list command."""
439
522
  from youtrack_cli.main import main
@@ -1751,7 +1751,7 @@ wheels = [
1751
1751
 
1752
1752
  [[package]]
1753
1753
  name = "youtrack-cli"
1754
- version = "0.3.2"
1754
+ version = "0.3.4"
1755
1755
  source = { editable = "." }
1756
1756
  dependencies = [
1757
1757
  { name = "click" },
@@ -153,6 +153,7 @@ class AdminManager:
153
153
  response = await client.get(
154
154
  f"{credentials.base_url.rstrip('/')}/api/admin/globalSettings/license",
155
155
  headers=headers,
156
+ params={"fields": "id,username,license,error"},
156
157
  timeout=10.0,
157
158
  )
158
159
  response.raise_for_status()
@@ -167,6 +168,14 @@ class AdminManager:
167
168
  "status": "error",
168
169
  "message": "Insufficient permissions to view license.",
169
170
  }
171
+ elif e.response.status_code == 404:
172
+ return {
173
+ "status": "error",
174
+ "message": (
175
+ "License endpoint not found. This may indicate an "
176
+ "incompatible YouTrack version or configuration."
177
+ ),
178
+ }
170
179
  return {"status": "error", "message": f"HTTP error: {e}"}
171
180
  except Exception as e:
172
181
  return {"status": "error", "message": f"Unexpected error: {e}"}
@@ -231,28 +240,62 @@ class AdminManager:
231
240
  "Accept": "application/json",
232
241
  }
233
242
 
234
- async with httpx.AsyncClient() as client:
235
- try:
236
- response = await client.get(
237
- f"{credentials.base_url.rstrip('/')}/api/admin/globalSettings/systemSettings",
238
- headers=headers,
239
- timeout=10.0,
240
- )
241
- response.raise_for_status()
242
-
243
- health_info = response.json()
244
- return {"status": "success", "data": health_info}
243
+ # List of endpoints to try, in order of preference
244
+ endpoints = [
245
+ "/api/admin/globalSettings/systemSettings",
246
+ "/api/admin/globalSettings/systemSettings?fields=baseUrl,isApplicationReadOnly,maxUploadFileSize,maxExportItems",
247
+ ]
245
248
 
246
- except httpx.HTTPError as e:
247
- if hasattr(e, "response") and e.response is not None:
248
- if e.response.status_code == 403:
249
+ async with httpx.AsyncClient() as client:
250
+ for endpoint in endpoints:
251
+ try:
252
+ response = await client.get(
253
+ f"{credentials.base_url.rstrip('/')}{endpoint}",
254
+ headers=headers,
255
+ timeout=10.0,
256
+ )
257
+ response.raise_for_status()
258
+
259
+ health_info = response.json()
260
+ return {"status": "success", "data": health_info}
261
+
262
+ except httpx.HTTPStatusError as e:
263
+ if e.response.status_code == 404:
264
+ continue # Try next endpoint
265
+ elif e.response.status_code == 403:
249
266
  return {
250
267
  "status": "error",
251
- "message": "Insufficient permissions for health check.",
268
+ "message": "Insufficient permissions for health check. "
269
+ "Requires 'Low-level Admin Read' permission.",
252
270
  }
253
- return {"status": "error", "message": f"HTTP error: {e}"}
254
- except Exception as e:
255
- return {"status": "error", "message": f"Unexpected error: {e}"}
271
+ elif e.response.status_code == 401:
272
+ return {
273
+ "status": "error",
274
+ "message": "Authentication failed. Your token may have expired. Run 'yt auth login' again.",
275
+ }
276
+ else:
277
+ response_text = e.response.text if hasattr(e.response, "text") else str(e)
278
+ return {
279
+ "status": "error",
280
+ "message": f"HTTP {e.response.status_code}: {response_text}",
281
+ }
282
+ except httpx.RequestError as e:
283
+ return {
284
+ "status": "error",
285
+ "message": f"Network error: {e}. Check your YouTrack URL and network connection.",
286
+ }
287
+ except Exception as e:
288
+ return {"status": "error", "message": f"Unexpected error: {e}"}
289
+
290
+ # If all endpoints failed with 404
291
+ return {
292
+ "status": "error",
293
+ "message": "System health endpoint not found (404). This may indicate:\n"
294
+ "1. Your YouTrack version doesn't support this endpoint\n"
295
+ "2. The endpoint URL may have changed\n"
296
+ "3. Your YouTrack instance has a different API configuration\n"
297
+ "Please verify your YouTrack version and API access.",
298
+ }
256
299
 
257
300
  async def clear_caches(self) -> dict[str, Any]:
258
301
  """Clear system caches.
@@ -1,6 +1,7 @@
1
1
  """Articles command group for YouTrack CLI."""
2
2
 
3
3
  import asyncio
4
+ from pathlib import Path
4
5
  from typing import Optional
5
6
 
6
7
  import click
@@ -20,9 +21,14 @@ def articles() -> None:
20
21
  @click.option(
21
22
  "--content",
22
23
  "-c",
23
- prompt=True,
24
24
  help="Article content",
25
25
  )
26
+ @click.option(
27
+ "--file",
28
+ "-f",
29
+ type=click.Path(exists=True, path_type=Path),
30
+ help="Path to markdown file containing article content",
31
+ )
26
32
  @click.option(
27
33
  "--project-id",
28
34
  "-p",
@@ -47,7 +53,8 @@ def articles() -> None:
47
53
  def create(
48
54
  ctx: click.Context,
49
55
  title: str,
50
- content: str,
56
+ content: Optional[str],
57
+ file: Optional[Path],
51
58
  project_id: Optional[str],
52
59
  parent_id: Optional[str],
53
60
  summary: Optional[str],
@@ -57,11 +64,39 @@ def create(
57
64
  from ..articles import ArticleManager
58
65
 
59
66
  console = Console()
67
+
68
+ # Validate that either content or file is provided, but not both
69
+ if content and file:
70
+ console.print("❌ Cannot specify both --content and --file options", style="red")
71
+ raise click.ClickException("Use either --content or --file, not both")
72
+
73
+ if not content and not file:
74
+ console.print("❌ Either --content or --file must be specified", style="red")
75
+ raise click.ClickException("Article content is required")
76
+
77
+ # Read content from file if provided
78
+ if file:
79
+ try:
80
+ console.print(f"📖 Reading content from '{file}'...", style="blue")
81
+ content = file.read_text(encoding="utf-8")
82
+ if not content.strip():
83
+ console.print(f"❌ File '{file}' is empty", style="red")
84
+ raise click.ClickException("File content cannot be empty")
85
+ except UnicodeDecodeError:
86
+ console.print(f"❌ File '{file}' is not a valid text file", style="red")
87
+ raise click.ClickException("File must be a valid text file") from None
88
+ except Exception as e:
89
+ console.print(f"❌ Error reading file '{file}': {e}", style="red")
90
+ raise click.ClickException("Failed to read file") from e
91
+
60
92
  auth_manager = AuthManager(ctx.obj.get("config"))
61
93
  article_manager = ArticleManager(auth_manager)
62
94
 
63
95
  console.print(f"📝 Creating article '{title}'...", style="blue")
64
96
 
97
+ # At this point, content is guaranteed to be a string due to validation above
98
+ assert content is not None
99
+
65
100
  try:
66
101
  result = asyncio.run(
67
102
  article_manager.create_article(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes