youtrack-cli 0.3.4__tar.gz → 0.3.6__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.4 → youtrack_cli-0.3.6}/PKG-INFO +1 -1
  2. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/pyproject.toml +1 -1
  3. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/uv.lock +1 -1
  4. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/admin.py +61 -39
  5. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/main.py +9 -5
  6. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/.claude/settings.local.json +0 -0
  7. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/.github/dependabot.yml +0 -0
  8. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/.github/workflows/ci.yml +0 -0
  9. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/.github/workflows/release.yml +0 -0
  10. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/.gitignore +0 -0
  11. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/.pre-commit-config.yaml +0 -0
  12. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/.readthedocs.yaml +0 -0
  13. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/CLAUDE.md +0 -0
  14. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/PUBLISHING.md +0 -0
  15. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/README.md +0 -0
  16. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/Makefile +0 -0
  17. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/api/index.rst +0 -0
  18. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/changelog.rst +0 -0
  19. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/command-aliases.rst +0 -0
  20. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/admin.rst +0 -0
  21. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/articles.rst +0 -0
  22. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/auth.rst +0 -0
  23. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/boards.rst +0 -0
  24. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/config.rst +0 -0
  25. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/index.rst +0 -0
  26. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/issues.rst +0 -0
  27. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/projects.rst +0 -0
  28. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/reports.rst +0 -0
  29. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/time.rst +0 -0
  30. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/commands/users.rst +0 -0
  31. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/conf.py +0 -0
  32. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/configuration.rst +0 -0
  33. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/development.rst +0 -0
  34. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/index.rst +0 -0
  35. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/installation.rst +0 -0
  36. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/learning-path.rst +0 -0
  37. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/logging.rst +0 -0
  38. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/performance.md +0 -0
  39. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/progress-indicators.md +0 -0
  40. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/quickstart.rst +0 -0
  41. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/requirements.txt +0 -0
  42. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/security.rst +0 -0
  43. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/troubleshooting.rst +0 -0
  44. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/workflows.rst +0 -0
  45. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/docs/youtrack-concepts.rst +0 -0
  46. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/justfile +0 -0
  47. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/package-lock.json +0 -0
  48. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/package.json +0 -0
  49. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/__init__.py +0 -0
  50. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/conftest.py +0 -0
  51. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_admin.py +0 -0
  52. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_articles.py +0 -0
  53. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_auth.py +0 -0
  54. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_boards.py +0 -0
  55. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_config.py +0 -0
  56. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_issues.py +0 -0
  57. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_logging.py +0 -0
  58. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_main.py +0 -0
  59. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_projects.py +0 -0
  60. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_reports.py +0 -0
  61. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_security.py +0 -0
  62. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_time.py +0 -0
  63. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tests/test_users.py +0 -0
  64. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/tox.ini +0 -0
  65. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/__init__.py +0 -0
  66. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/articles.py +0 -0
  67. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/auth.py +0 -0
  68. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/boards.py +0 -0
  69. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/cache.py +0 -0
  70. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/cli_utils/__init__.py +0 -0
  71. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/cli_utils/aliases.py +0 -0
  72. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/client.py +0 -0
  73. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/commands/__init__.py +0 -0
  74. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/commands/articles.py +0 -0
  75. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/commands/boards.py +0 -0
  76. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/commands/common.py +0 -0
  77. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/commands/issues.py +0 -0
  78. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/commands/projects.py +0 -0
  79. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/commands/time_tracking.py +0 -0
  80. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/commands/users.py +0 -0
  81. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/common.py +0 -0
  82. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/config.py +0 -0
  83. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/exceptions.py +0 -0
  84. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/issues.py +0 -0
  85. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/logging.py +0 -0
  86. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/performance.py +0 -0
  87. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/progress.py +0 -0
  88. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/projects.py +0 -0
  89. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/py.typed +0 -0
  90. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/reports.py +0 -0
  91. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/security.py +0 -0
  92. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/time.py +0 -0
  93. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/youtrack_cli/users.py +0 -0
  94. {youtrack_cli-0.3.4 → youtrack_cli-0.3.6}/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.4
3
+ Version: 0.3.6
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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "youtrack-cli"
3
- version = "0.3.4"
3
+ version = "0.3.6"
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"
@@ -1751,7 +1751,7 @@ wheels = [
1751
1751
 
1752
1752
  [[package]]
1753
1753
  name = "youtrack-cli"
1754
- version = "0.3.4"
1754
+ version = "0.3.6"
1755
1755
  source = { editable = "." }
1756
1756
  dependencies = [
1757
1757
  { name = "click" },
@@ -5,7 +5,6 @@ from typing import Any, Optional
5
5
  import httpx
6
6
  from rich.console import Console
7
7
  from rich.table import Table
8
- from rich.text import Text
9
8
 
10
9
  from .auth import AuthManager
11
10
 
@@ -201,7 +200,7 @@ class AdminManager:
201
200
  async with httpx.AsyncClient() as client:
202
201
  try:
203
202
  response = await client.get(
204
- f"{credentials.base_url.rstrip('/')}/api/admin/globalSettings/license/usage",
203
+ f"{credentials.base_url.rstrip('/')}/api/admin/globalSettings/license?fields=id,username,license,error",
205
204
  headers=headers,
206
205
  timeout=10.0,
207
206
  )
@@ -242,8 +241,8 @@ class AdminManager:
242
241
 
243
242
  # List of endpoints to try, in order of preference
244
243
  endpoints = [
244
+ "/api/admin/globalSettings/systemSettings?fields=baseUrl,isApplicationReadOnly,maxUploadFileSize,maxExportItems,allowStatisticsCollection",
245
245
  "/api/admin/globalSettings/systemSettings",
246
- "/api/admin/globalSettings/systemSettings?fields=baseUrl,isApplicationReadOnly,maxUploadFileSize,maxExportItems",
247
246
  ]
248
247
 
249
248
  async with httpx.AsyncClient() as client:
@@ -370,7 +369,7 @@ class AdminManager:
370
369
  async with httpx.AsyncClient() as client:
371
370
  try:
372
371
  response = await client.get(
373
- f"{credentials.base_url.rstrip('/')}/api/rest/usergroups",
372
+ f"{credentials.base_url.rstrip('/')}/hub/api/rest/usergroups",
374
373
  headers=headers,
375
374
  params=params,
376
375
  timeout=10.0,
@@ -540,19 +539,32 @@ class AdminManager:
540
539
  license_info: License information dictionary
541
540
  """
542
541
  self.console.print("\n[bold blue]License Information[/bold blue]")
543
- self.console.print(f"[cyan]License Type:[/cyan] {license_info.get('type', 'N/A')}")
544
- self.console.print(f"[cyan]Licensed To:[/cyan] {license_info.get('licensedTo', 'N/A')}")
545
542
 
546
- if license_info.get("expirationDate"):
547
- self.console.print(f"[cyan]Expires:[/cyan] {license_info['expirationDate']}")
543
+ # Display username (maps to "Licensed To")
544
+ username = license_info.get("username", "N/A")
545
+ self.console.print(f"[cyan]Licensed To:[/cyan] {username}")
548
546
 
549
- if license_info.get("maxUsers"):
550
- self.console.print(f"[cyan]Max Users:[/cyan] {license_info['maxUsers']}")
547
+ # Display license key (truncated for security)
548
+ license_key = license_info.get("license", "N/A")
549
+ if license_key != "N/A" and len(license_key) > 20:
550
+ # Show only first 20 characters for security
551
+ license_display = f"{license_key[:20]}..."
552
+ else:
553
+ license_display = license_key
554
+ self.console.print(f"[cyan]License Key:[/cyan] {license_display}")
555
+
556
+ # Display error status if present
557
+ error = license_info.get("error")
558
+ if error:
559
+ self.console.print(f"[cyan]Error:[/cyan] [red]{error}[/red]")
560
+ status_color = "red"
561
+ status_text = "Error"
562
+ else:
563
+ # If no error and license key exists, assume active
564
+ is_active = license_key != "N/A" and license_key is not None
565
+ status_color = "green" if is_active else "red"
566
+ status_text = "Active" if is_active else "No License"
551
567
 
552
- # Display active status
553
- is_active = license_info.get("isActive", False)
554
- status_color = "green" if is_active else "red"
555
- status_text = "Active" if is_active else "Inactive"
556
568
  self.console.print(f"[cyan]Status:[/cyan] [{status_color}]{status_text}[/{status_color}]")
557
569
 
558
570
  def display_system_health(self, health_info: dict[str, Any]) -> None:
@@ -561,31 +573,41 @@ class AdminManager:
561
573
  Args:
562
574
  health_info: Health information dictionary
563
575
  """
564
- self.console.print("\n[bold blue]System Health[/bold blue]")
565
-
566
- # Overall status
567
- overall_status = health_info.get("status", "unknown")
568
- status_color = "green" if overall_status == "healthy" else "red"
569
- self.console.print(f"[cyan]Overall Status:[/cyan] [{status_color}]{overall_status.title()}[/{status_color}]")
570
-
571
- # Individual checks
572
- checks = health_info.get("checks", [])
573
- if checks:
574
- table = Table(title="Health Checks")
575
- table.add_column("Check", style="cyan")
576
- table.add_column("Status", style="magenta")
577
- table.add_column("Message", style="dim")
578
-
579
- for check in checks:
580
- status = check.get("status", "unknown")
581
- color = "green" if status == "healthy" else "red"
582
- table.add_row(
583
- check.get("name", "Unknown"),
584
- Text(status.title(), style=color),
585
- check.get("message", ""),
586
- )
587
-
588
- self.console.print(table)
576
+ self.console.print("\n[bold blue]System Settings[/bold blue]")
577
+
578
+ # Display system settings information
579
+ base_url = health_info.get("baseUrl", "N/A")
580
+ self.console.print(f"[cyan]Base URL:[/cyan] {base_url}")
581
+
582
+ # Display read-only status
583
+ is_read_only = health_info.get("isApplicationReadOnly", False)
584
+ read_only_color = "red" if is_read_only else "green"
585
+ read_only_text = "Read-Only" if is_read_only else "Read-Write"
586
+ self.console.print(f"[cyan]Application Mode:[/cyan] [{read_only_color}]{read_only_text}[/{read_only_color}]")
587
+
588
+ # Display file upload settings
589
+ max_upload_size = health_info.get("maxUploadFileSize", "N/A")
590
+ if max_upload_size != "N/A":
591
+ # Convert bytes to MB for readability
592
+ max_upload_mb = max_upload_size / (1024 * 1024)
593
+ self.console.print(f"[cyan]Max Upload Size:[/cyan] {max_upload_mb:.1f} MB")
594
+ else:
595
+ self.console.print(f"[cyan]Max Upload Size:[/cyan] {max_upload_size}")
596
+
597
+ # Display export settings
598
+ max_export_items = health_info.get("maxExportItems", "N/A")
599
+ self.console.print(f"[cyan]Max Export Items:[/cyan] {max_export_items}")
600
+
601
+ # Display statistics collection status
602
+ stats_collection = health_info.get("allowStatisticsCollection", False)
603
+ stats_color = "green" if stats_collection else "yellow"
604
+ stats_text = "Enabled" if stats_collection else "Disabled"
605
+ self.console.print(f"[cyan]Statistics Collection:[/cyan] [{stats_color}]{stats_text}[/{stats_color}]")
606
+
607
+ # Overall system status based on read-only mode
608
+ overall_status = "Healthy" if not is_read_only else "Read-Only Mode"
609
+ status_color = "green" if not is_read_only else "yellow"
610
+ self.console.print(f"[cyan]Overall Status:[/cyan] [{status_color}]{overall_status}[/{status_color}]")
589
611
 
590
612
  def display_user_groups(self, groups: list[dict[str, Any]]) -> None:
591
613
  """Display user groups in a formatted table.
@@ -1044,11 +1044,15 @@ def usage(ctx: click.Context) -> None:
1044
1044
  console.print(f"[red]Error:[/red] {result['message']}")
1045
1045
  return
1046
1046
 
1047
- usage_data = result["data"]
1048
- console.print("[bold]License Usage Statistics:[/bold]")
1049
- console.print(f"Total Users: {usage_data.get('totalUsers', 'N/A')}")
1050
- console.print(f"Active Users: {usage_data.get('activeUsers', 'N/A')}")
1051
- console.print(f"Remaining Users: {usage_data.get('remainingUsers', 'N/A')}")
1047
+ license_data = result["data"]
1048
+ console.print("[bold]License Information:[/bold]")
1049
+ console.print(f"License ID: {license_data.get('id', 'N/A')}")
1050
+ console.print(f"Username: {license_data.get('username', 'N/A')}")
1051
+ console.print(f"License Key: {license_data.get('license', 'N/A')}")
1052
+ if license_data.get("error"):
1053
+ console.print(f"[red]Error:[/red] {license_data['error']}")
1054
+ else:
1055
+ console.print("[green]Status: Valid[/green]")
1052
1056
 
1053
1057
  asyncio.run(run_license_usage())
1054
1058
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes