youtrack-cli 0.3.1__tar.gz → 0.3.3__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.1 → youtrack_cli-0.3.3}/.claude/settings.local.json +2 -1
  2. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/PKG-INFO +11 -1
  3. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/README.md +10 -0
  4. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/articles.rst +49 -8
  5. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/justfile +2 -2
  6. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/pyproject.toml +1 -1
  7. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_admin.py +1 -1
  8. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_articles.py +83 -0
  9. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/uv.lock +1 -1
  10. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/admin.py +7 -6
  11. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/commands/articles.py +37 -2
  12. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/.github/dependabot.yml +0 -0
  13. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/.github/workflows/ci.yml +0 -0
  14. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/.github/workflows/release.yml +0 -0
  15. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/.gitignore +0 -0
  16. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/.pre-commit-config.yaml +0 -0
  17. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/.readthedocs.yaml +0 -0
  18. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/CLAUDE.md +0 -0
  19. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/PUBLISHING.md +0 -0
  20. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/Makefile +0 -0
  21. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/api/index.rst +0 -0
  22. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/changelog.rst +0 -0
  23. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/command-aliases.rst +0 -0
  24. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/admin.rst +0 -0
  25. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/auth.rst +0 -0
  26. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/boards.rst +0 -0
  27. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/config.rst +0 -0
  28. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/index.rst +0 -0
  29. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/issues.rst +0 -0
  30. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/projects.rst +0 -0
  31. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/reports.rst +0 -0
  32. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/time.rst +0 -0
  33. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/commands/users.rst +0 -0
  34. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/conf.py +0 -0
  35. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/configuration.rst +0 -0
  36. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/development.rst +0 -0
  37. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/index.rst +0 -0
  38. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/installation.rst +0 -0
  39. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/learning-path.rst +0 -0
  40. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/logging.rst +0 -0
  41. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/performance.md +0 -0
  42. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/progress-indicators.md +0 -0
  43. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/quickstart.rst +0 -0
  44. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/requirements.txt +0 -0
  45. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/security.rst +0 -0
  46. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/troubleshooting.rst +0 -0
  47. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/workflows.rst +0 -0
  48. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/docs/youtrack-concepts.rst +0 -0
  49. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/package-lock.json +0 -0
  50. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/package.json +0 -0
  51. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/__init__.py +0 -0
  52. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/conftest.py +0 -0
  53. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_auth.py +0 -0
  54. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_boards.py +0 -0
  55. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_config.py +0 -0
  56. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_issues.py +0 -0
  57. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_logging.py +0 -0
  58. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_main.py +0 -0
  59. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_projects.py +0 -0
  60. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_reports.py +0 -0
  61. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_security.py +0 -0
  62. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_time.py +0 -0
  63. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tests/test_users.py +0 -0
  64. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/tox.ini +0 -0
  65. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/__init__.py +0 -0
  66. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/articles.py +0 -0
  67. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/auth.py +0 -0
  68. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/boards.py +0 -0
  69. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/cache.py +0 -0
  70. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/cli_utils/__init__.py +0 -0
  71. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/cli_utils/aliases.py +0 -0
  72. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/client.py +0 -0
  73. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/commands/__init__.py +0 -0
  74. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/commands/boards.py +0 -0
  75. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/commands/common.py +0 -0
  76. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/commands/issues.py +0 -0
  77. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/commands/projects.py +0 -0
  78. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/commands/time_tracking.py +0 -0
  79. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/commands/users.py +0 -0
  80. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/common.py +0 -0
  81. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/config.py +0 -0
  82. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/exceptions.py +0 -0
  83. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/issues.py +0 -0
  84. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/logging.py +0 -0
  85. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/main.py +0 -0
  86. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/performance.py +0 -0
  87. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/progress.py +0 -0
  88. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/projects.py +0 -0
  89. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/py.typed +0 -0
  90. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/reports.py +0 -0
  91. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/security.py +0 -0
  92. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/time.py +0 -0
  93. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/users.py +0 -0
  94. {youtrack_cli-0.3.1 → youtrack_cli-0.3.3}/youtrack_cli/utils.py +0 -0
@@ -40,7 +40,8 @@
40
40
  "mcp__github__create_issue",
41
41
  "mcp__claude-code__Read",
42
42
  "mcp__claude-code__Grep",
43
- "WebFetch(domain:github.com)"
43
+ "WebFetch(domain:github.com)",
44
+ "Bash(git remote get-url:*)"
44
45
  ],
45
46
  "deny": []
46
47
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: youtrack-cli
3
- Version: 0.3.1
3
+ Version: 0.3.3
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
  ```
@@ -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
 
@@ -52,8 +52,8 @@ format-check:
52
52
  [group('quality')]
53
53
  typecheck:
54
54
  #!/usr/bin/env bash
55
- echo "Running mypy type checker..."
56
- uv run mypy youtrack_cli
55
+ echo "Running ty type checker..."
56
+ uv run ty youtrack_cli
57
57
  echo "✅ Type checking complete"
58
58
 
59
59
  [group('quality')]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "youtrack-cli"
3
- version = "0.3.1"
3
+ version = "0.3.3"
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"
@@ -209,7 +209,7 @@ class TestAdminManager:
209
209
 
210
210
  with patch("httpx.AsyncClient") as mock_client:
211
211
  mock_response = Mock()
212
- mock_response.json.return_value = mock_groups
212
+ mock_response.json.return_value = {"usergroups": mock_groups}
213
213
  mock_response.raise_for_status.return_value = None
214
214
 
215
215
  mock_client.return_value.__aenter__.return_value.get.return_value = mock_response
@@ -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.1"
1754
+ version = "0.3.3"
1755
1755
  source = { editable = "." }
1756
1756
  dependencies = [
1757
1757
  { name = "click" },
@@ -151,7 +151,7 @@ class AdminManager:
151
151
  async with httpx.AsyncClient() as client:
152
152
  try:
153
153
  response = await client.get(
154
- f"{credentials.base_url.rstrip('/')}/api/admin/license",
154
+ f"{credentials.base_url.rstrip('/')}/api/admin/globalSettings/license",
155
155
  headers=headers,
156
156
  timeout=10.0,
157
157
  )
@@ -192,7 +192,7 @@ class AdminManager:
192
192
  async with httpx.AsyncClient() as client:
193
193
  try:
194
194
  response = await client.get(
195
- f"{credentials.base_url.rstrip('/')}/api/admin/license/usage",
195
+ f"{credentials.base_url.rstrip('/')}/api/admin/globalSettings/license/usage",
196
196
  headers=headers,
197
197
  timeout=10.0,
198
198
  )
@@ -234,7 +234,7 @@ class AdminManager:
234
234
  async with httpx.AsyncClient() as client:
235
235
  try:
236
236
  response = await client.get(
237
- f"{credentials.base_url.rstrip('/')}/api/admin/health",
237
+ f"{credentials.base_url.rstrip('/')}/api/admin/globalSettings/systemSettings",
238
238
  headers=headers,
239
239
  timeout=10.0,
240
240
  )
@@ -327,14 +327,15 @@ class AdminManager:
327
327
  async with httpx.AsyncClient() as client:
328
328
  try:
329
329
  response = await client.get(
330
- f"{credentials.base_url.rstrip('/')}/api/admin/groups",
330
+ f"{credentials.base_url.rstrip('/')}/api/rest/usergroups",
331
331
  headers=headers,
332
332
  params=params,
333
333
  timeout=10.0,
334
334
  )
335
335
  response.raise_for_status()
336
336
 
337
- groups = response.json()
337
+ groups_response = response.json()
338
+ groups = groups_response.get("usergroups", [])
338
339
  return {"status": "success", "data": groups}
339
340
 
340
341
  except httpx.HTTPError as e:
@@ -378,7 +379,7 @@ class AdminManager:
378
379
  async with httpx.AsyncClient() as client:
379
380
  try:
380
381
  response = await client.post(
381
- f"{credentials.base_url.rstrip('/')}/api/admin/groups",
382
+ f"{credentials.base_url.rstrip('/')}/api/rest/usergroups",
382
383
  headers=headers,
383
384
  json=group_data,
384
385
  params={"fields": "id,name,description"},
@@ -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