{
  "schemaVersion": "1.0",
  "item": {
    "slug": "yandex-tracker",
    "name": "Yandex Tracker",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/Kandler3/yandex-tracker",
    "canonicalUrl": "https://clawhub.ai/Kandler3/yandex-tracker",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/yandex-tracker",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=yandex-tracker",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "README.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-07T17:22:31.273Z",
      "expiresAt": "2026-05-14T17:22:31.273Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
        "contentDisposition": "attachment; filename=\"afrexai-annual-report-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/yandex-tracker"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/yandex-tracker",
    "agentPageUrl": "https://openagent3.xyz/skills/yandex-tracker/agent",
    "manifestUrl": "https://openagent3.xyz/skills/yandex-tracker/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/yandex-tracker/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Yandex Tracker (Python Client)",
        "body": "Use yandex_tracker_client to interact with Yandex Tracker API v2."
      },
      {
        "title": "How to work with this skill",
        "body": "Write and execute Python scripts to fulfill user requests. The workflow:\n\nWrite a self-contained Python script that initializes the client, performs all needed API calls, aggregates and formats the result, then prints it.\nSave to /tmp/tracker_script.py and run: python3 /tmp/tracker_script.py\nFor simple one-liners it's fine to use python3 -c \"...\", but prefer a file for anything multi-step.\n\nAggregation: The API returns lazy iterables — always collect into lists when you need to count, sort, filter, or display summaries. Combine multiple queries in one script (e.g. fetch issues then fetch comments for each, or join data from two queues) rather than making separate tool calls. Print structured output so the result is easy to read.\n\n# Example: aggregate issues by assignee across a queue\nissues = list(client.issues.find(filter={'queue': 'QUEUE'}, per_page=100))\nfrom collections import Counter\ncounts = Counter(str(i.assignee) for i in issues)\nfor assignee, n in counts.most_common():\n    print(f'{n:3d}  {assignee}')"
      },
      {
        "title": "Credentials",
        "body": "Required env (declare in skill metadata; set in openclaw.json → env):\n\nTRACKER_TOKEN — Required. Use a least-privilege OAuth token (oauth.yandex.ru) with only Tracker scope, or a temporary IAM token for Yandex Cloud. Do not use broad admin tokens.\nOne of: TRACKER_ORG_ID (Yandex 360, numeric) or TRACKER_CLOUD_ORG_ID (Yandex Cloud, string)"
      },
      {
        "title": "Client initialization (boilerplate — always include)",
        "body": "import os\nfrom yandex_tracker_client import TrackerClient\n\ntoken = os.environ['TRACKER_TOKEN']\norg_id = os.environ.get('TRACKER_ORG_ID')\ncloud_org_id = os.environ.get('TRACKER_CLOUD_ORG_ID')\n\nif cloud_org_id:\n    client = TrackerClient(token=token, cloud_org_id=cloud_org_id)\nelse:\n    client = TrackerClient(token=token, org_id=int(org_id))"
      },
      {
        "title": "Get issue by key",
        "body": "issue = client.issues['QUEUE-42']\nprint(issue.key, issue.summary, issue.status.id, issue.assignee.login if issue.assignee else None)"
      },
      {
        "title": "Custom fields",
        "body": "Real queues almost always have custom fields (story points, business fields, etc.). Their keys look like customFieldId (camelCase) and are queue-specific.\n\n# Read — access by attribute name (same as standard fields)\nprint(issue.storyPoints)       # returns None if absent\nprint(issue.as_dict())         # dump all fields including custom ones — use to discover keys\n\n# Update\nissue.update(storyPoints=5, myCustomField='value')\n\n# Filter by custom field in find()\nissues = client.issues.find(filter={'queue': 'QUEUE', 'storyPoints': {'from': 3}})\n\n# Discover all fields available in a queue (id is the key to use)\nfor f in client.fields.get_all():\n    print(f.id, f.name)\n\nUse issue.as_dict() on a real issue to discover which custom field keys the queue uses before writing update/filter code."
      },
      {
        "title": "issues.find() — search",
        "body": "# Tracker Query Language string (copy from Tracker UI)\nissues = client.issues.find('Queue: QUEUE Assignee: me() Status: inProgress')\n\n# Structured filter (dict)\nissues = client.issues.find(\n    filter={\n        'queue': 'QUEUE',               # queue key\n        'assignee': 'user_login',       # login or 'me()'\n        'author': 'user_login',\n        'status': 'inProgress',         # status .id\n        'type': 'bug',                  # issue type .id\n        'priority': 'critical',         # priority .id\n        'tags': ['backend', 'urgent'],  # all tags must match\n        'created': {'from': '2026-01-01', 'to': '2026-02-01'},\n        'updated': {'from': '2026-01-15'},\n        'deadline': {'to': '2026-03-01'},\n        'followers': 'user_login',\n        'components': 'component_name',\n    },\n    order=['-updatedAt', '+priority'],  # prefix: - desc, + asc\n    per_page=100,                       # max 100 per page; pagination is automatic\n)\n# Iterating auto-fetches all pages; wrap in list() to materialise\nissues = list(issues)\n\n# Batch fetch specific issues by key\nissues = list(client.issues.find(keys=['QUEUE-1', 'QUEUE-2', 'QUEUE-3']))"
      },
      {
        "title": "issues.create()",
        "body": "issue = client.issues.create(\n    queue='QUEUE',              # required\n    summary='Bug: login fails', # required\n    type={'name': 'Bug'},       # or {'id': 'bug'} — use client.issue_types.get_all()\n    description='Steps...',\n    assignee='user_login',\n    priority='critical',        # id: 'blocker','critical','major','normal','minor','trivial'\n    followers=['login1', 'login2'],\n    tags=['backend', 'urgent'],\n    components=['component_name'],\n    parent='QUEUE-10',          # parent issue key\n    sprint={'id': 123},         # sprint id\n)\nprint(issue.key)"
      },
      {
        "title": "issue.update()",
        "body": "issue.update(\n    summary='New title',\n    description='Updated text',\n    assignee='other_login',\n    priority='minor',\n    # Lists: pass full replacement OR mutation dict\n    tags=['new_tag'],                         # replace entirely\n    tags={'add': ['tag1'], 'remove': ['tag2']},  # partial mutation\n    followers={'add': ['login1']},\n    components={'add': ['comp'], 'remove': []},\n)"
      },
      {
        "title": "issue.transitions — status changes",
        "body": "# Always list first — transition IDs are queue-specific, never guess\nfor t in issue.transitions.get_all():\n    print(t.id, t.to.id, t.to.display)\n\n# Execute — all kwargs optional\nissue.transitions['close'].execute(\n    comment='Fixed in v2.3',\n    resolution='fixed',  # 'fixed','wontFix','duplicate','invalid','later' — queue-dependent\n)"
      },
      {
        "title": "issue.comments",
        "body": "for c in list(issue.comments.get_all()):\n    print(c.id, c.createdBy.login, c.text)\n\n# Create\nissue.comments.create(\n    text='Fixed in v2.3',\n    summonees=['login1', 'login2'],   # triggers @mention notification\n    attachments=['path/to/file.png'], # file paths — auto-uploaded, converted to IDs\n)\n\nissue.comments[42].update(text='Corrected note', summonees=['login1'])\nissue.comments[42].delete()"
      },
      {
        "title": "issue.links",
        "body": "for link in issue.links:\n    print(link.type.id, link.direction, link.object.key)\n\n# relationship values (standard):\n# 'relates', 'blocks', 'is blocked by',\n# 'duplicates', 'is duplicated by',\n# 'depends on', 'is dependent of',\n# 'is subtask for', 'is parent task for'\nissue.links.create(issue='OTHER-10', relationship='relates')\nissue.links[42].delete()"
      },
      {
        "title": "issue.attachments",
        "body": "for a in issue.attachments:\n    print(a.id, a.name, a.mimetype, a.size)\n\nissue.attachments.create('/path/to/file.txt')   # upload by path\na.download_to('/tmp/')                           # download to dir\nissue.attachments[42].delete()"
      },
      {
        "title": "issue.worklog",
        "body": "# Create worklog entry\nissue.worklog.create(\n    duration='PT1H30M',              # ISO 8601: PT30M, PT2H, P1D, P1DT2H30M\n    comment='Fixed auth bug',        # optional\n    start='2026-02-24T10:00:00+03:00',  # optional, defaults to now\n)\n\nfor w in list(issue.worklog.get_all()):\n    print(w.id, w.duration, w.comment, w.createdBy.login)\n\nissue.worklog[42].update(duration='PT2H', comment='Revised estimate')\nissue.worklog[42].delete()\n\n# Fetch worklogs across multiple issues at once\nentries = client.worklog.find(issue=['QUEUE-1', 'QUEUE-2'], createdBy='me()')"
      },
      {
        "title": "Queues",
        "body": "queue = client.queues['QUEUE']\nprint(queue.key, queue.name, queue.lead.login)\n\nfor q in client.queues.get_all():\n    print(q.key, q.name)"
      },
      {
        "title": "Bulk operations",
        "body": "# issues arg: list of keys OR list of issue objects from find()\n\n# Bulk update — any issue field as kwarg\nbc = client.bulkchange.update(\n    ['QUEUE-1', 'QUEUE-2', 'QUEUE-3'],\n    priority='minor',\n    assignee='user_login',\n    tags={'add': ['reviewed'], 'remove': ['draft']},\n)\nbc.wait()\nprint(bc.status)  # 'COMPLETE' or 'FAILED'\n\n# Bulk transition — transition id + optional field values\nbc = client.bulkchange.transition(\n    ['QUEUE-1', 'QUEUE-2'],\n    'close',                # transition id\n    resolution='wontFix',   # optional extra fields\n)\nbc.wait()\n\n# Bulk move to another queue\nbc = client.bulkchange.move(\n    ['QUEUE-1', 'QUEUE-2'],\n    'NEWQUEUE',\n    move_all_fields=False,        # copy all field values\n    move_to_initial_status=False, # reset to initial status\n)\nbc.wait()"
      },
      {
        "title": "Object fields reference",
        "body": "All objects are dynamic — accessing a missing attribute returns None, not AttributeError.\nCall .as_dict() on any object to get a plain dict.\n\nReference fields (status, priority, assignee, queue, type…) are Reference objects — access .id, .display, .key, or .login directly without a second request."
      },
      {
        "title": "Issue",
        "body": "AttributeNoteskeystr — 'QUEUE-42'summarystrdescriptionstr | NonestatusReference → .id e.g. 'inProgress', .display localized namepriorityReference → .id e.g. 'normal' / 'critical'typeReference → .id e.g. 'bug' / 'task'queueReference → .key e.g. 'QUEUE'assigneeReference | None → .login, .displayreporterReference → .login, .displaycreatedByReference → .logincreatedAtstr ISO-8601updatedAtstr ISO-8601deadlinestr | None — date '2026-03-01'tagslist[str]followerslist[Reference] → each .logincomponentslist[Reference] → each .displayfixVersionslist[Reference] → each .displaysprintlist[Reference] | None → each .displayparentReference | None → .keyvotesint"
      },
      {
        "title": "Comment",
        "body": "AttributeNotesidinttextstrtextHtmlstrcreatedByReference → .login, .displaycreatedAt / updatedAtstr ISO-8601summoneeslist[Reference]attachmentslist[Reference]"
      },
      {
        "title": "Link",
        "body": "AttributeNotesidinttypeReference → .id e.g. 'relates', 'blocks', 'is blocked by'directionstr — 'inward' / 'outward'objectReference → .key, .display (the linked issue)createdByReference → .logincreatedAtstr ISO-8601"
      },
      {
        "title": "Attachment",
        "body": "AttributeNotesidintnamestr — filenamecontentstr — download URLmimetypestrsizeint — bytescreatedByReference → .logincreatedAtstr ISO-8601"
      },
      {
        "title": "Transition",
        "body": "AttributeNotesidstr — transition key, e.g. 'close', 'start_progress'toReference → .id, .display (target status)screenReference | None"
      },
      {
        "title": "Worklog entry",
        "body": "AttributeNotesidintissueReference → .keycommentstr | Nonestartstr ISO-8601 datetimedurationstr ISO-8601 duration e.g. 'PT1H30M'createdByReference → .logincreatedAtstr ISO-8601"
      },
      {
        "title": "Queue",
        "body": "AttributeNotesidintkeystrnamestrdescriptionstr | NoneleadReference → .login, .displayassignAutobooldefaultTypeReferencedefaultPriorityReferenceteamUserslist[Reference] → each .login"
      },
      {
        "title": "User",
        "body": "AttributeNotesuidintloginstrfirstName / lastNamestrdisplaystr — full nameemailstr"
      },
      {
        "title": "BulkChange",
        "body": "AttributeNotesidstrstatusstr — 'COMPLETE' / 'FAILED' / 'PROCESSING'statusTextstrexecutionChunkPercentintexecutionIssuePercentint"
      },
      {
        "title": "Users",
        "body": "# Find user by login, email, or display name — use when user says \"assign to Иванов\"\nfor u in client.users.get_all():\n    print(u.login, u.display, u.email)\n\n# Get current user\nme = client.myself\nprint(me.login)"
      },
      {
        "title": "Sprints",
        "body": "# Boards list\nfor b in client.boards.get_all():\n    print(b.id, b.name)\n\n# Sprints for a board\nfor s in client.boards[123].sprints.get_all():\n    print(s.id, s.name, s.status)  # status: 'active','closed','draft'\n\n# Assign issue to sprint by id (found above)\nissue.update(sprint={'id': 456})"
      },
      {
        "title": "Error handling",
        "body": "from yandex_tracker_client import exceptions\n\ntry:\n    issue = client.issues['QUEUE-99999']\nexcept exceptions.NotFound:\n    print('Issue not found')\nexcept exceptions.Forbidden:\n    print('No access to this queue or issue')\nexcept exceptions.BadRequest as e:\n    print('Invalid field or value:', e)\nexcept exceptions.Conflict:\n    # Concurrent modification — re-fetch and retry\n    issue = client.issues['QUEUE-42']\n    issue.update(...)\n\nAvailable exception classes: NotFound, Forbidden, BadRequest, Conflict, TrackerClientError (base)."
      },
      {
        "title": "Notes",
        "body": "org_id must be an int; cloud_org_id is a string.\nFor Yandex Cloud orgs: use cloud_org_id= instead of org_id=, and optionally iam_token= for temporary IAM tokens instead of token=.\nTo get valid resolution IDs for a queue: [r.id for r in client.resolutions.get_all()].\nPrint results clearly — use formatted strings, tables, or JSON so the user gets a readable summary."
      }
    ],
    "body": "Yandex Tracker (Python Client)\n\nUse yandex_tracker_client to interact with Yandex Tracker API v2.\n\nHow to work with this skill\n\nWrite and execute Python scripts to fulfill user requests. The workflow:\n\nWrite a self-contained Python script that initializes the client, performs all needed API calls, aggregates and formats the result, then prints it.\nSave to /tmp/tracker_script.py and run: python3 /tmp/tracker_script.py\nFor simple one-liners it's fine to use python3 -c \"...\", but prefer a file for anything multi-step.\n\nAggregation: The API returns lazy iterables — always collect into lists when you need to count, sort, filter, or display summaries. Combine multiple queries in one script (e.g. fetch issues then fetch comments for each, or join data from two queues) rather than making separate tool calls. Print structured output so the result is easy to read.\n\n# Example: aggregate issues by assignee across a queue\nissues = list(client.issues.find(filter={'queue': 'QUEUE'}, per_page=100))\nfrom collections import Counter\ncounts = Counter(str(i.assignee) for i in issues)\nfor assignee, n in counts.most_common():\n    print(f'{n:3d}  {assignee}')\n\nCredentials\n\nRequired env (declare in skill metadata; set in openclaw.json → env):\n\nTRACKER_TOKEN — Required. Use a least-privilege OAuth token (oauth.yandex.ru) with only Tracker scope, or a temporary IAM token for Yandex Cloud. Do not use broad admin tokens.\nOne of: TRACKER_ORG_ID (Yandex 360, numeric) or TRACKER_CLOUD_ORG_ID (Yandex Cloud, string)\nClient initialization (boilerplate — always include)\nimport os\nfrom yandex_tracker_client import TrackerClient\n\ntoken = os.environ['TRACKER_TOKEN']\norg_id = os.environ.get('TRACKER_ORG_ID')\ncloud_org_id = os.environ.get('TRACKER_CLOUD_ORG_ID')\n\nif cloud_org_id:\n    client = TrackerClient(token=token, cloud_org_id=cloud_org_id)\nelse:\n    client = TrackerClient(token=token, org_id=int(org_id))\n\nGet issue by key\nissue = client.issues['QUEUE-42']\nprint(issue.key, issue.summary, issue.status.id, issue.assignee.login if issue.assignee else None)\n\nCustom fields\n\nReal queues almost always have custom fields (story points, business fields, etc.). Their keys look like customFieldId (camelCase) and are queue-specific.\n\n# Read — access by attribute name (same as standard fields)\nprint(issue.storyPoints)       # returns None if absent\nprint(issue.as_dict())         # dump all fields including custom ones — use to discover keys\n\n# Update\nissue.update(storyPoints=5, myCustomField='value')\n\n# Filter by custom field in find()\nissues = client.issues.find(filter={'queue': 'QUEUE', 'storyPoints': {'from': 3}})\n\n# Discover all fields available in a queue (id is the key to use)\nfor f in client.fields.get_all():\n    print(f.id, f.name)\n\n\nUse issue.as_dict() on a real issue to discover which custom field keys the queue uses before writing update/filter code.\n\nissues.find() — search\n# Tracker Query Language string (copy from Tracker UI)\nissues = client.issues.find('Queue: QUEUE Assignee: me() Status: inProgress')\n\n# Structured filter (dict)\nissues = client.issues.find(\n    filter={\n        'queue': 'QUEUE',               # queue key\n        'assignee': 'user_login',       # login or 'me()'\n        'author': 'user_login',\n        'status': 'inProgress',         # status .id\n        'type': 'bug',                  # issue type .id\n        'priority': 'critical',         # priority .id\n        'tags': ['backend', 'urgent'],  # all tags must match\n        'created': {'from': '2026-01-01', 'to': '2026-02-01'},\n        'updated': {'from': '2026-01-15'},\n        'deadline': {'to': '2026-03-01'},\n        'followers': 'user_login',\n        'components': 'component_name',\n    },\n    order=['-updatedAt', '+priority'],  # prefix: - desc, + asc\n    per_page=100,                       # max 100 per page; pagination is automatic\n)\n# Iterating auto-fetches all pages; wrap in list() to materialise\nissues = list(issues)\n\n# Batch fetch specific issues by key\nissues = list(client.issues.find(keys=['QUEUE-1', 'QUEUE-2', 'QUEUE-3']))\n\nissues.create()\nissue = client.issues.create(\n    queue='QUEUE',              # required\n    summary='Bug: login fails', # required\n    type={'name': 'Bug'},       # or {'id': 'bug'} — use client.issue_types.get_all()\n    description='Steps...',\n    assignee='user_login',\n    priority='critical',        # id: 'blocker','critical','major','normal','minor','trivial'\n    followers=['login1', 'login2'],\n    tags=['backend', 'urgent'],\n    components=['component_name'],\n    parent='QUEUE-10',          # parent issue key\n    sprint={'id': 123},         # sprint id\n)\nprint(issue.key)\n\nissue.update()\nissue.update(\n    summary='New title',\n    description='Updated text',\n    assignee='other_login',\n    priority='minor',\n    # Lists: pass full replacement OR mutation dict\n    tags=['new_tag'],                         # replace entirely\n    tags={'add': ['tag1'], 'remove': ['tag2']},  # partial mutation\n    followers={'add': ['login1']},\n    components={'add': ['comp'], 'remove': []},\n)\n\nissue.transitions — status changes\n# Always list first — transition IDs are queue-specific, never guess\nfor t in issue.transitions.get_all():\n    print(t.id, t.to.id, t.to.display)\n\n# Execute — all kwargs optional\nissue.transitions['close'].execute(\n    comment='Fixed in v2.3',\n    resolution='fixed',  # 'fixed','wontFix','duplicate','invalid','later' — queue-dependent\n)\n\nissue.comments\nfor c in list(issue.comments.get_all()):\n    print(c.id, c.createdBy.login, c.text)\n\n# Create\nissue.comments.create(\n    text='Fixed in v2.3',\n    summonees=['login1', 'login2'],   # triggers @mention notification\n    attachments=['path/to/file.png'], # file paths — auto-uploaded, converted to IDs\n)\n\nissue.comments[42].update(text='Corrected note', summonees=['login1'])\nissue.comments[42].delete()\n\nissue.links\nfor link in issue.links:\n    print(link.type.id, link.direction, link.object.key)\n\n# relationship values (standard):\n# 'relates', 'blocks', 'is blocked by',\n# 'duplicates', 'is duplicated by',\n# 'depends on', 'is dependent of',\n# 'is subtask for', 'is parent task for'\nissue.links.create(issue='OTHER-10', relationship='relates')\nissue.links[42].delete()\n\nissue.attachments\nfor a in issue.attachments:\n    print(a.id, a.name, a.mimetype, a.size)\n\nissue.attachments.create('/path/to/file.txt')   # upload by path\na.download_to('/tmp/')                           # download to dir\nissue.attachments[42].delete()\n\nissue.worklog\n# Create worklog entry\nissue.worklog.create(\n    duration='PT1H30M',              # ISO 8601: PT30M, PT2H, P1D, P1DT2H30M\n    comment='Fixed auth bug',        # optional\n    start='2026-02-24T10:00:00+03:00',  # optional, defaults to now\n)\n\nfor w in list(issue.worklog.get_all()):\n    print(w.id, w.duration, w.comment, w.createdBy.login)\n\nissue.worklog[42].update(duration='PT2H', comment='Revised estimate')\nissue.worklog[42].delete()\n\n# Fetch worklogs across multiple issues at once\nentries = client.worklog.find(issue=['QUEUE-1', 'QUEUE-2'], createdBy='me()')\n\nQueues\nqueue = client.queues['QUEUE']\nprint(queue.key, queue.name, queue.lead.login)\n\nfor q in client.queues.get_all():\n    print(q.key, q.name)\n\nBulk operations\n# issues arg: list of keys OR list of issue objects from find()\n\n# Bulk update — any issue field as kwarg\nbc = client.bulkchange.update(\n    ['QUEUE-1', 'QUEUE-2', 'QUEUE-3'],\n    priority='minor',\n    assignee='user_login',\n    tags={'add': ['reviewed'], 'remove': ['draft']},\n)\nbc.wait()\nprint(bc.status)  # 'COMPLETE' or 'FAILED'\n\n# Bulk transition — transition id + optional field values\nbc = client.bulkchange.transition(\n    ['QUEUE-1', 'QUEUE-2'],\n    'close',                # transition id\n    resolution='wontFix',   # optional extra fields\n)\nbc.wait()\n\n# Bulk move to another queue\nbc = client.bulkchange.move(\n    ['QUEUE-1', 'QUEUE-2'],\n    'NEWQUEUE',\n    move_all_fields=False,        # copy all field values\n    move_to_initial_status=False, # reset to initial status\n)\nbc.wait()\n\nObject fields reference\n\nAll objects are dynamic — accessing a missing attribute returns None, not AttributeError.\nCall .as_dict() on any object to get a plain dict.\n\nReference fields (status, priority, assignee, queue, type…) are Reference objects — access .id, .display, .key, or .login directly without a second request.\n\nIssue\nAttribute\tNotes\nkey\tstr — 'QUEUE-42'\nsummary\tstr\ndescription\tstr | None\nstatus\tReference → .id e.g. 'inProgress', .display localized name\npriority\tReference → .id e.g. 'normal' / 'critical'\ntype\tReference → .id e.g. 'bug' / 'task'\nqueue\tReference → .key e.g. 'QUEUE'\nassignee\tReference | None → .login, .display\nreporter\tReference → .login, .display\ncreatedBy\tReference → .login\ncreatedAt\tstr ISO-8601\nupdatedAt\tstr ISO-8601\ndeadline\tstr | None — date '2026-03-01'\ntags\tlist[str]\nfollowers\tlist[Reference] → each .login\ncomponents\tlist[Reference] → each .display\nfixVersions\tlist[Reference] → each .display\nsprint\tlist[Reference] | None → each .display\nparent\tReference | None → .key\nvotes\tint\nComment\nAttribute\tNotes\nid\tint\ntext\tstr\ntextHtml\tstr\ncreatedBy\tReference → .login, .display\ncreatedAt / updatedAt\tstr ISO-8601\nsummonees\tlist[Reference]\nattachments\tlist[Reference]\nLink\nAttribute\tNotes\nid\tint\ntype\tReference → .id e.g. 'relates', 'blocks', 'is blocked by'\ndirection\tstr — 'inward' / 'outward'\nobject\tReference → .key, .display (the linked issue)\ncreatedBy\tReference → .login\ncreatedAt\tstr ISO-8601\nAttachment\nAttribute\tNotes\nid\tint\nname\tstr — filename\ncontent\tstr — download URL\nmimetype\tstr\nsize\tint — bytes\ncreatedBy\tReference → .login\ncreatedAt\tstr ISO-8601\nTransition\nAttribute\tNotes\nid\tstr — transition key, e.g. 'close', 'start_progress'\nto\tReference → .id, .display (target status)\nscreen\tReference | None\nWorklog entry\nAttribute\tNotes\nid\tint\nissue\tReference → .key\ncomment\tstr | None\nstart\tstr ISO-8601 datetime\nduration\tstr ISO-8601 duration e.g. 'PT1H30M'\ncreatedBy\tReference → .login\ncreatedAt\tstr ISO-8601\nQueue\nAttribute\tNotes\nid\tint\nkey\tstr\nname\tstr\ndescription\tstr | None\nlead\tReference → .login, .display\nassignAuto\tbool\ndefaultType\tReference\ndefaultPriority\tReference\nteamUsers\tlist[Reference] → each .login\nUser\nAttribute\tNotes\nuid\tint\nlogin\tstr\nfirstName / lastName\tstr\ndisplay\tstr — full name\nemail\tstr\nBulkChange\nAttribute\tNotes\nid\tstr\nstatus\tstr — 'COMPLETE' / 'FAILED' / 'PROCESSING'\nstatusText\tstr\nexecutionChunkPercent\tint\nexecutionIssuePercent\tint\nUsers\n# Find user by login, email, or display name — use when user says \"assign to Иванов\"\nfor u in client.users.get_all():\n    print(u.login, u.display, u.email)\n\n# Get current user\nme = client.myself\nprint(me.login)\n\nSprints\n# Boards list\nfor b in client.boards.get_all():\n    print(b.id, b.name)\n\n# Sprints for a board\nfor s in client.boards[123].sprints.get_all():\n    print(s.id, s.name, s.status)  # status: 'active','closed','draft'\n\n# Assign issue to sprint by id (found above)\nissue.update(sprint={'id': 456})\n\nError handling\nfrom yandex_tracker_client import exceptions\n\ntry:\n    issue = client.issues['QUEUE-99999']\nexcept exceptions.NotFound:\n    print('Issue not found')\nexcept exceptions.Forbidden:\n    print('No access to this queue or issue')\nexcept exceptions.BadRequest as e:\n    print('Invalid field or value:', e)\nexcept exceptions.Conflict:\n    # Concurrent modification — re-fetch and retry\n    issue = client.issues['QUEUE-42']\n    issue.update(...)\n\n\nAvailable exception classes: NotFound, Forbidden, BadRequest, Conflict, TrackerClientError (base).\n\nNotes\norg_id must be an int; cloud_org_id is a string.\nFor Yandex Cloud orgs: use cloud_org_id= instead of org_id=, and optionally iam_token= for temporary IAM tokens instead of token=.\nTo get valid resolution IDs for a queue: [r.id for r in client.resolutions.get_all()].\nPrint results clearly — use formatted strings, tables, or JSON so the user gets a readable summary."
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/Kandler3/yandex-tracker",
    "publisherUrl": "https://clawhub.ai/Kandler3/yandex-tracker",
    "owner": "Kandler3",
    "version": "1.0.1",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/yandex-tracker",
    "downloadUrl": "https://openagent3.xyz/downloads/yandex-tracker",
    "agentUrl": "https://openagent3.xyz/skills/yandex-tracker/agent",
    "manifestUrl": "https://openagent3.xyz/skills/yandex-tracker/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/yandex-tracker/agent.md"
  }
}