chore: remove YouTrack integration files and documentation

This commit is contained in:
2026-02-21 13:29:34 +05:30
parent d609974f8e
commit b5c843ca69
5 changed files with 0 additions and 795 deletions

View File

@@ -1,96 +0,0 @@
# YouTrack API References
## Core API Endpoints
### Base URL
Your YouTrack instance: `https://your-instance.youtrack.cloud/`
API base: `https://your-instance.youtrack.cloud/api/`
### Authentication
- Use permanent token with `Authorization: Bearer <token>` header
- To generate a token: From main navigation menu, select **Administration** > **Access Management** > **Users**, find your user, and generate a permanent API token
### Projects
**List all projects**
- `GET /api/admin/projects?fields=id,name,shortName`
- Returns array of project objects with id, name, shortName
**Get specific project**
- `GET /api/admin/projects/{projectId}?fields=id,name,shortName,description`
### Issues
**List issues with query**
- `GET /api/issues?query={query}&fields={fields}`
- Query examples:
- `project: MyProject`
- `project: MyProject updated: 2026-01-01 ..`
- `assignee: me`
- Common fields to request:
- `id,summary,description,created,updated,project(id,name),customFields(name,value)`
**Get issue**
- `GET /api/issues/{issueId}`
**Create issue**
- `POST /api/issues`
- Body: `{"project": {"id": "projectId"}, "summary": "Summary", "description": "Description"}`
**Update issue**
- `POST /api/issues/{issueId}`
- Body: `{"summary": "New summary", "description": "New description"}`
### Time Tracking
**Get work items for an issue**
- `GET /api/issues/{issueId}/timeTracking/workItems`
- Returns array of work items with:
- `id`, `date`, `duration` (in minutes), `author`, `text`
**Work item structure:**
```json
{
"id": "...",
"date": "2026-01-15T10:30:00.000+0000",
"duration": {"minutes": 30},
"author": {"name": "User Name", "id": "..."},
"text": "Work description",
"type": {...}
}
```
### Knowledge Base (Articles)
**List articles**
- `GET /api/articles?project={projectId}`
**Get article**
- `GET /api/articles/{articleId}`
**Create article**
- `POST /api/articles`
- Body: `{"project": {"id": "projectId"}, "title": "Title", "content": "Content"}`
## Query Language Examples
```
project: MyProject
project: MyProject assignee: me
project: MyProject updated >= 2026-01-01
priority: Critical
has: time
```
**Note:** Date filtering in REST API uses `updated >= YYYY-MM-DD` format, not `updated: date ..` format.
## Field IDs Reference
Common custom field IDs (may vary by setup):
- Priority: `priority`
- State: `State`
- Assignee: `Assignee`
- Due Date: `Due Date`
- Type: `Type`
Check your instance's actual field IDs in YouTrack UI or via API.

View File

@@ -1,218 +0,0 @@
---
name: youtrack
description: Interact with YouTrack project management system via REST API. Read projects and issues, create tasks, generate invoices from time tracking data, and manage knowledge base articles. Use for reading projects and work items, creating or updating issues, generating client invoices from time tracking, and working with knowledge base articles.
---
# YouTrack
YouTrack integration for project management, time tracking, and knowledge base.
## Quick Start
### Authentication
To generate a permanent token:
1. From the main navigation menu, select **Administration** > **Access Management** > **Users**
2. Find your user and click to open settings
3. Generate a new permanent API token
4. Set the token as an environment variable:
```bash
export YOUTRACK_TOKEN=your-permanent-token-here
```
**Important:** Configure your hourly rate (default $100/hour) by passing `--rate` to invoice_generator.py or updating `hourly_rate` parameter in your code.
Then use any YouTrack script:
```bash
# List all projects
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud --list-projects
# List issues in a project
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud --list-issues "project: MyProject"
# Generate invoice for a project
python3 scripts/invoice_generator.py --url https://your-instance.youtrack.cloud --project MyProject --month "January 2026" --from-date "2026-01-01"
```
## Python Scripts
### `scripts/youtrack_api.py`
Core API client for all YouTrack operations.
**In your Python code:**
```python
from youtrack_api import YouTrackAPI
api = YouTrackAPI('https://your-instance.youtrack.cloud', token='your-token')
# Projects
projects = api.get_projects()
project = api.get_project('project-id')
# Issues
issues = api.get_issues(query='project: MyProject')
issue = api.get_issue('issue-id')
# Create issue
api.create_issue('project-id', 'Summary', 'Description')
# Work items (time tracking)
work_items = api.get_work_items('issue-id')
issue_with_time = api.get_issue_with_work_items('issue-id')
# Knowledge base
articles = api.get_articles()
article = api.get_article('article-id')
api.create_article('project-id', 'Title', 'Content')
```
**CLI usage:**
```bash
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud \
--token YOUR_TOKEN \
--list-projects
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud \
--get-issue ABC-123
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud \
--get-articles
```
### `scripts/invoice_generator.py`
Generate client invoices from time tracking data.
**In your Python code:**
```python
from youtrack_api import YouTrackAPI
from invoice_generator import InvoiceGenerator
api = YouTrackAPI('https://your-instance.youtrack.cloud', token='your-token')
generator = InvoiceGenerator(api, hourly_rate=100.0)
# Get time data for a project
project_data = generator.get_project_time_data('project-id', from_date='2026-01-01')
# Generate invoice
invoice_text = generator.generate_invoice_text(project_data, month='January 2026')
print(invoice_text)
```
**CLI usage:**
```bash
python3 scripts/invoice_generator.py \
--url https://your-instance.youtrack.cloud \
--project MyProject \
--from-date 2026-01-01 \
--month "January 2026" \
--rate 100 \
--format text
```
Save the text output and print to PDF for clients.
## Common Workflows
### 1. List All Projects
```bash
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud --list-projects
```
### 2. Find Issues in a Project
```bash
# All issues in a project
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud --list-issues "project: MyProject"
# Issues updated since a date
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud --list-issues "project: MyProject updated >= 2026-01-01"
# Issues assigned to you
python3 scripts/youtrack_api.py --url https://your-instance.youtrack.cloud --list-issues "assignee: me"
```
### 3. Create a New Issue
```python
from youtrack_api import YouTrackAPI
api = YouTrackAPI('https://your-instance.youtrack.cloud')
api.create_issue(
project_id='MyProject',
summary='Task title',
description='Task description'
)
```
### 4. Generate Monthly Invoice
```bash
# Generate invoice for January 2026
python3 scripts/invoice_generator.py \
--url https://your-instance.youtrack.cloud \
--project ClientProject \
--from-date 2026-01-01 \
--month "January 2026" \
--rate 100 \
--format text > invoice.txt
```
Save the text output and print to PDF for clients.
### 5. Read Knowledge Base
```python
from youtrack_api import YouTrackAPI
api = YouTrackAPI('https://your-instance.youtrack.cloud')
# All articles
articles = api.get_articles()
# Articles for specific project
articles = api.get_articles(project_id='MyProject')
# Get specific article
article = api.get_article('article-id')
```
## Billing Logic
Invoice generator uses this calculation:
1. Sum all time tracked per issue (in minutes)
2. Convert to 30-minute increments (round up)
3. Minimum charge is 30 minutes (at configured rate/2)
4. Multiply by rate (default $100/hour = $50 per half-hour)
Examples:
- 15 minutes → $50 (30 min minimum)
- 35 minutes → $100 (rounded to 60 min)
- 60 minutes → $100
- 67 minutes → $150 (rounded to 90 min)
## Environment Variables
- `YOUTRACK_TOKEN`: Your permanent API token (recommended over passing as argument)
- Set with `export YOUTRACK_TOKEN=your-token`
## API Details
See `REFERENCES.md` for:
- Complete API endpoint documentation
- Query language examples
- Field IDs and structures
## Error Handling
Scripts will raise errors for:
- Missing or invalid token
- Network issues
- API errors (404, 403, etc.)
Check stderr for error details.

View File

@@ -1,11 +0,0 @@
{
"owner": "digisal",
"slug": "youtrack-digisal",
"displayName": "YouTrack Project Management",
"latest": {
"version": "1.0.1",
"publishedAt": 1769572337862,
"commit": "https://github.com/clawdbot/skills/commit/62ea9dc7577ed1e4c2a99c7d470bc5afbc58d356"
},
"history": []
}

View File

@@ -1,221 +0,0 @@
#!/usr/bin/env python3
"""
YouTrack Invoice Generator
Generates invoices from time tracking data in YouTrack projects.
"""
import os
import sys
import json
import argparse
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
from youtrack_api import YouTrackAPI
class InvoiceGenerator:
"""Generate invoices from YouTrack time tracking data."""
def __init__(self, api: YouTrackAPI, hourly_rate: float = 100.0):
"""
Initialize invoice generator.
Args:
api: YouTrackAPI instance
hourly_rate: Rate per hour (default $100)
"""
self.api = api
self.hourly_rate = hourly_rate
self.rate_per_half_hour = hourly_rate / 2
def get_project_time_data(self, project_id: str, from_date: Optional[str] = None) -> Dict[str, Any]:
"""
Get time tracking data for a project.
Args:
project_id: Project ID
from_date: Optional start date (not currently supported in REST API)
Returns:
Dictionary with time data per issue
"""
# Build query for project issues
# Note: Date filtering with 'updated:' syntax not supported in REST API
# Use 'updated >= YYYY-MM-DD' format instead
query = f'project: {project_id}'
if from_date:
query += f' updated >= {from_date}'
issues = self.api.get_issues(query=query)
project_data = {
'project': None,
'issues': [],
'total_minutes': 0,
'total_hours': 0,
'total_cost': 0
}
# Get project info
try:
project_data['project'] = self.api.get_project(project_id)
except:
# Fallback: use project name from first issue
if issues:
project_data['project'] = {'name': issues[0].get('project', {}).get('name', project_id)}
for issue in issues:
issue_id = issue.get('id')
issue_with_time = self.api.get_issue_with_work_items(issue_id)
work_items = issue_with_time.get('workItems', [])
total_minutes = sum(
wi.get('duration', {}).get('minutes', 0)
for wi in work_items
)
if total_minutes > 0:
hours = total_minutes / 60
cost = self._calculate_cost(total_minutes)
project_data['issues'].append({
'id': issue_id,
'summary': issue.get('summary', 'No summary'),
'description': issue.get('description', ''),
'work_items': work_items,
'total_minutes': total_minutes,
'total_hours': hours,
'cost': cost
})
project_data['total_minutes'] += total_minutes
project_data['total_cost'] += cost
project_data['total_hours'] = project_data['total_minutes'] / 60
return project_data
def _calculate_cost(self, minutes: int) -> float:
"""
Calculate cost based on time, billed in 30-minute increments.
Args:
minutes: Total minutes
Returns:
Total cost
"""
# Convert to 30-minute increments, round up
half_hour_units = (minutes + 29) // 30
# Minimum 30 minutes (1 half-hour unit)
half_hour_units = max(half_hour_units, 1)
return half_hour_units * self.rate_per_half_hour
def generate_invoice_text(self, project_data: Dict[str, Any], month: Optional[str] = None) -> str:
"""
Generate invoice as plain text (can be printed to PDF).
Args:
project_data: Project data from get_project_time_data()
month: Optional month label (e.g., "January 2026")
Returns:
Invoice text
"""
project = project_data['project'] or {}
project_name = project.get('name', 'Unknown Project')
lines = []
lines.append("=" * 70)
lines.append(f"INVOICE - {project_name}")
if month:
lines.append(f"Period: {month}")
lines.append("")
lines.append("WORK ITEMS")
lines.append("-" * 70)
for issue in project_data['issues']:
lines.append("")
lines.append(f"Task: {issue['summary']}")
lines.append(f"ID: {issue['id']}")
if issue['description']:
desc = issue['description'][:200] + "..." if len(issue['description']) > 200 else issue['description']
lines.append(f"Description: {desc}")
for wi in issue['work_items']:
duration = wi.get('duration', {})
mins = duration.get('minutes', 0)
date = wi.get('date', '')
author = wi.get('author', {}).get('name', 'Unknown')
lines.append(f" - {date}: {mins} min ({author})")
lines.append(f" Task total: {issue['total_hours']:.2f} hours (${issue['cost']:.2f})")
lines.append("")
lines.append("-" * 70)
lines.append(f"TOTAL: {project_data['total_hours']:.2f} hours")
lines.append(f"TOTAL COST: ${project_data['total_cost']:.2f}")
lines.append("=" * 70)
return "\n".join(lines)
def generate_invoice_json(self, project_data: Dict[str, Any], month: Optional[str] = None) -> str:
"""
Generate invoice as JSON for programmatic use.
Args:
project_data: Project data from get_project_time_data()
month: Optional month label
Returns:
JSON string
"""
invoice = {
'project': project_data['project'],
'period': month,
'items': project_data['issues'],
'summary': {
'total_minutes': project_data['total_minutes'],
'total_hours': project_data['total_hours'],
'total_cost': project_data['total_cost']
}
}
return json.dumps(invoice, indent=2)
def main():
"""CLI interface for generating invoices."""
parser = argparse.ArgumentParser(description='YouTrack Invoice Generator')
parser.add_argument('--url', required=True, help='YouTrack instance URL')
parser.add_argument('--token', help='API token (or set YOUTRACK_TOKEN env var)')
parser.add_argument('--project', required=True, help='Project ID to generate invoice for')
parser.add_argument('--from-date', help='Start date (YYYY-MM-DD)')
parser.add_argument('--month', help='Month label (e.g., "January 2026")')
parser.add_argument('--rate', type=float, default=100.0, help='Hourly rate (default: 100)')
parser.add_argument('--format', choices=['text', 'json'], default='text', help='Output format')
args = parser.parse_args()
try:
api = YouTrackAPI(args.url, args.token)
generator = InvoiceGenerator(api, hourly_rate=args.rate)
project_data = generator.get_project_time_data(args.project, args.from_date)
if args.format == 'text':
output = generator.generate_invoice_text(project_data, args.month)
else:
output = generator.generate_invoice_json(project_data, args.month)
print(output)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()

View File

@@ -1,249 +0,0 @@
#!/usr/bin/env python3
"""
YouTrack REST API Client
Handles authentication and basic API calls for YouTrack Cloud instances.
"""
import os
import sys
import json
import argparse
from urllib.parse import urljoin
from typing import Optional, Dict, List, Any
import urllib.request
import urllib.error
from datetime import datetime
class YouTrackAPI:
"""Simple YouTrack REST API client."""
def __init__(self, base_url: str, token: Optional[str] = None):
"""
Initialize YouTrack API client.
Args:
base_url: Your YouTrack instance URL (e.g., https://sl.youtrack.cloud)
token: Permanent API token (or set YOUTRACK_TOKEN env var)
"""
# Normalize base URL
self.base_url = base_url.rstrip('/')
self.token = token or os.environ.get('YOUTRACK_TOKEN')
if not self.token:
raise ValueError(
"YouTrack token required. Set YOUTRACK_TOKEN env var or pass as argument."
)
# Set up headers with bearer token auth
self.headers = {
'Authorization': f'Bearer {self.token}',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
def _make_request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
"""
Make an authenticated API request.
Args:
method: HTTP method (GET, POST, PUT, DELETE)
endpoint: API endpoint (e.g., '/api/issues')
data: Request body for POST/PUT
Returns:
Parsed JSON response
"""
url = urljoin(self.base_url, endpoint)
req_data = None
if data is not None:
req_data = json.dumps(data).encode('utf-8')
req = urllib.request.Request(
url,
data=req_data,
headers=self.headers,
method=method
)
try:
with urllib.request.urlopen(req) as response:
if response.status >= 400:
error_body = response.read().decode('utf-8')
raise RuntimeError(f"API Error {response.status}: {error_body}")
result = response.read().decode('utf-8')
if result:
return json.loads(result)
return {}
except urllib.error.HTTPError as e:
error_body = e.read().decode('utf-8') if e.fp else ''
raise RuntimeError(f"HTTP Error {e.code}: {error_body}")
except Exception as e:
raise RuntimeError(f"Request failed: {e}")
# Projects
def get_projects(self) -> List[Dict]:
"""Get all projects."""
result = self._make_request('GET', '/api/admin/projects?fields=id,name,shortName')
return result if isinstance(result, list) else []
def get_project(self, project_id: str) -> Dict:
"""Get a specific project by ID."""
return self._make_request('GET', f'/api/admin/projects/{project_id}?fields=id,name,shortName,description')
# Issues
def get_issues(self, query: Optional[str] = None, fields: str = 'id,summary,description,created,updated,project(id,name),customFields(name,value)') -> List[Dict]:
"""
Get issues, optionally filtered by a query.
Args:
query: YouTrack query language (e.g., 'project: MyProject')
fields: Comma-separated list of fields to return
Returns:
List of issues
"""
params = {'fields': fields}
if query:
params['query'] = query
# Build query string
query_string = '&'.join(f'{k}={urllib.parse.quote(str(v))}' for k, v in params.items())
endpoint = f'/api/issues?{query_string}'
result = self._make_request('GET', endpoint)
return result if isinstance(result, list) else []
def get_issue(self, issue_id: str) -> Dict:
"""Get a specific issue by ID."""
return self._make_request('GET', f'/api/issues/{issue_id}')
def create_issue(self, project_id: str, summary: str, description: str = '') -> Dict:
"""
Create a new issue.
Args:
project_id: Project ID or short name
summary: Issue summary
description: Issue description
Returns:
Created issue
"""
data = {
'project': {'id': project_id},
'summary': summary,
'description': description
}
return self._make_request('POST', '/api/issues', data)
def update_issue(self, issue_id: str, summary: Optional[str] = None, description: Optional[str] = None) -> Dict:
"""Update an issue's summary and/or description."""
data = {}
if summary is not None:
data['summary'] = summary
if description is not None:
data['description'] = description
return self._make_request('POST', f'/api/issues/{issue_id}', data)
# Time Tracking
def get_work_items(self, issue_id: str) -> List[Dict]:
"""Get all work items (time entries) for an issue."""
result = self._make_request('GET', f'/api/issues/{issue_id}/timeTracking/workItems?fields=id,date,duration(minutes),author(name),text')
# Convert date from milliseconds to ISO format
for wi in result:
if 'date' in wi and wi['date']:
wi['date'] = datetime.fromtimestamp(wi['date'] / 1000).isoformat()
return result if isinstance(result, list) else []
def get_issue_with_work_items(self, issue_id: str) -> Dict:
"""Get an issue with all its work items included."""
issue = self.get_issue(issue_id)
work_items = self.get_work_items(issue_id)
issue['workItems'] = work_items
return issue
# Knowledge Base (Articles)
def get_articles(self, project_id: Optional[str] = None) -> List[Dict]:
"""
Get knowledge base articles.
Args:
project_id: Optional project ID to filter by
Returns:
List of articles
"""
endpoint = '/api/articles'
if project_id:
endpoint += f'?project={project_id}'
result = self._make_request('GET', endpoint)
return result if isinstance(result, list) else []
def get_article(self, article_id: str) -> Dict:
"""Get a specific article by ID."""
return self._make_request('GET', f'/api/articles/{article_id}')
def create_article(self, project_id: str, title: str, content: str) -> Dict:
"""
Create a new knowledge base article.
Args:
project_id: Project ID
title: Article title
content: Article content
Returns:
Created article
"""
data = {
'project': {'id': project_id},
'title': title,
'content': content
}
return self._make_request('POST', '/api/articles', data)
def main():
"""CLI interface for testing the YouTrack API."""
parser = argparse.ArgumentParser(description='YouTrack API Client')
parser.add_argument('--url', required=True, help='YouTrack instance URL')
parser.add_argument('--token', help='API token (or set YOUTRACK_TOKEN env var)')
parser.add_argument('--list-projects', action='store_true', help='List all projects')
parser.add_argument('--list-issues', help='List issues (optional query)')
parser.add_argument('--get-issue', help='Get specific issue ID')
parser.add_argument('--get-articles', action='store_true', help='List articles')
args = parser.parse_args()
try:
api = YouTrackAPI(args.url, args.token)
if args.list_projects:
projects = api.get_projects()
print(json.dumps(projects, indent=2))
elif args.list_issues is not None:
issues = api.get_issues(query=args.list_issues)
print(json.dumps(issues, indent=2))
elif args.get_issue:
issue = api.get_issue_with_work_items(args.get_issue)
print(json.dumps(issue, indent=2))
elif args.get_articles:
articles = api.get_articles()
print(json.dumps(articles, indent=2))
else:
print("No action specified. Use --help for options.")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()